v2.4.3
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useContext, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu, Editor, defaultMenuOptions } from '../../src/';
|
||||
import { Gantt, ContextMenu, Editor, getMenuOptions } from '../../src/';
|
||||
import { context } from '@svar-ui/react-core';
|
||||
|
||||
export default function ContextMenuOptions({ skinSettings }) {
|
||||
@@ -17,9 +17,7 @@ export default function ContextMenuOptions({ skinSettings }) {
|
||||
const [options] = useState(() => {
|
||||
const ids = ['cut-task', 'copy-task', 'paste-task', 'delete-task'];
|
||||
let arr = [{ id: 'add-task:after', text: ' Add below', icon: 'wxi-plus' }];
|
||||
arr = arr.concat(
|
||||
defaultMenuOptions.filter((op) => ids.indexOf(op.id) >= 0),
|
||||
);
|
||||
arr = arr.concat(getMenuOptions().filter((op) => ids.indexOf(op.id) >= 0));
|
||||
arr.push({
|
||||
id: 'my-action',
|
||||
text: 'My action',
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function DropDownMenu({ skinSettings }) {
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
<Editor api={api} />
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -9,7 +9,6 @@ const parseDates = (data) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
export default function GanttBackend() {
|
||||
const server = 'https://master--svar-gantt-go--dev.webix.io';
|
||||
|
||||
@@ -29,30 +28,26 @@ export default function GanttBackend() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const init = useCallback(
|
||||
(api) => {
|
||||
setApi(api);
|
||||
const init = useCallback((api) => {
|
||||
setApi(api);
|
||||
|
||||
api.on('request-data', (ev) => {
|
||||
Promise.all([
|
||||
fetch(server + `/tasks/${ev.id}`)
|
||||
.then((res) => res.json())
|
||||
.then((arr) => parseDates(arr)),
|
||||
fetch(server + `/links/${ev.id}`).then((res) => res.json()),
|
||||
]).then(([tasks, links]) => {
|
||||
api.exec('provide-data', {
|
||||
id: ev.id,
|
||||
data: {
|
||||
tasks,
|
||||
links,
|
||||
},
|
||||
});
|
||||
api.on('request-data', (ev) => {
|
||||
Promise.all([
|
||||
fetch(server + `/tasks/${ev.id}`)
|
||||
.then((res) => res.json())
|
||||
.then((arr) => parseDates(arr)),
|
||||
fetch(server + `/links/${ev.id}`).then((res) => res.json()),
|
||||
]).then(([tasks, links]) => {
|
||||
api.exec('provide-data', {
|
||||
id: ev.id,
|
||||
data: {
|
||||
tasks,
|
||||
links,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu, Editor, defaultEditorItems } from '../../src';
|
||||
import { Gantt, ContextMenu, Editor, getEditorItems } from '../../src';
|
||||
import { RadioButtonGroup } from '@svar-ui/react-core';
|
||||
import './GanttDurationUnitChanges.css';
|
||||
|
||||
@@ -18,18 +18,18 @@ export default function GanttDurationUnitChanges({ skinSettings }) {
|
||||
);
|
||||
|
||||
const options = [
|
||||
{ id: 'hour', label: 'Hour' },
|
||||
{ id: 'day', label: 'Day' },
|
||||
{ id: 'hour', label: 'Hour' },
|
||||
];
|
||||
|
||||
const [durationUnit, setDurationUnit] = useState('hour');
|
||||
const [scales, setScales] = useState(scalesMap['hour']);
|
||||
const [durationUnit, setDurationUnit] = useState('day');
|
||||
const [scales, setScales] = useState(scalesMap['day']);
|
||||
|
||||
const [api, setApi] = useState(null);
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
defaultEditorItems.map((ed) => ({
|
||||
getEditorItems().map((ed) => ({
|
||||
...ed,
|
||||
...(ed.comp === 'date' && {
|
||||
config: { time: durationUnit === 'hour' },
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
Gantt,
|
||||
ContextMenu,
|
||||
Editor,
|
||||
defaultEditorItems,
|
||||
getEditorItems,
|
||||
defaultColumns,
|
||||
} from '../../src';
|
||||
import { format } from 'date-fns';
|
||||
@@ -16,7 +16,7 @@ export default function GanttDurationUnitHour({ skinSettings }) {
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
defaultEditorItems.map((ed) => ({
|
||||
getEditorItems().map((ed) => ({
|
||||
...ed,
|
||||
...(ed.comp === 'date' && { config: { time: true } }),
|
||||
})),
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import {
|
||||
Gantt,
|
||||
Editor,
|
||||
defaultEditorItems,
|
||||
registerEditorItem,
|
||||
} from '../../src';
|
||||
import { Gantt, Editor, getEditorItems, registerEditorItem } from '../../src';
|
||||
import { Comments } from '@svar-ui/react-comments';
|
||||
registerEditorItem('comments', Comments);
|
||||
|
||||
@@ -47,7 +42,7 @@ function GanttEditorComments(props) {
|
||||
const [api, setApi] = useState();
|
||||
const keys = useMemo(() => ['text', 'details'], []);
|
||||
const items = useMemo(() => {
|
||||
const items = defaultEditorItems.filter((op) => keys.indexOf(op.key) >= 0);
|
||||
const items = getEditorItems().filter((op) => keys.indexOf(op.key) >= 0);
|
||||
items.push({
|
||||
key: 'comments',
|
||||
comp: 'comments',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, defaultEditorItems } from '../../src';
|
||||
import { Gantt, Editor, getEditorItems } from '../../src';
|
||||
|
||||
function GanttEditorConfig({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
@@ -24,12 +24,14 @@ function GanttEditorConfig({ skinSettings }) {
|
||||
[],
|
||||
);
|
||||
|
||||
const defaultEditorItems = useMemo(() => getEditorItems(), []);
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
keys.map((key) => ({
|
||||
...defaultEditorItems.find((op) => op.key === key),
|
||||
})),
|
||||
[keys],
|
||||
[keys, defaultEditorItems],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -41,14 +43,16 @@ function GanttEditorConfig({ skinSettings }) {
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{api && <Editor
|
||||
api={api}
|
||||
items={items}
|
||||
bottomBar={bottomBar}
|
||||
topBar={false}
|
||||
placement="modal"
|
||||
autoSave={false}
|
||||
/>}
|
||||
{api && (
|
||||
<Editor
|
||||
api={api}
|
||||
items={items}
|
||||
bottomBar={bottomBar}
|
||||
topBar={false}
|
||||
placement="modal"
|
||||
autoSave={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Gantt,
|
||||
Editor,
|
||||
defaultEditorItems,
|
||||
registerEditorItem,
|
||||
defaultTaskTypes,
|
||||
getEditorItems,
|
||||
} from '../../src';
|
||||
import { RadioButtonGroup } from '@svar-ui/react-core';
|
||||
import UsersCustomCombo from '../custom/UsersCustomCombo.jsx';
|
||||
@@ -18,6 +18,8 @@ export default function GanttEditorCustomControls({ skinSettings }) {
|
||||
}, []);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const defaultEditorItems = getEditorItems();
|
||||
|
||||
const items = defaultEditorItems.map((item) => ({ ...item }));
|
||||
items.splice(
|
||||
defaultEditorItems.findIndex((d) => d.key === 'type'),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor } from '../../src';
|
||||
|
||||
function GanttEditorReadonly({...skinSettings}) {
|
||||
function GanttEditorReadonly({ ...skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const [api, setApi] = useState();
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import {
|
||||
Gantt,
|
||||
Editor,
|
||||
defaultEditorItems,
|
||||
registerEditorItem,
|
||||
} from '../../src';
|
||||
import { Gantt, Editor, getEditorItems, registerEditorItem } from '../../src';
|
||||
import { Tasklist } from '@svar-ui/react-tasklist';
|
||||
|
||||
registerEditorItem('tasks', Tasklist);
|
||||
@@ -71,7 +66,7 @@ export default function GanttEditorTasks({ skinSettings }) {
|
||||
|
||||
const items = useMemo(() => {
|
||||
const keys = ['text', 'details'];
|
||||
const items = defaultEditorItems.filter((op) => keys.indexOf(op.key) >= 0);
|
||||
const items = getEditorItems().filter((op) => keys.indexOf(op.key) >= 0);
|
||||
items.push({
|
||||
key: 'tasks',
|
||||
comp: 'tasks',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, defaultEditorItems } from '../../src';
|
||||
import { Gantt, Editor, getEditorItems } from '../../src';
|
||||
|
||||
function GanttEditorValidation({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
@@ -8,7 +8,7 @@ function GanttEditorValidation({ skinSettings }) {
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
defaultEditorItems.map((ed) => ({
|
||||
getEditorItems().map((ed) => ({
|
||||
...ed,
|
||||
...(ed.comp === 'text' && { required: true }),
|
||||
...(ed.comp === 'counter' && {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Fullscreen } from '../../src/';
|
||||
import { Gantt } from '../../src/';
|
||||
import { Fullscreen } from '@svar-ui/react-core';
|
||||
import './GanttFullscreen.css';
|
||||
|
||||
function GanttFullscreen({ skinSettings }) {
|
||||
@@ -11,11 +12,7 @@ function GanttFullscreen({ skinSettings }) {
|
||||
<h4>Click the "expand" icon, or click on Gantt and press Ctrl+Shift+F</h4>
|
||||
<div className="wx-0qqHrQ85 gtcell">
|
||||
<Fullscreen hotkey="ctrl+shift+f">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
/>
|
||||
<Gantt {...skinSettings} tasks={data.tasks} links={data.links} />
|
||||
</Fullscreen>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,10 @@ function GanttHolidays({ skinSettings }) {
|
||||
|
||||
const scales = useMemo(
|
||||
() => [
|
||||
{ unit: 'year', step: 1, format: 'yyyy' },
|
||||
{ unit: 'month', step: 2, format: 'MMMM yyy' },
|
||||
{ unit: 'week', step: 1, format: 'wo' },
|
||||
{ unit: 'day', step: 1, format: 'd, EEEE' /* , css: dayStyle */ }
|
||||
{ unit: 'year', step: 1, format: '%Y' },
|
||||
{ unit: 'month', step: 2, format: '%F %Y' },
|
||||
{ unit: 'week', step: 1, format: 'Week %W' },
|
||||
{ unit: 'day', step: 1, format: '%j, %l' },
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -23,36 +23,36 @@ function GanttLengthUnit({ skinSettings }) {
|
||||
switch (lengthUnit) {
|
||||
case 'minute':
|
||||
scales = [
|
||||
{ unit: 'day', step: 1, format: 'MMM d' },
|
||||
{ unit: 'hour', step: 1, format: 'HH:mm' },
|
||||
{ unit: 'day', step: 1, format: '%M %j' },
|
||||
{ unit: 'hour', step: 1, format: '%H:%i' },
|
||||
];
|
||||
break;
|
||||
case 'hour':
|
||||
scales = [
|
||||
{ unit: 'month', step: 1, format: 'MMM' },
|
||||
{ unit: 'day', step: 1, format: 'MMM d' },
|
||||
{ unit: 'month', step: 1, format: '%M' },
|
||||
{ unit: 'day', step: 1, format: '%M %j' },
|
||||
];
|
||||
break;
|
||||
case 'day':
|
||||
scales = [
|
||||
{ unit: 'month', step: 1, format: 'MMM' },
|
||||
{ unit: 'week', step: 1, format: 'w' },
|
||||
{ unit: 'month', step: 1, format: '%M' },
|
||||
{ unit: 'week', step: 1, format: '%w' },
|
||||
];
|
||||
break;
|
||||
case 'week':
|
||||
scales = [
|
||||
{ unit: 'year', step: 1, format: 'yyyy' },
|
||||
{ unit: 'month', step: 1, format: 'MMM' },
|
||||
{ unit: 'year', step: 1, format: '%Y' },
|
||||
{ unit: 'month', step: 1, format: '%M' },
|
||||
];
|
||||
break;
|
||||
case 'month':
|
||||
scales = [
|
||||
{ unit: 'year', step: 1, format: 'yyyy' },
|
||||
{ unit: 'quarter', step: 1, format: 'QQQ' },
|
||||
{ unit: 'year', step: 1, format: '%Y' },
|
||||
{ unit: 'quarter', step: 1, format: '%Q' },
|
||||
];
|
||||
break;
|
||||
case 'quarter':
|
||||
scales = [{ unit: 'year', step: 1, format: 'yyyy' }];
|
||||
scales = [{ unit: 'year', step: 1, format: '%Y' }];
|
||||
break;
|
||||
default:
|
||||
scales = bigScales;
|
||||
|
||||
@@ -22,11 +22,7 @@ function GanttLocale({ skinSettings }) {
|
||||
onChange={({ value }) => setLang(value)}
|
||||
/>
|
||||
</div>
|
||||
{lang === 'en' && (
|
||||
<GanttWidget
|
||||
skinSettings={skinSettings}
|
||||
/>
|
||||
)}
|
||||
{lang === 'en' && <GanttWidget skinSettings={skinSettings} />}
|
||||
{lang === 'cn' && (
|
||||
<Locale words={{ ...cn, ...cnCore }}>
|
||||
<GanttWidget skinSettings={skinSettings} />
|
||||
@@ -42,19 +38,20 @@ function GanttWidget(props) {
|
||||
const [api, setApi] = useState(null);
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
|
||||
const settings = {
|
||||
...skinSettings,
|
||||
tasks: data.tasks,
|
||||
links: data.links,
|
||||
scales: data.scales,
|
||||
zoom: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar api={api} />
|
||||
<div className="wx-ycv5Oz8L gtcell">
|
||||
<ContextMenu api={api}>
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
init={setApi}
|
||||
/>
|
||||
<Gantt {...settings} init={setApi} />
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
|
||||
@@ -8,17 +8,17 @@ import {
|
||||
isSameMonth,
|
||||
addMonths,
|
||||
addDays,
|
||||
format,
|
||||
differenceInDays,
|
||||
format,
|
||||
} from 'date-fns';
|
||||
import './GanttMinScaleUnit.css';
|
||||
|
||||
const options = [
|
||||
{ id: 1, label: 'sprint' },
|
||||
{ id: 2, label: 'month, sprint' },
|
||||
{ id: 3, label: 'month, sprint, week' },
|
||||
{ id: 4, label: 'month, sprint, week, day' },
|
||||
];
|
||||
{ id: 1, label: 'sprint' },
|
||||
{ id: 2, label: 'month, sprint' },
|
||||
{ id: 3, label: 'month, sprint, week' },
|
||||
{ id: 4, label: 'month, sprint, week, day' },
|
||||
];
|
||||
|
||||
export default function GanttMinScaleUnit({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
@@ -51,10 +51,10 @@ export default function GanttMinScaleUnit({ skinSettings }) {
|
||||
|
||||
const allScales = useMemo(
|
||||
() => [
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyy' },
|
||||
{ unit: 'month', step: 1, format: '%F %Y' },
|
||||
{ unit: 'sprint', step: 1, format: sprintFormat },
|
||||
{ unit: 'week', step: 1, format: 'w' },
|
||||
{ unit: 'day', step: 1, format: 'd' },
|
||||
{ unit: 'week', step: 1, format: '%w' },
|
||||
{ unit: 'day', step: 1, format: '%j' },
|
||||
],
|
||||
[],
|
||||
);
|
||||
@@ -67,7 +67,7 @@ export default function GanttMinScaleUnit({ skinSettings }) {
|
||||
if (scaleOption == 3) return allScales.slice(0, 3);
|
||||
return allScales;
|
||||
}, [scaleOption, allScales]);
|
||||
|
||||
|
||||
const registeredRef = useRef(false);
|
||||
if (!registeredRef.current) {
|
||||
registerScaleUnit('sprint', {
|
||||
@@ -128,8 +128,8 @@ export default function GanttMinScaleUnit({ skinSettings }) {
|
||||
links={data.links}
|
||||
scales={scales}
|
||||
zoom={true}
|
||||
start={new Date(2024, 3, 1)}
|
||||
end={new Date(2024, 5, 1)}
|
||||
start={new Date(2026, 3, 1)}
|
||||
end={new Date(2026, 5, 1)}
|
||||
cellWidth={60}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,33 +4,59 @@ import { Gantt } from '../../src/';
|
||||
import { Button } from '@svar-ui/react-core';
|
||||
import './GanttPerformance.css';
|
||||
|
||||
function RenderTime({ start }){
|
||||
const [end, setEnd] = useState(null);
|
||||
const [label, setLabel] = useState('')
|
||||
|
||||
let active = useRef(true);
|
||||
useEffect(() => {
|
||||
if (start && end) {
|
||||
setLabel((end - start)+" ms");
|
||||
}
|
||||
|
||||
active.current = true;
|
||||
setTimeout(() => {
|
||||
active.current = false;
|
||||
}, 1000);
|
||||
}, [start, end]);
|
||||
|
||||
window.__RENDER_METRICS_ENABLED__ = true;
|
||||
useEffect(() => {
|
||||
window.addEventListener('render-metric', (e) => {
|
||||
if (!active.current) return;
|
||||
if (e.detail.label === "chart") {
|
||||
setEnd(new Date());
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
delete window.__RENDER_METRICS_ENABLED__
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
return <span>{label}</span>
|
||||
}
|
||||
|
||||
const count = 10000;
|
||||
const years = 3;
|
||||
const data = getGeneratedData('', count, years);
|
||||
|
||||
function GanttPerformance(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const count = 1000;
|
||||
const years = 3;
|
||||
const data = useMemo(() => getGeneratedData('', count, years), []);
|
||||
|
||||
const [start, setStart] = useState(null);
|
||||
const outAreaRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (start && outAreaRef.current) {
|
||||
outAreaRef.current.innerHTML = new Date() - start;
|
||||
}
|
||||
}, [start]);
|
||||
|
||||
return (
|
||||
<div className="wx-KB3Eoqwm rows">
|
||||
<div className="wx-KB3Eoqwm row">
|
||||
{start ? (
|
||||
<>
|
||||
1 000 tasks ({years} years ) rendered in{' '}
|
||||
<span ref={outAreaRef}></span> ms
|
||||
10 000 tasks ({years} years ) rendered in{' '}
|
||||
<RenderTime start={start} />
|
||||
</>
|
||||
) : (
|
||||
<Button type="primary" onClick={() => setStart(new Date())}>
|
||||
Press me to render Gantt chart with 1 000 tasks
|
||||
<Button type="primary" onClick={() => setStart(new Date()) }>
|
||||
Press me to render Gantt chart with 10 000 tasks
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
align-items: end;
|
||||
font-family: var(--wx-font-family);
|
||||
font-size: var(--wx-font-size);
|
||||
padding-top: 12px;
|
||||
padding: 12px 0;
|
||||
--wx-label-width: 130px;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,16 @@
|
||||
.gantt.wx-RPSbwjNq.hide-links > .wx-gantt .wx-bar .wx-link {
|
||||
display: none;
|
||||
}
|
||||
.gantt.wx-RPSbwjNq.hide-delete-links > .wx-gantt .wx-delete-link {
|
||||
display: none;
|
||||
}
|
||||
.gantt.wx-RPSbwjNq.hide-delete-links > .wx-gantt .wx-line:hover {
|
||||
cursor: default;
|
||||
stroke: var(--wx-gantt-link-color);
|
||||
}
|
||||
.gantt.wx-RPSbwjNq.hide-delete-links > .wx-gantt .wx-line.wx-line-selectable {
|
||||
cursor: default;
|
||||
}
|
||||
.gantt.wx-RPSbwjNq.hide-drag > .wx-gantt .wx-bar {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, defaultColumns, Editor } from '../../src/';
|
||||
import { Gantt, defaultColumns } from '../../src/';
|
||||
import { Field, Switch } from '@svar-ui/react-core';
|
||||
import './GanttPreventActions.css';
|
||||
|
||||
function GanttPreventActions({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [edit, setEdit] = useState(true); // if false - cannot add and edit task
|
||||
const [add, setAdd] = useState(true); // if false - cannot add and edit task
|
||||
const [drag, setDrag] = useState(true); // if false - cannot drag tasks on scale
|
||||
const [order, setOrder] = useState(true); // if false - cannot reorder tasks in grid
|
||||
const [newLink, setNewLink] = useState(true); // if false - cannot create new links
|
||||
const [deleteLink, setDeleteLink] = useState(true); // if false - cannot delete links
|
||||
const [progress, setProgress] = useState(true); // if false - cannot edit progress in chart
|
||||
|
||||
const ignoreRef = useRef(false);
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const editRef = useRef(edit);
|
||||
const dragRef = useRef(drag);
|
||||
const orderRef = useRef(order);
|
||||
|
||||
useEffect(() => {
|
||||
editRef.current = edit;
|
||||
}, [edit]);
|
||||
|
||||
useEffect(() => {
|
||||
dragRef.current = drag;
|
||||
}, [drag]);
|
||||
@@ -32,9 +26,6 @@ function GanttPreventActions({ skinSettings }) {
|
||||
}, [order]);
|
||||
|
||||
const init = useCallback((gApi) => {
|
||||
setApi(gApi);
|
||||
|
||||
gApi.intercept('show-editor', () => editRef.current || ignoreRef.current);
|
||||
gApi.intercept('drag-task', (ev) => {
|
||||
if (typeof ev.top !== 'undefined') return orderRef.current;
|
||||
return dragRef.current; // ev.width && ev.left
|
||||
@@ -43,66 +34,46 @@ function GanttPreventActions({ skinSettings }) {
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
edit ? defaultColumns : defaultColumns.filter((a) => a.id != 'add-task'),
|
||||
[edit],
|
||||
add ? defaultColumns : defaultColumns.filter((a) => a.id != 'add-task'),
|
||||
[add],
|
||||
);
|
||||
|
||||
// for demo purposes: close editor when checkbox is unchecked
|
||||
useEffect(() => {
|
||||
if (!edit) {
|
||||
ignoreRef.current = true;
|
||||
api.exec('show-editor', { id: null });
|
||||
ignoreRef.current = false;
|
||||
}
|
||||
}, [edit, api]);
|
||||
|
||||
return (
|
||||
<div className="wx-RPSbwjNq rows">
|
||||
<div className="wx-RPSbwjNq bar">
|
||||
<Field label="Adding and editing" position={'left'}>
|
||||
{({ id }) => (
|
||||
<Switch
|
||||
value={edit}
|
||||
onChange={({ value }) => setEdit(value)}
|
||||
id={id}
|
||||
/>
|
||||
)}
|
||||
<Field label="Adding tasks" position={'left'}>
|
||||
<Switch value={add} onChange={({ value }) => setAdd(value)} />
|
||||
</Field>
|
||||
<Field label="Creating links" position={'left'}>
|
||||
{({ id }) => (
|
||||
<Switch
|
||||
value={newLink}
|
||||
onChange={({ value }) => setNewLink(value)}
|
||||
id={id}
|
||||
/>
|
||||
)}
|
||||
<Switch value={newLink} onChange={({ value }) => setNewLink(value)} />
|
||||
</Field>
|
||||
<Field label="Deleting links" position={'left'}>
|
||||
<Switch
|
||||
value={deleteLink}
|
||||
onChange={({ value }) => setDeleteLink(value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Dragging tasks" position={'left'}>
|
||||
{({ id }) => (
|
||||
<Switch
|
||||
value={drag}
|
||||
onChange={({ value }) => setDrag(value)}
|
||||
id={id}
|
||||
/>
|
||||
)}
|
||||
<Switch value={drag} onChange={({ value }) => setDrag(value)} />
|
||||
</Field>
|
||||
<Field label="Reordering tasks" position={'left'}>
|
||||
{({ id }) => (
|
||||
<Switch
|
||||
value={order}
|
||||
onChange={({ value }) => setOrder(value)}
|
||||
id={id}
|
||||
/>
|
||||
)}
|
||||
<Switch value={order} onChange={({ value }) => setOrder(value)} />
|
||||
</Field>
|
||||
<Field label="Editing progress" position={'left'}>
|
||||
<Switch
|
||||
value={progress}
|
||||
onChange={({ value }) => setProgress(value)}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
'wx-RPSbwjNq ' +
|
||||
('gantt' +
|
||||
(!edit ? ' hide-progress' : '') +
|
||||
(!newLink ? ' hide-links' : '') +
|
||||
(!drag ? ' hide-drag' : ''))
|
||||
(!deleteLink ? ' hide-delete-links' : '') +
|
||||
(!drag ? ' hide-drag' : '') +
|
||||
(!progress ? ' hide-progress' : ''))
|
||||
}
|
||||
>
|
||||
<Gantt
|
||||
@@ -113,7 +84,6 @@ function GanttPreventActions({ skinSettings }) {
|
||||
scales={data.scales}
|
||||
columns={columns}
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -74,13 +74,13 @@ export default function GanttScaleUnit(props) {
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={[
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyy' },
|
||||
{ unit: 'month', step: 1, format: '%F %Y' },
|
||||
{ unit: 'sprint', step: 1, format: sprintFormat },
|
||||
{ unit: 'day', step: 1, format: 'd' },
|
||||
{ unit: 'day', step: 1, format: '%j' },
|
||||
]}
|
||||
zoom={true}
|
||||
start={new Date(2024, 3, 1)}
|
||||
end={new Date(2024, 5, 1)}
|
||||
start={new Date(2026, 3, 1)}
|
||||
end={new Date(2026, 5, 1)}
|
||||
cellWidth={60}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -11,8 +11,8 @@ function GanttScales({ skinSettings }) {
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={complexScales}
|
||||
start={new Date(2024, 3, 1)}
|
||||
end={new Date(2024, 4, 12)}
|
||||
start={new Date(2026, 3, 1)}
|
||||
end={new Date(2026, 4, 12)}
|
||||
cellWidth={60}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -6,8 +6,8 @@ import './GanttStartEnd.css';
|
||||
|
||||
export default function GanttStartEnd({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const [start, setStart] = useState(new Date(2024, 3, 5));
|
||||
const [end, setEnd] = useState(new Date(2024, 4, 1));
|
||||
const [start, setStart] = useState(new Date(2026, 3, 5));
|
||||
const [end, setEnd] = useState(new Date(2026, 4, 1));
|
||||
const [autoScale, setAutoScale] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -15,33 +15,21 @@ export default function GanttStartEnd({ skinSettings }) {
|
||||
<Locale>
|
||||
<div className="wx-FJQN2sNt bar">
|
||||
<Field label="Start" position="left">
|
||||
{({ id }) => (
|
||||
<DatePicker
|
||||
value={start}
|
||||
id={id}
|
||||
onChange={({ value }) => setStart(value)}
|
||||
/>
|
||||
)}
|
||||
<DatePicker
|
||||
value={start}
|
||||
onChange={({ value }) => setStart(value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="End" position="left">
|
||||
{({ id }) => (
|
||||
<DatePicker
|
||||
value={end}
|
||||
id={id}
|
||||
onChange={({ value }) => setEnd(value)}
|
||||
/>
|
||||
)}
|
||||
<DatePicker value={end} onChange={({ value }) => setEnd(value)} />
|
||||
</Field>
|
||||
<Field label="autoScale" position="left">
|
||||
{({ id }) => (
|
||||
<div className="wx-FJQN2sNt input">
|
||||
<Switch
|
||||
value={autoScale}
|
||||
id={id}
|
||||
onChange={({ value }) => setAutoScale(value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="wx-FJQN2sNt input">
|
||||
<Switch
|
||||
value={autoScale}
|
||||
onChange={({ value }) => setAutoScale(value)}
|
||||
/>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
</Locale>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useMemo, useCallback, useContext } from 'react';
|
||||
import { context } from '@svar-ui/react-core';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Toolbar, Editor, defaultToolbarButtons } from '../../src/';
|
||||
import { Gantt, Toolbar, Editor, getToolbarButtons } from '../../src/';
|
||||
import './GanttToolbarButtons.css';
|
||||
|
||||
export default function GanttToolbarButtons({ skinSettings }) {
|
||||
@@ -15,7 +15,7 @@ export default function GanttToolbarButtons({ skinSettings }) {
|
||||
}, [helpers]);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const items = defaultToolbarButtons.filter((b) => {
|
||||
const items = getToolbarButtons().filter((b) => {
|
||||
return b.id?.indexOf('indent') === -1;
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function GanttToolbarCustom({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [api, setApi] = useState(null);
|
||||
const selectedValue = useStoreLater(api, "selected");
|
||||
const selectedValue = useStoreLater(api, 'selected');
|
||||
|
||||
function handleAdd() {
|
||||
if (!api) return;
|
||||
@@ -81,13 +81,15 @@ export default function GanttToolbarCustom({ skinSettings }) {
|
||||
}, [api, selectedValue, allItems]);
|
||||
|
||||
const GanttInitialised = useMemo(() => {
|
||||
return <Gantt
|
||||
{...(skinSettings || {})}
|
||||
init={setApi}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
return (
|
||||
<Gantt
|
||||
{...(skinSettings || {})}
|
||||
init={setApi}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
);
|
||||
}, [skinSettings, data.tasks, data.links, data.scales]);
|
||||
|
||||
return (
|
||||
|
||||
23
demos/cases/ProAutoSchedule.css
Normal file
23
demos/cases/ProAutoSchedule.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.demo.wx-vkht5Uh1 {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bar.wx-vkht5Uh1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
gap: 20px;
|
||||
border-bottom: var(--wx-gantt-border);
|
||||
}
|
||||
|
||||
.gantt.wx-vkht5Uh1 {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-left.wx-vkht5Uh1 .wx-field {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
42
demos/cases/ProAutoSchedule.jsx
Normal file
42
demos/cases/ProAutoSchedule.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, ContextMenu } from '../../src';
|
||||
import { DatePicker, Field } from '@svar-ui/react-core';
|
||||
import './ProAutoSchedule.css';
|
||||
|
||||
function ProAutoSchedule({ skinSettings }) {
|
||||
const [api, setApi] = useState(null);
|
||||
const [projectStart, setProjectStart] = useState(new Date(2026, 3, 2));
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<div className="demo wx-vkht5Uh1">
|
||||
<div className="bar wx-vkht5Uh1">
|
||||
<Field label="Project start" position="left">
|
||||
<DatePicker
|
||||
value={projectStart}
|
||||
onChange={({ value }) => setProjectStart(value)}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="gantt wx-vkht5Uh1">
|
||||
{api && <Editor api={api} />}
|
||||
<ContextMenu api={api}>
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
init={setApi}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
schedule={{ auto: true }}
|
||||
projectStart={projectStart}
|
||||
projectEnd={new Date(2026, 5, 2)}
|
||||
/>
|
||||
</ContextMenu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProAutoSchedule;
|
||||
54
demos/cases/ProBaselines.jsx
Normal file
54
demos/cases/ProBaselines.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, getEditorItems } from '../../src/';
|
||||
|
||||
function ProBaseline({ skinSettings }) {
|
||||
const data = useMemo(() => getData('day', { baselines: true }), []);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
getEditorItems().flatMap((item) =>
|
||||
item.key === 'links'
|
||||
? [
|
||||
...[
|
||||
{
|
||||
key: 'base_start',
|
||||
comp: 'date',
|
||||
label: 'Baseline start',
|
||||
},
|
||||
{
|
||||
key: 'base_end',
|
||||
comp: 'date',
|
||||
label: 'Baseline end',
|
||||
},
|
||||
{
|
||||
key: 'base_duration',
|
||||
comp: 'counter',
|
||||
hidden: true,
|
||||
},
|
||||
],
|
||||
item,
|
||||
]
|
||||
: item,
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
ref={setApi}
|
||||
{...skinSettings}
|
||||
baselines={true}
|
||||
cellHeight={45}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
/>
|
||||
{api && <Editor api={api} items={items} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProBaseline;
|
||||
43
demos/cases/ProCalendar.jsx
Normal file
43
demos/cases/ProCalendar.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor } from '../../src';
|
||||
import { Calendar } from '@svar-ui/gantt-store';
|
||||
|
||||
function ProCalendar({ skinSettings }) {
|
||||
const { tasks, links, scales } = useMemo(() => getData('calendar'), []);
|
||||
|
||||
const calendar = useMemo(
|
||||
() =>
|
||||
new Calendar({
|
||||
weekHours: {
|
||||
monday: 8,
|
||||
tuesday: 8,
|
||||
wednesday: 8,
|
||||
thursday: 8,
|
||||
friday: 8,
|
||||
saturday: 0,
|
||||
sunday: 0,
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
init={setApi}
|
||||
calendar={calendar}
|
||||
tasks={tasks}
|
||||
links={links}
|
||||
scales={scales}
|
||||
cellWidth={60}
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProCalendar;
|
||||
20
demos/cases/ProCalendarChanges.css
Normal file
20
demos/cases/ProCalendarChanges.css
Normal file
@@ -0,0 +1,20 @@
|
||||
.rows.wx-FkPcChng {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-FkPcChng {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
.gtcell.wx-FkPcChng {
|
||||
position: relative;
|
||||
height: calc(100% - 56px);
|
||||
border-top: var(--wx-gantt-border);
|
||||
}
|
||||
72
demos/cases/ProCalendarChanges.jsx
Normal file
72
demos/cases/ProCalendarChanges.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor } from '../../src';
|
||||
import { Calendar } from '@svar-ui/gantt-store';
|
||||
import { Button } from '@svar-ui/react-core';
|
||||
import './ProCalendarChanges.css';
|
||||
|
||||
function ProCalendarChanges({ skinSettings }) {
|
||||
const initialData = useMemo(() => getData('calendar'), []);
|
||||
const [tasks, setTasks] = useState(initialData.tasks);
|
||||
const { links, scales } = initialData;
|
||||
|
||||
const calendar = useMemo(
|
||||
() =>
|
||||
new Calendar({
|
||||
weekHours: {
|
||||
monday: 8,
|
||||
tuesday: 8,
|
||||
wednesday: 8,
|
||||
thursday: 8,
|
||||
friday: 8,
|
||||
saturday: 0,
|
||||
sunday: 0,
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const addNewRule = useCallback(() => {
|
||||
calendar.addRule((date) => {
|
||||
const weekday = date.getDay();
|
||||
if (weekday === 3) return 0;
|
||||
});
|
||||
|
||||
setTasks(
|
||||
api.serialize().map((task) => {
|
||||
if (!calendar.isWorkingDay(task.start)) {
|
||||
task.start = calendar.getNextWorkingDay(task.start);
|
||||
}
|
||||
return task;
|
||||
}),
|
||||
);
|
||||
}, [api, calendar]);
|
||||
|
||||
return (
|
||||
<div className="wx-FkPcChng rows">
|
||||
<div className="wx-FkPcChng bar">
|
||||
<span> Rule: every Wednesday is off</span>
|
||||
<Button type="primary" onClick={addNewRule}>
|
||||
Add rule
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="wx-FkPcChng gtcell">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
init={setApi}
|
||||
calendar={calendar}
|
||||
tasks={tasks}
|
||||
links={links}
|
||||
scales={scales}
|
||||
cellWidth={60}
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProCalendarChanges;
|
||||
12
demos/cases/ProCriticalPath.css
Normal file
12
demos/cases/ProCriticalPath.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.demo.wx-D71fWZ6y {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bar.wx-D71fWZ6y {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
border-bottom: var(--wx-gantt-border);
|
||||
}
|
||||
62
demos/cases/ProCriticalPath.jsx
Normal file
62
demos/cases/ProCriticalPath.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor } from '../../src';
|
||||
import { DatePicker, Field, Locale, RichSelect } from '@svar-ui/react-core';
|
||||
import './ProCriticalPath.css';
|
||||
|
||||
export default function ProCriticalPath({ skinSettings }) {
|
||||
const data = useMemo(() => getData('critical'), []);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
const [pathMode, setPathMode] = useState('flexible');
|
||||
const [projectStart, setProjectStart] = useState(new Date(2026, 3, 2));
|
||||
const [projectEnd, setProjectEnd] = useState(new Date(2026, 3, 12));
|
||||
|
||||
function init(ganttApi) {
|
||||
setApi(ganttApi);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="demo wx-D71fWZ6y">
|
||||
<Locale>
|
||||
<div className="bar wx-D71fWZ6y">
|
||||
<Field label="Mode" position="left">
|
||||
<RichSelect
|
||||
options={[
|
||||
{ id: 'flexible', label: 'Flexible' },
|
||||
{ id: 'strict', label: 'Strict' },
|
||||
]}
|
||||
value={pathMode}
|
||||
onChange={({ value }) => setPathMode(value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Project start" position="left">
|
||||
<DatePicker
|
||||
value={projectStart}
|
||||
onChange={({ value }) => setProjectStart(value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Project end" position="left">
|
||||
<DatePicker
|
||||
value={projectEnd}
|
||||
onChange={({ value }) => setProjectEnd(value)}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</Locale>
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
init={init}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
criticalPath={{ type: pathMode }}
|
||||
projectStart={projectStart}
|
||||
projectEnd={projectEnd}
|
||||
/>
|
||||
</div>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
10
demos/cases/ProMarkers.css
Normal file
10
demos/cases/ProMarkers.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.gt-cell.wx-g4H1PKcW {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.gt-cell.wx-g4H1PKcW > .wx-gantt .myMiddleClass {
|
||||
background-color: rgba(255, 84, 84, 0.77);
|
||||
}
|
||||
.gt-cell.wx-g4H1PKcW > .wx-gantt .myEndClass {
|
||||
background-color: rgba(54, 206, 124, 0.77);
|
||||
}
|
||||
42
demos/cases/ProMarkers.jsx
Normal file
42
demos/cases/ProMarkers.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import './ProMarkers.css';
|
||||
|
||||
const ProMarkers = ({ skinSettings }) => {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const markers = useMemo(
|
||||
() => [
|
||||
{
|
||||
start: new Date(2026, 3, 2),
|
||||
text: 'Start Project',
|
||||
},
|
||||
{
|
||||
start: new Date(2026, 3, 8),
|
||||
text: 'Today',
|
||||
css: 'myMiddleClass',
|
||||
},
|
||||
{
|
||||
start: new Date(2026, 4, 3),
|
||||
text: 'End Project',
|
||||
css: 'myEndClass',
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="wx-g4H1PKcW gt-cell">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
markers={markers}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProMarkers;
|
||||
23
demos/cases/ProScheduleAll.css
Normal file
23
demos/cases/ProScheduleAll.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.demo.wx-D71fWZ7y {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bar.wx-D71fWZ7y {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
gap: 20px;
|
||||
border-bottom: var(--wx-gantt-border);
|
||||
}
|
||||
|
||||
.gantt.wx-D71fWZ7y {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-D71fWZ7y .wx-field.wx-left {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
160
demos/cases/ProScheduleAll.jsx
Normal file
160
demos/cases/ProScheduleAll.jsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, ContextMenu } from '../../src';
|
||||
import { DatePicker, Field, Checkbox } from '@svar-ui/react-core';
|
||||
import { Calendar } from '@svar-ui/gantt-store';
|
||||
import './ProScheduleAll.css';
|
||||
|
||||
function ProScheduleAll({ skinSettings }) {
|
||||
const data = useMemo(
|
||||
() =>
|
||||
getData('calendar', {
|
||||
splitTasks: true,
|
||||
unscheduledTasks: true,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
const [tasks, setTasks] = useState(data.tasks);
|
||||
|
||||
const calendar = useMemo(() => new Calendar(), []);
|
||||
const [projectStart, setProjectStart] = useState(new Date(2026, 3, 2));
|
||||
const [projectEnd, setProjectEnd] = useState(new Date(2026, 4, 20));
|
||||
|
||||
const [criticalPath, setCriticalPath] = useState({ type: 'flexible' });
|
||||
const [baselines, setBaselines] = useState(true);
|
||||
const [unscheduledTasks, setUnscheduledTasks] = useState(true);
|
||||
const [splitTasks, setSplitTasks] = useState(true);
|
||||
|
||||
const [cellHeight, setCellHeight] = useState(44);
|
||||
|
||||
const markers = useMemo(
|
||||
() =>
|
||||
projectStart
|
||||
? [
|
||||
{
|
||||
text: 'Start',
|
||||
start: projectStart,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
[projectStart],
|
||||
);
|
||||
|
||||
function onCriticalPathChange() {
|
||||
setCriticalPath(
|
||||
criticalPath?.type === 'flexible' ? null : { type: 'flexible' },
|
||||
);
|
||||
}
|
||||
|
||||
function onBaselinesChange(ev) {
|
||||
setBaselines(ev.value);
|
||||
setCellHeight(ev.value ? 44 : 38);
|
||||
}
|
||||
|
||||
function onSplitChange() {
|
||||
setTasks(
|
||||
api.serialize().map((t) => {
|
||||
//recalculate duration
|
||||
if (t.segments) delete t.duration;
|
||||
return t;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
//calculate baselines after summary dates are set
|
||||
function init(ganttApi) {
|
||||
setApi(ganttApi);
|
||||
setTasks(
|
||||
ganttApi.serialize().map((t) => {
|
||||
return {
|
||||
...t,
|
||||
base_start: t.start,
|
||||
base_end: t.end,
|
||||
base_duration: t.segments ? 0 : t.duration,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/*data.links.push({
|
||||
source: 2,
|
||||
target: 3,
|
||||
type: "e2s",
|
||||
id: 100,
|
||||
});
|
||||
data.links.push({
|
||||
source: 30,
|
||||
target: 4,
|
||||
type: "e2s",
|
||||
id: 101,
|
||||
});*/
|
||||
|
||||
return (
|
||||
<div className="demo wx-D71fWZ7y">
|
||||
<div className="bar wx-D71fWZ7y">
|
||||
<Field label="Project start" position="left" width="250px">
|
||||
<DatePicker
|
||||
value={projectStart}
|
||||
onChange={({ value }) => setProjectStart(value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Project end" position="left" width="250px">
|
||||
<DatePicker
|
||||
value={projectEnd}
|
||||
onChange={({ value }) => setProjectEnd(value)}
|
||||
/>
|
||||
</Field>
|
||||
<Checkbox
|
||||
value={!!criticalPath}
|
||||
label="Critical path"
|
||||
onChange={onCriticalPathChange}
|
||||
/>
|
||||
<Checkbox
|
||||
value={baselines}
|
||||
label="Baselines"
|
||||
onChange={onBaselinesChange}
|
||||
/>
|
||||
<Checkbox
|
||||
value={unscheduledTasks}
|
||||
label="Unscheduled tasks"
|
||||
onChange={({ value }) => setUnscheduledTasks(value)}
|
||||
/>
|
||||
<Checkbox
|
||||
value={splitTasks}
|
||||
label="Split tasks"
|
||||
onChange={(ev) => {
|
||||
setSplitTasks(ev.value);
|
||||
onSplitChange();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="gantt wx-D71fWZ7y">
|
||||
{api && <Editor api={api} />}
|
||||
<ContextMenu api={api}>
|
||||
<Gantt
|
||||
init={init}
|
||||
{...skinSettings}
|
||||
cellWidth={50}
|
||||
cellHeight={cellHeight}
|
||||
tasks={tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
calendar={calendar}
|
||||
schedule={{ auto: true }}
|
||||
criticalPath={criticalPath}
|
||||
projectStart={projectStart}
|
||||
projectEnd={projectEnd}
|
||||
markers={markers}
|
||||
baselines={baselines}
|
||||
unscheduledTasks={unscheduledTasks}
|
||||
splitTasks={splitTasks}
|
||||
/>
|
||||
</ContextMenu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProScheduleAll;
|
||||
4
demos/cases/ProSplitTasks.css
Normal file
4
demos/cases/ProSplitTasks.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.gtcell.wx-4eOjA4yB {
|
||||
height: calc(100% - 50px);
|
||||
border-top: var(--wx-gantt-border);
|
||||
}
|
||||
28
demos/cases/ProSplitTasks.jsx
Normal file
28
demos/cases/ProSplitTasks.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu, Editor, Toolbar } from '../../src/';
|
||||
import './ProSplitTasks.css';
|
||||
|
||||
export default function ProSplitTasks({ skinSettings }) {
|
||||
const [api, setApi] = useState();
|
||||
const data = useMemo(() => getData('day', { splitTasks: true }), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar api={api} />
|
||||
<div className="gtcell">
|
||||
<ContextMenu api={api}>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
splitTasks={true}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
109
demos/cases/ProSummariesConvert.jsx
Normal file
109
demos/cases/ProSummariesConvert.jsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { useMemo, useCallback, useRef } from 'react';
|
||||
import { useStoreLater } from '@svar-ui/lib-react';
|
||||
import { getData } from '../data';
|
||||
import {
|
||||
Gantt,
|
||||
ContextMenu,
|
||||
Editor,
|
||||
getEditorItems,
|
||||
defaultTaskTypes,
|
||||
} from '../../src';
|
||||
|
||||
export default function SummariesConvert({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const gApi = useRef(null);
|
||||
|
||||
const toSummary = useCallback(
|
||||
(id, self) => {
|
||||
const api = gApi.current;
|
||||
if (!api) return;
|
||||
const task = api.getTask(id);
|
||||
if (!self) id = task.parent;
|
||||
|
||||
if (id && task.type !== 'summary') {
|
||||
api.exec('update-task', {
|
||||
id,
|
||||
task: { type: 'summary' },
|
||||
});
|
||||
}
|
||||
},
|
||||
[gApi],
|
||||
);
|
||||
|
||||
const toTask = useCallback(
|
||||
(id) => {
|
||||
const api = gApi.current;
|
||||
if (!api) return;
|
||||
const obj = api.getTask(id);
|
||||
if (obj && !obj.data?.length) {
|
||||
api.exec('update-task', {
|
||||
id,
|
||||
task: { type: 'task' },
|
||||
});
|
||||
}
|
||||
},
|
||||
[gApi],
|
||||
);
|
||||
|
||||
const init = useCallback(
|
||||
(api) => {
|
||||
gApi.current = api;
|
||||
|
||||
api.getState().tasks.forEach((task) => {
|
||||
if (task.data?.length) {
|
||||
toSummary(task.id, true);
|
||||
}
|
||||
});
|
||||
|
||||
api.on('add-task', ({ id, mode }) => {
|
||||
if (mode === 'child') toSummary(id);
|
||||
});
|
||||
|
||||
api.on('move-task', ({ id, source, mode, inProgress }) => {
|
||||
if (inProgress) return;
|
||||
if (mode == 'child') toSummary(id);
|
||||
else toTask(source);
|
||||
});
|
||||
|
||||
api.on('delete-task', ({ source }) => {
|
||||
toTask(source);
|
||||
});
|
||||
},
|
||||
[toSummary, toTask],
|
||||
);
|
||||
|
||||
const activeTaskId = useStoreLater(gApi.current, 'activeTask');
|
||||
|
||||
const items = useMemo(() => {
|
||||
const api = gApi.current;
|
||||
const task = activeTaskId ? api?.getTask(activeTaskId) : null;
|
||||
if (task) {
|
||||
return getEditorItems().map((item) => {
|
||||
item = { ...item };
|
||||
if (item.comp === 'select' && item.key === 'type') {
|
||||
item.options =
|
||||
task.type !== 'summary'
|
||||
? defaultTaskTypes.filter((t) => t.id !== 'summary')
|
||||
: [];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}, [activeTaskId, gApi.current]);
|
||||
|
||||
return (
|
||||
<div className="wx-TEIogFEZ gt-cell">
|
||||
<ContextMenu api={gApi.current}>
|
||||
<Gantt
|
||||
init={init}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{gApi.current && <Editor api={gApi.current} items={items} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
demos/cases/ProSummariesProgress.css
Normal file
9
demos/cases/ProSummariesProgress.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.gt-cell > .wx-gantt .wx-summary .wx-progress-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gt-cell.wx-OeNgRLo4,
|
||||
.wrapper.wx-OeNgRLo4 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
131
demos/cases/ProSummariesProgress.jsx
Normal file
131
demos/cases/ProSummariesProgress.jsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, getEditorItems, ContextMenu } from '../../src';
|
||||
import './ProSummariesProgress.css';
|
||||
|
||||
function SummariesProgress({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const tasks = data.tasks;
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const dayDiff = (next, prev) => {
|
||||
const d = (next - prev) / 1000 / 60 / 60 / 24;
|
||||
return Math.ceil(Math.abs(d));
|
||||
};
|
||||
|
||||
/**
|
||||
* The formula of calculation is ∑d*p / ∑d , where "d" is task duration in days,
|
||||
* "p" is the task progress and "∑" stands for a sum of all loaded tasks
|
||||
*/
|
||||
function getSummaryProgress(id) {
|
||||
const [totalProgress, totalDuration] = collectProgressFromKids(id);
|
||||
const res = totalProgress / totalDuration;
|
||||
return isNaN(res) ? 0 : Math.round(res);
|
||||
}
|
||||
|
||||
function collectProgressFromKids(id) {
|
||||
let totalProgress = 0,
|
||||
totalDuration = 0;
|
||||
const kids = api.getTask(id).data;
|
||||
|
||||
kids?.forEach((kid) => {
|
||||
let duration = 0;
|
||||
if (kid.type !== 'milestone' && kid.type !== 'summary') {
|
||||
duration = kid.duration || dayDiff(kid.end, kid.start);
|
||||
totalDuration += duration;
|
||||
totalProgress += duration * kid.progress;
|
||||
}
|
||||
|
||||
const [p, d] = collectProgressFromKids(kid.id);
|
||||
totalProgress += p;
|
||||
totalDuration += d;
|
||||
});
|
||||
return [totalProgress, totalDuration];
|
||||
}
|
||||
|
||||
function recalcSummaryProgress(id, self) {
|
||||
const { tasks } = api.getState();
|
||||
const task = api.getTask(id);
|
||||
|
||||
if (task.type != 'milestone') {
|
||||
const summary =
|
||||
self && task.type === 'summary' ? id : tasks.getSummaryId(id);
|
||||
|
||||
if (summary) {
|
||||
const progress = getSummaryProgress(summary);
|
||||
api.exec('update-task', {
|
||||
id: summary,
|
||||
task: { progress },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const recalcRef = useRef(null);
|
||||
recalcRef.current = recalcSummaryProgress;
|
||||
|
||||
const init = useCallback((api) => {
|
||||
setApi(api);
|
||||
// auto progress calculations
|
||||
api.on('add-task', ({ id }) => {
|
||||
recalcRef.current(id);
|
||||
});
|
||||
api.on('update-task', ({ id }) => {
|
||||
recalcRef.current(id);
|
||||
});
|
||||
|
||||
api.on('delete-task', ({ source }) => {
|
||||
recalcRef.current(source, true);
|
||||
});
|
||||
api.on('copy-task', ({ id }) => {
|
||||
recalcRef.current(id);
|
||||
});
|
||||
api.on('move-task', ({ id, source, inProgress }) => {
|
||||
if (inProgress) return;
|
||||
|
||||
if (api.getTask(id).parent != source) recalcRef.current(source, true);
|
||||
recalcRef.current(id);
|
||||
});
|
||||
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="wx-OeNgRLo4 wrapper">
|
||||
<ContextMenu api={api}>
|
||||
<div className="wx-OeNgRLo4 gt-cell">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
init={init}
|
||||
tasks={tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
cellWidth={30}
|
||||
/>
|
||||
</div>
|
||||
</ContextMenu>
|
||||
{api && <ConfiguredEditor api={api} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ConfiguredEditor({ api }) {
|
||||
const [editorItems, setEditorItems] = useState(getEditorItems());
|
||||
|
||||
// disabling progress slider for summary tasks
|
||||
api.on('show-editor', ({ id }) => {
|
||||
if (id) {
|
||||
const type = api.getTask(id).type;
|
||||
setEditorItems(
|
||||
getEditorItems().map((ed) => ({
|
||||
...ed,
|
||||
...(ed.key == 'progress' && {
|
||||
config: { disabled: type === 'summary' },
|
||||
}),
|
||||
})),
|
||||
);
|
||||
}
|
||||
});
|
||||
return <Editor api={api} items={editorItems} />;
|
||||
}
|
||||
|
||||
export default SummariesProgress;
|
||||
35
demos/cases/ProUndo.css
Normal file
35
demos/cases/ProUndo.css
Normal file
@@ -0,0 +1,35 @@
|
||||
.rows.wx-D71fWZ9y {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.buttons.wx-D71fWZ9y {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.gtcell.wx-D71fWZ9y {
|
||||
position: relative;
|
||||
height: calc(100% - 48px);
|
||||
border-top: var(--wx-gantt-border);
|
||||
}
|
||||
.button.wx-D71fWZ9y {
|
||||
position: relative;
|
||||
}
|
||||
.button.wx-D71fWZ9y span {
|
||||
background-color: var(--wx-color-danger);
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 50px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
padding: 3px 0;
|
||||
color: #fff;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
}
|
||||
79
demos/cases/ProUndo.jsx
Normal file
79
demos/cases/ProUndo.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { Button } from '@svar-ui/react-core';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, ContextMenu } from '../../src';
|
||||
import './ProUndo.css';
|
||||
|
||||
export default function ProUndo({ skinSettings }) {
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const { tasks, links, scales } = useMemo(() => getData(), []);
|
||||
function init(ganttApi) {
|
||||
setApi(ganttApi);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rows wx-D71fWZ9y">
|
||||
{api && <History api={api} />}
|
||||
<div className="gtcell wx-D71fWZ9y">
|
||||
<ContextMenu api={api}>
|
||||
<Gantt
|
||||
init={init}
|
||||
{...skinSettings}
|
||||
tasks={tasks}
|
||||
links={links}
|
||||
scales={scales}
|
||||
undo
|
||||
/>
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function History({ api }) {
|
||||
|
||||
const [history, setHistory] = useState(null);
|
||||
function handleUndo() {
|
||||
api.exec('undo');
|
||||
}
|
||||
|
||||
function handleRedo() {
|
||||
api.exec('redo');
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
api.getReactiveState().history.subscribe((v) => (setHistory(v)));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="buttons wx-D71fWZ9y">
|
||||
<div className="button wx-D71fWZ9y">
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleUndo}
|
||||
disabled={history && !history.undo}
|
||||
>
|
||||
Undo
|
||||
</Button>
|
||||
{history && history.undo > 0 && (
|
||||
<span>{history.undo}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="button wx-D71fWZ9y">
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleRedo}
|
||||
disabled={history && !history.redo}
|
||||
>
|
||||
Redo
|
||||
</Button>
|
||||
{history && history.redo > 0 && (
|
||||
<span>{history.redo}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
4
demos/cases/ProUndoToolbar.css
Normal file
4
demos/cases/ProUndoToolbar.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.gtcell.wx-D71fWZAy {
|
||||
height: calc(100% - 50px);
|
||||
border-top: var(--wx-gantt-border);
|
||||
}
|
||||
27
demos/cases/ProUndoToolbar.jsx
Normal file
27
demos/cases/ProUndoToolbar.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Toolbar, Editor } from '../../src/';
|
||||
import './ProUndoToolbar.css';
|
||||
|
||||
export default function ProUndoToolbar({ skinSettings }) {
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar api={api} />
|
||||
<div className="gtcell wx-D71fWZAy">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
init={setApi}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
undo
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
25
demos/cases/ProUnscheduledTasks.jsx
Normal file
25
demos/cases/ProUnscheduledTasks.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor } from '../../src';
|
||||
|
||||
function ProUnscheduledTasks({ skinSettings }) {
|
||||
const [api, setApi] = useState(null);
|
||||
|
||||
const data = getData('day', { unscheduledTasks: true });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
unscheduledTasks={true}
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProUnscheduledTasks;
|
||||
89
demos/cases/ProUnscheduledTasksAndBaselines.jsx
Normal file
89
demos/cases/ProUnscheduledTasksAndBaselines.jsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Gantt, Editor, defaultEditorItems, defaultColumns } from '../../src';
|
||||
import { format } from 'date-fns';
|
||||
import { getBaselinesData } from '../data';
|
||||
|
||||
function ProUnscheduledTasksAndBaselines({ skinSettings }) {
|
||||
const [api, setApi] = useState(null);
|
||||
|
||||
const data = useMemo(() => {
|
||||
const rawData = getBaselinesData();
|
||||
const cloned = { ...rawData };
|
||||
|
||||
cloned.tasks = cloned.tasks.map((task) => {
|
||||
if (task.id === 10 || task.id === 12) {
|
||||
task = { ...task, unscheduled: true };
|
||||
}
|
||||
|
||||
if (task.id === 10) {
|
||||
task = {
|
||||
...task,
|
||||
start: new Date(2024, 3, 5),
|
||||
end: new Date(2024, 3, 7),
|
||||
};
|
||||
}
|
||||
if (task.id === 11) {
|
||||
task = {
|
||||
...task,
|
||||
start: new Date(2024, 3, 2),
|
||||
end: new Date(2024, 3, 5),
|
||||
};
|
||||
}
|
||||
return task;
|
||||
});
|
||||
|
||||
return cloned;
|
||||
}, []);
|
||||
|
||||
const items = useMemo(() => {
|
||||
return defaultEditorItems.flatMap((item) =>
|
||||
item.key === 'links'
|
||||
? [
|
||||
...[
|
||||
{
|
||||
key: 'base_start',
|
||||
comp: 'date',
|
||||
label: 'Baseline start',
|
||||
},
|
||||
{
|
||||
key: 'base_end',
|
||||
comp: 'date',
|
||||
label: 'Baseline end',
|
||||
},
|
||||
],
|
||||
item,
|
||||
]
|
||||
: item,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return defaultColumns.map((col) => {
|
||||
if (col.id == 'start')
|
||||
col.template = (value, row) =>
|
||||
row.unscheduled ? '-' : format(value, 'dd-MM-yyyy');
|
||||
if (col.id == 'duration')
|
||||
col.template = (value, row) => (row.unscheduled ? '-' : value);
|
||||
return col;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
columns={columns}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
unscheduledTasks={true}
|
||||
baselines={true}
|
||||
cellHeight={45}
|
||||
/>
|
||||
{api && <Editor api={api} items={items} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProUnscheduledTasksAndBaselines;
|
||||
@@ -76,13 +76,13 @@
|
||||
}
|
||||
|
||||
.wx-demos.sidebar.active {
|
||||
width: 300px;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.wx-demos.sidebar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
width: 320px;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #ebebeb;
|
||||
@@ -133,7 +133,7 @@
|
||||
.wx-demos.box-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.wx-demos.hint {
|
||||
@@ -210,14 +210,14 @@
|
||||
.wx-demos.demo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 37px;
|
||||
font-weight: 400;
|
||||
padding-left: 16px;
|
||||
padding: 8px 12px 8px 16px;
|
||||
border-left: 4px solid transparent;
|
||||
color: #595b66;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.wx-demos.demo.active {
|
||||
@@ -226,7 +226,26 @@
|
||||
|
||||
.wx-demos.demo.active,
|
||||
.wx-demos.demo:hover {
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
color: #42454d;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.wx-demos.demo.active {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.wx-demos.demo .pro {
|
||||
color: #087a9f;
|
||||
border: 1px solid #087a9f;
|
||||
border-radius: 4px;
|
||||
padding: 0px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-left: auto;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.wx-theme {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { HashRouter, NavLink, useNavigate } from 'react-router-dom';
|
||||
import { Willow } from '@svar-ui/react-core';
|
||||
|
||||
import Router from './Router';
|
||||
import { links } from '../routes';
|
||||
@@ -18,38 +19,28 @@ function DemoExplorerContent({
|
||||
const [skin, setSkin] = useState(skins[0].id);
|
||||
const [title, setTitle] = useState('');
|
||||
const [githubLink, setGithubLink] = useState('');
|
||||
const [show, setShow] = useState(false);
|
||||
const [show, setShow] = useState(true);
|
||||
|
||||
const baseLink =
|
||||
'https://github.com/svar-widgets/react-' +
|
||||
productTag +
|
||||
'/tree/main/demos/cases/';
|
||||
|
||||
useEffect(() => {
|
||||
document.body.className = `wx-willow-theme`;
|
||||
const handleRouteChange = useCallback((path) => {
|
||||
const parts = path.split('/');
|
||||
const page = parts[1];
|
||||
const newSkin = parts[2];
|
||||
setSkin(newSkin);
|
||||
|
||||
const targetPage = `/${page}/:skin`;
|
||||
const matched = links.find((a) => a[0] === targetPage);
|
||||
if (matched) {
|
||||
setTitle(matched[1]);
|
||||
const name = matched[3] || matched[1];
|
||||
setGithubLink(`${baseLink}${name}.jsx`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleRouteChange = useCallback(
|
||||
(path) => {
|
||||
const parts = path.split('/');
|
||||
const page = parts[1];
|
||||
const newSkin = parts[2];
|
||||
|
||||
if (newSkin && newSkin !== skin) {
|
||||
setSkin(newSkin);
|
||||
}
|
||||
|
||||
const targetPage = `/${page}/:skin`;
|
||||
const matched = links.find((a) => a[0] === targetPage);
|
||||
if (matched) {
|
||||
setTitle(matched[1]);
|
||||
const name = matched[3] || matched[1];
|
||||
setGithubLink(`${baseLink}${name}.jsx`);
|
||||
}
|
||||
},
|
||||
[skin],
|
||||
);
|
||||
|
||||
const handleSkinChange = ({ value }) => {
|
||||
setSkin(value);
|
||||
const currentPath = window.location.hash.slice(1);
|
||||
@@ -63,8 +54,10 @@ function DemoExplorerContent({
|
||||
setShow(!show);
|
||||
};
|
||||
|
||||
const SkinComponent = skins.find((s) => s.id === skin).Component;
|
||||
|
||||
return (
|
||||
<div className={`wx-demos layout ${show ? 'active' : ''}`}>
|
||||
<div className={`wx-demos wx-willow-theme layout ${show ? 'active' : ''}`}>
|
||||
<div
|
||||
className={`wx-demos sidebar ${show ? 'active' : ''}`}
|
||||
role="tabpanel"
|
||||
@@ -81,7 +74,7 @@ function DemoExplorerContent({
|
||||
</a>
|
||||
<div className="wx-demos separator"></div>
|
||||
<a
|
||||
href={`https://svar.dev/react/${productTag}/`}
|
||||
href={`https://svar.dev/react/gantt/`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -107,6 +100,7 @@ function DemoExplorerContent({
|
||||
}
|
||||
>
|
||||
{data[1]}
|
||||
{data[4] && data[4].pro && <span className="pro">PRO</span>}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
@@ -159,7 +153,9 @@ function DemoExplorerContent({
|
||||
data-wx-portal-root="true"
|
||||
>
|
||||
<Globals>
|
||||
<Router skin={skin} onRouteChange={handleRouteChange} />
|
||||
<SkinComponent>
|
||||
<Router skin={skin} onRouteChange={handleRouteChange} />
|
||||
</SkinComponent>
|
||||
</Globals>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,12 +165,9 @@ function DemoExplorerContent({
|
||||
}
|
||||
|
||||
export default function DemoExplorer(props) {
|
||||
const skins = props.skins;
|
||||
return (
|
||||
<>
|
||||
{skins.map((skin) => (
|
||||
<skin.Component key={skin.id} />
|
||||
))}
|
||||
<Willow />
|
||||
<HashRouter>
|
||||
<DemoExplorerContent {...props} />
|
||||
</HashRouter>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import {
|
||||
Routes,
|
||||
Route,
|
||||
@@ -12,7 +12,9 @@ export default function Router({ onRouteChange }) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const lastRef = useRef(location.pathname);
|
||||
useEffect(() => {
|
||||
if (lastRef.current === location.pathname) return;
|
||||
if (location.pathname === '/') {
|
||||
navigate('/base/willow', { replace: true });
|
||||
} else {
|
||||
|
||||
@@ -65,68 +65,50 @@ export default function Form(props) {
|
||||
</div>
|
||||
<div className="wx-QkE5vh0y body">
|
||||
<Field label="Name">
|
||||
{({ id }) => (
|
||||
<Text
|
||||
id={id}
|
||||
focus={true}
|
||||
value={task.text}
|
||||
onChange={(ev) => handleChange(ev, 'text')}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
focus={true}
|
||||
value={task.text}
|
||||
onChange={(ev) => handleChange(ev, 'text')}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Description">
|
||||
{({ id }) => (
|
||||
<TextArea
|
||||
id={id}
|
||||
value={task.details}
|
||||
onChange={(ev) => handleChange(ev, 'details')}
|
||||
/>
|
||||
)}
|
||||
<TextArea
|
||||
value={task.details}
|
||||
onChange={(ev) => handleChange(ev, 'details')}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{taskTypes.length > 1 ? (
|
||||
<Field label="Type">
|
||||
{({ id }) => (
|
||||
<Select
|
||||
id={id}
|
||||
value={task.type}
|
||||
options={taskTypes}
|
||||
onChange={(ev) => handleChange(ev, 'type')}
|
||||
/>
|
||||
)}
|
||||
<Select
|
||||
value={task.type}
|
||||
options={taskTypes}
|
||||
onChange={(ev) => handleChange(ev, 'type')}
|
||||
/>
|
||||
</Field>
|
||||
) : null}
|
||||
|
||||
<Field label="Start date">
|
||||
{({ id }) => (
|
||||
<DatePicker
|
||||
id={id}
|
||||
value={task.start}
|
||||
onChange={(ev) => handleChange(ev, 'start')}
|
||||
/>
|
||||
)}
|
||||
<DatePicker
|
||||
value={task.start}
|
||||
onChange={(ev) => handleChange(ev, 'start')}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{task.type !== 'milestone' ? (
|
||||
<>
|
||||
<Field label="End date">
|
||||
{({ id }) => (
|
||||
<DatePicker
|
||||
id={id}
|
||||
value={task.end}
|
||||
onChange={(ev) => handleChange(ev, 'end')}
|
||||
/>
|
||||
)}
|
||||
<DatePicker
|
||||
value={task.end}
|
||||
onChange={(ev) => handleChange(ev, 'end')}
|
||||
/>
|
||||
</Field>
|
||||
<Field label={`Progress: ${task.progress}%`}>
|
||||
{({ id }) => (
|
||||
<Slider
|
||||
id={id}
|
||||
value={task.progress}
|
||||
onChange={(ev) => handleChange(ev, 'progress')}
|
||||
/>
|
||||
)}
|
||||
<Slider
|
||||
value={task.progress}
|
||||
onChange={(ev) => handleChange(ev, 'progress')}
|
||||
/>
|
||||
</Field>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
767
demos/data.js
767
demos/data.js
File diff suppressed because it is too large
Load Diff
@@ -30,14 +30,13 @@ import GanttTaskTypes from './cases/GanttTaskTypes.jsx';
|
||||
import ChartCellBorders from './cases/ChartBorders.jsx';
|
||||
import ContextMenu from './cases/ContextMenu.jsx';
|
||||
import ContextMenuHandler from './cases/ContextMenuHandler.jsx';
|
||||
// import DropDownMenu from "./cases/DropDownMenu.jsx";
|
||||
//import DropDownMenu from "./cases/DropDownMenu.jsx";
|
||||
import ContextMenuOptions from './cases/ContextMenuOptions.jsx';
|
||||
import GanttHolidays from './cases/GanttHolidays.jsx';
|
||||
import GanttSort from './cases/GanttSort.jsx';
|
||||
import GanttCustomSort from './cases/GanttCustomSort.jsx';
|
||||
import GanttSummariesProgress from './cases/GanttSummariesProgress.jsx';
|
||||
import GanttSummariesNoDrag from './cases/GanttSummariesNoDrag.jsx';
|
||||
import GanttSummariesConvert from './cases/GanttSummariesConvert.jsx';
|
||||
import SummariesProgress from './cases/ProSummariesProgress.jsx';
|
||||
import SummariesConvert from './cases/ProSummariesConvert.jsx';
|
||||
import GanttEditor from './cases/GanttEditor.jsx';
|
||||
import GanttEditorConfig from './cases/GanttEditorConfig.jsx';
|
||||
import GanttEditorCustomControls from './cases/GanttEditorCustomControls.jsx';
|
||||
@@ -79,28 +78,11 @@ export const links = [
|
||||
|
||||
['/holidays/:skin', 'Holidays', GanttHolidays, 'GanttHolidays'],
|
||||
|
||||
|
||||
['/templates/:skin', 'Custom text', GanttText, 'GanttText'],
|
||||
['/tooltips/:skin', 'Tooltips', GanttTooltips, 'GanttTooltips'],
|
||||
|
||||
['/task-types/:skin', 'Task types', GanttTaskTypes, 'GanttTaskTypes'],
|
||||
[
|
||||
'/summary-progress/:skin',
|
||||
'Summary tasks with auto progress',
|
||||
GanttSummariesProgress,
|
||||
'GanttSummariesProgress',
|
||||
],
|
||||
[
|
||||
'/summary-no-drag/:skin',
|
||||
'No drag for summary tasks',
|
||||
GanttSummariesNoDrag,
|
||||
'GanttSummariesNoDrag',
|
||||
],
|
||||
[
|
||||
'/summary-convert/:skin',
|
||||
'Auto convert to summary tasks',
|
||||
GanttSummariesConvert,
|
||||
'GanttSummariesConvert',
|
||||
],
|
||||
|
||||
['/zoom/:skin', 'Zoom', GanttZoom, 'GanttZoom'],
|
||||
['/custom-zoom/:skin', 'Custom Zoom', GanttCustomZoom, 'GanttCustomZoom'],
|
||||
@@ -176,17 +158,9 @@ export const links = [
|
||||
HeaderMenu,
|
||||
'GridHeaderMenu',
|
||||
],
|
||||
['/custom-edit-form/:skin', 'Custom editor', GanttForm, 'GanttForm'],
|
||||
['/locale/:skin', 'Locales', GanttLocale, 'GanttLocale'],
|
||||
['/fullscreen/:skin', 'Fullscreen', GanttFullscreen, 'GanttFullscreen'],
|
||||
['/readonly/:skin', 'Readonly mode', GanttReadOnly, 'GanttReadOnly'],
|
||||
|
||||
[
|
||||
'/prevent-actions/:skin',
|
||||
'Preventing actions',
|
||||
GanttPreventActions,
|
||||
'GanttPreventActions',
|
||||
],
|
||||
[
|
||||
'/gantt-multiple/:skin',
|
||||
'Many Gantts per page',
|
||||
@@ -194,7 +168,12 @@ export const links = [
|
||||
'GanttMultiple',
|
||||
],
|
||||
['/performance/:skin', 'Performance', GanttPerformance, 'GanttPerformance'],
|
||||
|
||||
[
|
||||
'/prevent-actions/:skin',
|
||||
'Preventing UI actions',
|
||||
GanttPreventActions,
|
||||
'GanttPreventActions',
|
||||
],
|
||||
['/sorting/:skin', 'Custom sorting', GanttSort, 'GanttSort'],
|
||||
['/sorting-api/:skin', 'Sort by API', GanttCustomSort, 'GanttCustomSort'],
|
||||
|
||||
@@ -248,4 +227,5 @@ export const links = [
|
||||
GanttEditorValidation,
|
||||
'GanttEditorValidation',
|
||||
],
|
||||
['/custom-edit-form/:skin', 'Custom edit form', GanttForm, 'GanttForm'],
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user