v2.3.1
This commit is contained in:
3
demos/assets/icons/GitHubLogo.svg
Normal file
3
demos/assets/icons/GitHubLogo.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.99935 0.666748C7.905 0.666748 6.82137 0.882296 5.81032 1.30109C4.79927 1.71987 3.88061 2.3337 3.10679 3.10752C1.54399 4.67033 0.666016 6.78994 0.666016 9.00008C0.666016 12.6834 3.05768 15.8084 6.36602 16.9167C6.78268 16.9834 6.91602 16.7251 6.91602 16.5001C6.91602 16.3084 6.91602 15.7834 6.91602 15.0917C4.60768 15.5917 4.11602 13.9751 4.11602 13.9751C3.73268 13.0084 3.19102 12.7501 3.19102 12.7501C2.43268 12.2334 3.24935 12.2501 3.24935 12.2501C4.08268 12.3084 4.52435 13.1084 4.52435 13.1084C5.24935 14.3751 6.47435 14.0001 6.94935 13.8001C7.02435 13.2584 7.24102 12.8917 7.47435 12.6834C5.62435 12.4751 3.68268 11.7584 3.68268 8.58342C3.68268 7.65841 3.99935 6.91675 4.54102 6.32508C4.45768 6.11675 4.16602 5.25008 4.62435 4.12508C4.62435 4.12508 5.32435 3.90008 6.91602 4.97508C7.57435 4.79175 8.29102 4.70008 8.99935 4.70008C9.70768 4.70008 10.4243 4.79175 11.0827 4.97508C12.6743 3.90008 13.3743 4.12508 13.3743 4.12508C13.8327 5.25008 13.541 6.11675 13.4577 6.32508C13.9993 6.91675 14.316 7.65841 14.316 8.58342C14.316 11.7667 12.366 12.4667 10.5077 12.6751C10.8077 12.9334 11.0827 13.4417 11.0827 14.2167C11.0827 15.3334 11.0827 16.2334 11.0827 16.5001C11.0827 16.7251 11.216 16.9917 11.641 16.9167C14.9493 15.8001 17.3327 12.6834 17.3327 9.00008C17.3327 7.90573 17.1171 6.8221 16.6983 5.81105C16.2796 4.80001 15.6657 3.88135 14.8919 3.10752C14.1181 2.3337 13.1994 1.71987 12.1884 1.30109C11.1773 0.882296 10.0937 0.666748 8.99935 0.666748Z" fill="#42454D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
9
demos/assets/icons/Logo.svg
Normal file
9
demos/assets/icons/Logo.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="26" height="18" viewBox="0 0 26 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.06631 1.11863e-06L10.5114 2.3209e-06L5.44169 9.00172L-1.9482e-06 9.00172L5.06631 1.11863e-06Z" fill="#087A9F"/>
|
||||
<path d="M0 9.00122L5.44169 9.00122L7.78709 13.1683L5.06631 17.9994L0 9.00122Z" fill="#087A9F"/>
|
||||
<path d="M0 9.00122L5.44169 9.00122L7.78709 13.1683L5.06631 17.9994L0 9.00122Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.06738 18L15.2034 2.2291e-06L17.9242 4.83461L10.5125 18L5.06738 18Z" fill="#087A9F"/>
|
||||
<path d="M20.6439 1.19674e-06L25.7136 9.00173L20.2684 9.00172L15.2021 0L20.6439 1.19674e-06Z" fill="#087A9F"/>
|
||||
<path d="M20.6439 1.19674e-06L25.7136 9.00173L20.2684 9.00172L15.2021 0L20.6439 1.19674e-06Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.6438 17.9994L15.2021 17.9994L20.2684 9.00122L25.7135 9.00122L20.6438 17.9994Z" fill="#087A9F"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 883 B |
2
demos/assets/icons/index.js
Normal file
2
demos/assets/icons/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as GitHubLogoIcon } from './GitHubLogo.svg';
|
||||
export { default as LogoIcon } from './Logo.svg';
|
||||
16
demos/cases/BasicInit.jsx
Normal file
16
demos/cases/BasicInit.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
|
||||
export default function BasicInit({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
);
|
||||
}
|
||||
27
demos/cases/ChartBorders.css
Normal file
27
demos/cases/ChartBorders.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.rows.wx-gnwCbZe9 {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--wx-background);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-gnwCbZe9 {
|
||||
padding: 10px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.gtcell.wx-gnwCbZe9 {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
border-top: var(--wx-gantt-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
.label.wx-gnwCbZe9 {
|
||||
padding-right: 20px;
|
||||
font-size: var(--wx-font-size);
|
||||
font-weight: var(--wx-label-font-weight);
|
||||
}
|
||||
42
demos/cases/ChartBorders.jsx
Normal file
42
demos/cases/ChartBorders.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import { RadioButtonGroup } from '@svar-ui/react-core';
|
||||
import './ChartBorders.css';
|
||||
|
||||
export default function ChartBorders({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const [cellBorders, setCellBorders] = useState('full');
|
||||
|
||||
const options = useMemo(
|
||||
() => [
|
||||
{ id: 'full', label: 'Full' },
|
||||
{ id: 'column', label: 'Column' },
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="wx-gnwCbZe9 rows">
|
||||
<div className="wx-gnwCbZe9 bar">
|
||||
<div className="wx-gnwCbZe9 label">Chart cell borders</div>
|
||||
<RadioButtonGroup
|
||||
options={options}
|
||||
value={cellBorders}
|
||||
onChange={({ value }) => setCellBorders(value)}
|
||||
type="inline"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="wx-gnwCbZe9 gtcell">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
cellBorders={cellBorders}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
26
demos/cases/ContextMenu.jsx
Normal file
26
demos/cases/ContextMenu.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu as ContextMenuComponent, Editor } from '../../src/';
|
||||
|
||||
function ContextMenu(props) {
|
||||
const { skinSettings } = props;
|
||||
const [api, setApi] = useState(null);
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenuComponent api={api}>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
</ContextMenuComponent>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContextMenu;
|
||||
41
demos/cases/ContextMenuHandler.jsx
Normal file
41
demos/cases/ContextMenuHandler.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu, Editor } from '../../src/';
|
||||
|
||||
function ContextMenuHandler(props) {
|
||||
const { skinSettings } = props;
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const resolver = useCallback((id) => {
|
||||
return id > 2;
|
||||
}, []);
|
||||
|
||||
const filter = useCallback((option, task) => {
|
||||
const type = task.type;
|
||||
if (option.id) {
|
||||
const ids = option.id.toString().split(':');
|
||||
if (type == 'milestone' && ids[0] == 'add-task') return ids[1] != 'child';
|
||||
}
|
||||
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu api={api} resolver={resolver} filter={filter}>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContextMenuHandler;
|
||||
53
demos/cases/ContextMenuOptions.jsx
Normal file
53
demos/cases/ContextMenuOptions.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useState, useContext, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu, Editor, defaultMenuOptions } from '../../src/';
|
||||
import { context } from '@svar-ui/react-core';
|
||||
|
||||
export default function ContextMenuOptions({ skinSettings }) {
|
||||
const helpers = useContext(context.helpers);
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
function actionHandler() {
|
||||
helpers.showNotice({ text: "'My action' clicked" });
|
||||
}
|
||||
|
||||
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.push({
|
||||
id: 'my-action',
|
||||
text: 'My action',
|
||||
icon: 'wxi-empty',
|
||||
handler: actionHandler,
|
||||
});
|
||||
return arr;
|
||||
});
|
||||
|
||||
function onClick({ context: ctx, action }) {
|
||||
if (!action.handler)
|
||||
helpers.showNotice({
|
||||
text: `'${action.id}' clicked for the '${ctx.id}' task`,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu api={api} options={options} onClick={onClick}>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
17
demos/cases/DropDownMenu.css
Normal file
17
demos/cases/DropDownMenu.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.rows.wx-T6JFUSGo {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-T6JFUSGo {
|
||||
padding: 12px;
|
||||
}
|
||||
.gtcell.wx-T6JFUSGo {
|
||||
position: relative;
|
||||
height: calc(100% - 56px);
|
||||
border-top: var(--wx-gantt-border);
|
||||
}
|
||||
52
demos/cases/DropDownMenu.jsx
Normal file
52
demos/cases/DropDownMenu.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useRef, useState, useCallback, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu, Editor } from '../../src/';
|
||||
import { Button } from '@svar-ui/react-core';
|
||||
import './DropDownMenu.css';
|
||||
|
||||
export default function DropDownMenu({ skinSettings }) {
|
||||
const apiRef = useRef(null);
|
||||
const [api, setApi] = useState(null);
|
||||
const menu = useRef(null);
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const resolver = useCallback(() => {
|
||||
const inst = apiRef.current;
|
||||
const selected = inst ? inst.getReactiveState().selected : null;
|
||||
const id =
|
||||
selected && selected.length ? selected[selected.length - 1] : null;
|
||||
return id ? inst.getTask(id) : null;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu api={api} resolver={resolver} at="right" ref={menu} />
|
||||
|
||||
<div className="wx-T6JFUSGo rows">
|
||||
<div className="wx-T6JFUSGo bar">
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={(ev) => menu.current && menu.current.show(ev)}
|
||||
>
|
||||
Task action
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="wx-T6JFUSGo gtcell">
|
||||
<Gantt
|
||||
ref={(inst) => {
|
||||
apiRef.current = inst;
|
||||
setApi(inst);
|
||||
}}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
<Editor api={api} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
65
demos/cases/GanttBackend.jsx
Normal file
65
demos/cases/GanttBackend.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Gantt, ContextMenu, Editor } from '../../src';
|
||||
|
||||
const parseDates = (data) => {
|
||||
data.forEach((item) => {
|
||||
item.start = new Date(item.start);
|
||||
if (item.end) item.end = new Date(item.end);
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
export default function GanttBackend() {
|
||||
const server = 'https://master--svar-gantt-go--dev.webix.io';
|
||||
|
||||
const [api, setApi] = useState();
|
||||
const [tasks, setTasks] = useState([]);
|
||||
const [links, setLinks] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
fetch(server + '/tasks')
|
||||
.then((res) => res.json())
|
||||
.then((arr) => parseDates(arr)),
|
||||
fetch(server + '/links').then((res) => res.json()),
|
||||
]).then(([t, l]) => {
|
||||
setTasks(t);
|
||||
setLinks(l);
|
||||
});
|
||||
}, []);
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu api={api}>
|
||||
<Gantt init={init} tasks={tasks} links={links} />
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
53
demos/cases/GanttBatchProvider.jsx
Normal file
53
demos/cases/GanttBatchProvider.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { RestDataProvider } from '@svar-ui/gantt-data-provider';
|
||||
import { Gantt, ContextMenu, Editor } from '../../src';
|
||||
|
||||
export default function GanttBatchProvider() {
|
||||
const restProvider = useMemo(
|
||||
() =>
|
||||
new RestDataProvider('https://master--svar-gantt-go--dev.webix.io', {
|
||||
batchURL: 'batch',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
const [tasks, setTasks] = useState();
|
||||
const [links, setLinks] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
restProvider.getData().then(({ tasks: t, links: l }) => {
|
||||
setTasks(t);
|
||||
setLinks(l);
|
||||
});
|
||||
}, [restProvider]);
|
||||
|
||||
const init = useCallback(
|
||||
(api) => {
|
||||
setApi(api);
|
||||
api.setNext(restProvider);
|
||||
|
||||
api.on('request-data', (ev) => {
|
||||
restProvider.getData(ev.id).then(({ tasks, links }) => {
|
||||
api.exec('provide-data', {
|
||||
id: ev.id,
|
||||
data: {
|
||||
tasks,
|
||||
links,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
[restProvider],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu api={api}>
|
||||
<Gantt init={init} tasks={tasks} links={links} />
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
31
demos/cases/GanttCustomSort.css
Normal file
31
demos/cases/GanttCustomSort.css
Normal file
@@ -0,0 +1,31 @@
|
||||
.rows.wx-q0L4gF4x {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-q0L4gF4x {
|
||||
padding: 10px 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.gtcell.wx-q0L4gF4x {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
border-top: var(--wx-gantt-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
.label.wx-q0L4gF4x {
|
||||
padding-right: 10px;
|
||||
font-size: var(--wx-font-size);
|
||||
font-weight: var(--wx-label-font-weight);
|
||||
}
|
||||
i.wx-q0L4gF4x {
|
||||
position: relative;
|
||||
right: -5px;
|
||||
}
|
||||
70
demos/cases/GanttCustomSort.jsx
Normal file
70
demos/cases/GanttCustomSort.jsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import { Button } from '@svar-ui/react-core';
|
||||
import './GanttCustomSort.css';
|
||||
|
||||
export default function GanttCustomSort({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [gApi, setGApi] = useState(null);
|
||||
const [sortConfig, setSortConfig] = useState({});
|
||||
const getIcons = useCallback(() => {
|
||||
const obj = { text: '', start: '', duration: '' };
|
||||
const { key, order } = sortConfig;
|
||||
if (key) obj[key] = `wxi-arrow-${order == 'asc' ? 'up' : 'down'}`;
|
||||
return obj;
|
||||
}, [sortConfig]);
|
||||
const [icons, setIcons] = useState(() => getIcons());
|
||||
|
||||
const sort = useCallback(
|
||||
(id) => {
|
||||
const { key, order } = sortConfig;
|
||||
let newOrder = !key ? 'desc' : 'asc';
|
||||
|
||||
if (key === id) newOrder = order === 'asc' ? 'desc' : 'asc';
|
||||
|
||||
if (gApi) gApi.exec('sort-tasks', { key: id, order: newOrder });
|
||||
},
|
||||
[gApi, sortConfig],
|
||||
);
|
||||
|
||||
const init = useCallback((api) => {
|
||||
api.on('sort-tasks', (config) => {
|
||||
setSortConfig(config);
|
||||
// Compute icons based on the incoming config to mirror Svelte's sequential state updates
|
||||
const obj = { text: '', start: '', duration: '' };
|
||||
const { key, order } = config;
|
||||
if (key) obj[key] = `wxi-arrow-${order == 'asc' ? 'up' : 'down'}`;
|
||||
setIcons(obj);
|
||||
});
|
||||
setGApi(api);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="wx-q0L4gF4x rows">
|
||||
<div className="wx-q0L4gF4x bar">
|
||||
<div className="wx-q0L4gF4x label">Sort by</div>
|
||||
<Button onClick={() => sort('text')}>
|
||||
Task Name <i className={'wx-q0L4gF4x ' + icons.text}></i>
|
||||
</Button>
|
||||
<Button onClick={() => sort('start')}>
|
||||
Start Date <i className={'wx-q0L4gF4x ' + icons.start}></i>
|
||||
</Button>
|
||||
<Button onClick={() => sort('duration')}>
|
||||
Duration <i className={'wx-q0L4gF4x ' + icons.duration}></i>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="wx-q0L4gF4x gtcell">
|
||||
<Gantt
|
||||
init={init}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
demos/cases/GanttCustomZoom.css
Normal file
12
demos/cases/GanttCustomZoom.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.demo.wx-6q6Giv9n {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gtcell.wx-6q6Giv9n {
|
||||
overflow: hidden;
|
||||
border: var(--wx-gantt-border);
|
||||
height: calc(100% - 32px);
|
||||
}
|
||||
26
demos/cases/GanttCustomZoom.jsx
Normal file
26
demos/cases/GanttCustomZoom.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData, zoomConfig } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import './GanttCustomZoom.css';
|
||||
|
||||
const GanttCustomZoom = ({ skinSettings }) => {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<div className="wx-6q6Giv9n demo">
|
||||
<h4>
|
||||
Point over Gantt chart, then hold Ctrl and use mouse wheel to zoom
|
||||
</h4>
|
||||
<div className="wx-6q6Giv9n gtcell">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
zoom={zoomConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GanttCustomZoom;
|
||||
28
demos/cases/GanttDurationUnitChanges.css
Normal file
28
demos/cases/GanttDurationUnitChanges.css
Normal file
@@ -0,0 +1,28 @@
|
||||
.rows.wx-d4Cw5r6y {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--wx-background);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-d4Cw5r6y {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
}
|
||||
.bar.wx-d4Cw5r6y div {
|
||||
display: flex;
|
||||
}
|
||||
.gtcell.wx-d4Cw5r6y {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
border-top: var(--wx-gantt-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
.label.wx-d4Cw5r6y {
|
||||
padding-right: 20px;
|
||||
font-size: var(--wx-font-size);
|
||||
font-weight: var(--wx-label-font-weight);
|
||||
}
|
||||
84
demos/cases/GanttDurationUnitChanges.jsx
Normal file
84
demos/cases/GanttDurationUnitChanges.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu, Editor, defaultEditorItems } from '../../src';
|
||||
import { RadioButtonGroup } from '@svar-ui/react-core';
|
||||
import './GanttDurationUnitChanges.css';
|
||||
|
||||
export default function GanttDurationUnitChanges({ skinSettings }) {
|
||||
const initialData = getData();
|
||||
const [tasks, setTasks] = useState(initialData.tasks);
|
||||
const [links] = useState(initialData.links);
|
||||
|
||||
const scalesMap = useMemo(
|
||||
() => ({
|
||||
hour: getData('hour').scales,
|
||||
day: getData().scales,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const options = [
|
||||
{ id: 'hour', label: 'Hour' },
|
||||
{ id: 'day', label: 'Day' },
|
||||
];
|
||||
|
||||
const [durationUnit, setDurationUnit] = useState('hour');
|
||||
const [scales, setScales] = useState(scalesMap['hour']);
|
||||
|
||||
const [api, setApi] = useState(null);
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
defaultEditorItems.map((ed) => ({
|
||||
...ed,
|
||||
...(ed.comp === 'date' && {
|
||||
config: { time: durationUnit === 'hour' },
|
||||
}),
|
||||
})),
|
||||
[durationUnit],
|
||||
);
|
||||
|
||||
function handleUnitChange({ value }) {
|
||||
const sTasks = api.serialize().map((task) => {
|
||||
if (task.start && task.end) {
|
||||
const ms = 1000 * 60 * 60 * (value === 'day' ? 24 : 1);
|
||||
const duration = Math.floor((task.end - task.start) / ms);
|
||||
return { ...task, duration };
|
||||
}
|
||||
return task;
|
||||
});
|
||||
setTasks(sTasks);
|
||||
setDurationUnit(value);
|
||||
setScales(scalesMap[value]);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wx-d4Cw5r6y rows">
|
||||
<div className="wx-d4Cw5r6y bar">
|
||||
<div className="wx-d4Cw5r6y label">Gantt duration unit</div>
|
||||
<RadioButtonGroup
|
||||
options={options}
|
||||
value={durationUnit}
|
||||
type="inline"
|
||||
onChange={handleUnitChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="wx-d4Cw5r6y gtcell">
|
||||
<ContextMenu api={api}>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={tasks}
|
||||
links={links}
|
||||
scales={scales}
|
||||
cellWidth={40}
|
||||
durationUnit={durationUnit}
|
||||
lengthUnit={'hour'}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} items={items} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
63
demos/cases/GanttDurationUnitHour.jsx
Normal file
63
demos/cases/GanttDurationUnitHour.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useMemo, useState, useCallback } from 'react';
|
||||
import {
|
||||
Gantt,
|
||||
ContextMenu,
|
||||
Editor,
|
||||
defaultEditorItems,
|
||||
defaultColumns,
|
||||
} from '../../src';
|
||||
import { format } from 'date-fns';
|
||||
import { getData } from '../data';
|
||||
|
||||
export default function GanttDurationUnitHour({ skinSettings }) {
|
||||
const { tasks, links, scales } = useMemo(() => getData('hour'), []);
|
||||
|
||||
const [api, setApi] = useState(null);
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
defaultEditorItems.map((ed) => ({
|
||||
...ed,
|
||||
...(ed.comp === 'date' && { config: { time: true } }),
|
||||
})),
|
||||
[],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
defaultColumns.map((col) => ({
|
||||
...col,
|
||||
...(col.id === 'start' && {
|
||||
template: (d) => format(d, 'MMM d, HH:mm'),
|
||||
width: 120,
|
||||
}),
|
||||
})),
|
||||
[],
|
||||
);
|
||||
|
||||
const highlightTime = useCallback((date, unit) => {
|
||||
const h = date.getHours();
|
||||
if ((unit === 'hour' && h < 8) || h > 21) return 'wx-weekend';
|
||||
return '';
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu api={api}>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...(skinSettings || {})}
|
||||
tasks={tasks}
|
||||
links={links}
|
||||
columns={columns}
|
||||
scales={scales}
|
||||
cellWidth={40}
|
||||
durationUnit="hour"
|
||||
lengthUnit="minute"
|
||||
highlightTime={highlightTime}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} items={items} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
14
demos/cases/GanttEditor.css
Normal file
14
demos/cases/GanttEditor.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.wx-6xDuvBVG .gantt_task_line .gantt_link_control {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wx-6xDuvBVG .gantt_task_line .gantt_link_control .gantt_link_point {
|
||||
position: relative;
|
||||
top: auto;
|
||||
left: auto;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
margin: 0 3px;
|
||||
}
|
||||
34
demos/cases/GanttEditor.jsx
Normal file
34
demos/cases/GanttEditor.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useMemo, useState, useCallback } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor } from '../../src/';
|
||||
|
||||
function GanttEditor(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const init = useCallback((ganttApi) => {
|
||||
setApi(ganttApi);
|
||||
// show Editor on "add-task" action
|
||||
ganttApi.on('add-task', ({ id }) => {
|
||||
ganttApi.exec('show-editor', { id });
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
init={init}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttEditor;
|
||||
81
demos/cases/GanttEditorComments.jsx
Normal file
81
demos/cases/GanttEditorComments.jsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import {
|
||||
Gantt,
|
||||
Editor,
|
||||
defaultEditorItems,
|
||||
registerEditorItem,
|
||||
} from '../../src';
|
||||
import { Comments } from '@svar-ui/react-comments';
|
||||
registerEditorItem('comments', Comments);
|
||||
|
||||
function GanttEditorComments(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const data = useMemo(() => {
|
||||
const data = getData();
|
||||
data.tasks.forEach((d, i) => {
|
||||
d.comments = !i
|
||||
? [
|
||||
{
|
||||
id: 1,
|
||||
user: 1,
|
||||
content:
|
||||
'Greetings, fellow colleagues. I would like to share my insights on this task. I reckon we should deal with at least half of the points in the plan without further delays.',
|
||||
date: new Date(),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
user: 2,
|
||||
content:
|
||||
"Hi, Diego. I am sure that that's exactly what is thought best out there in Dunwall. Let's just do what we are supposed to do to get the result.",
|
||||
date: new Date(),
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
user: 3,
|
||||
content:
|
||||
"Absolutely, Diego. Action speaks louder than words, and in this case, it's about executing the plan efficiently. Let's prioritize tasks and tackle them head-on.",
|
||||
date: new Date(),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
});
|
||||
return data;
|
||||
}, []);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
const keys = useMemo(() => ['text', 'details'], []);
|
||||
const items = useMemo(() => {
|
||||
const items = defaultEditorItems.filter((op) => keys.indexOf(op.key) >= 0);
|
||||
items.push({
|
||||
key: 'comments',
|
||||
comp: 'comments',
|
||||
label: 'Comments',
|
||||
users: [
|
||||
{ id: 1, name: 'Alex' },
|
||||
{ id: 2, name: 'John' },
|
||||
{ id: 3, name: 'Bob' },
|
||||
{ id: 4, name: 'Mary' },
|
||||
{ id: 5, name: 'Kate' },
|
||||
],
|
||||
activeUser: 1,
|
||||
});
|
||||
return items;
|
||||
}, [keys]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{api && <Editor api={api} items={items} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttEditorComments;
|
||||
56
demos/cases/GanttEditorConfig.jsx
Normal file
56
demos/cases/GanttEditorConfig.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, defaultEditorItems } from '../../src';
|
||||
|
||||
function GanttEditorConfig({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const bottomBar = useMemo(
|
||||
() => ({
|
||||
items: [
|
||||
{ comp: 'button', type: 'secondary', text: 'Close', id: 'close' },
|
||||
{ comp: 'spacer' },
|
||||
{ comp: 'button', type: 'danger', text: 'Delete', id: 'delete' },
|
||||
{ comp: 'button', type: 'primary', text: 'Save', id: 'save' },
|
||||
],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const keys = useMemo(
|
||||
() => ['text', 'type', 'start', 'end', 'duration', 'progress', 'details'],
|
||||
[],
|
||||
);
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
keys.map((key) => ({
|
||||
...defaultEditorItems.find((op) => op.key === key),
|
||||
})),
|
||||
[keys],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{api && <Editor
|
||||
api={api}
|
||||
items={items}
|
||||
bottomBar={bottomBar}
|
||||
topBar={false}
|
||||
placement="modal"
|
||||
autoSave={false}
|
||||
/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttEditorConfig;
|
||||
82
demos/cases/GanttEditorCustomControls.jsx
Normal file
82
demos/cases/GanttEditorCustomControls.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Gantt,
|
||||
Editor,
|
||||
defaultEditorItems,
|
||||
registerEditorItem,
|
||||
defaultTaskTypes,
|
||||
} from '../../src';
|
||||
import { RadioButtonGroup } from '@svar-ui/react-core';
|
||||
import UsersCustomCombo from '../custom/UsersCustomCombo.jsx';
|
||||
import AvatarCell from '../custom/AvatarCell.jsx';
|
||||
import { getData, users } from '../data';
|
||||
|
||||
export default function GanttEditorCustomControls({ skinSettings }) {
|
||||
useEffect(() => {
|
||||
registerEditorItem('radio', RadioButtonGroup);
|
||||
registerEditorItem('custom-combo', UsersCustomCombo);
|
||||
}, []);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const items = defaultEditorItems.map((item) => ({ ...item }));
|
||||
items.splice(
|
||||
defaultEditorItems.findIndex((d) => d.key === 'type'),
|
||||
1,
|
||||
{
|
||||
key: 'type',
|
||||
comp: 'radio',
|
||||
label: 'Type',
|
||||
options: defaultTaskTypes.map((o) => ({
|
||||
...o,
|
||||
value: o.id,
|
||||
})),
|
||||
config: {
|
||||
type: 'inline',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'assigned',
|
||||
comp: 'custom-combo',
|
||||
label: 'Assigned',
|
||||
options: users,
|
||||
},
|
||||
);
|
||||
|
||||
items.forEach((d) => {
|
||||
if (d.comp === 'date') {
|
||||
d.config = {
|
||||
time: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}, []);
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{ id: 'text', header: 'Task name', width: 220 },
|
||||
{ id: 'assigned', header: 'Assigned', width: 160, cell: AvatarCell },
|
||||
{ id: 'start', header: 'Start Date', width: 100 },
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
lengthUnit="hour"
|
||||
columns={columns}
|
||||
/>
|
||||
{api && <Editor api={api} items={items} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
23
demos/cases/GanttEditorReadonly.jsx
Normal file
23
demos/cases/GanttEditorReadonly.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor } from '../../src';
|
||||
|
||||
function GanttEditorReadonly({...skinSettings}) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const [api, setApi] = useState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{api && <Editor api={api} readonly={true} topBar={true} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttEditorReadonly;
|
||||
96
demos/cases/GanttEditorTasks.jsx
Normal file
96
demos/cases/GanttEditorTasks.jsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import {
|
||||
Gantt,
|
||||
Editor,
|
||||
defaultEditorItems,
|
||||
registerEditorItem,
|
||||
} from '../../src';
|
||||
import { Tasklist } from '@svar-ui/react-tasklist';
|
||||
|
||||
registerEditorItem('tasks', Tasklist);
|
||||
|
||||
export default function GanttEditorTasks({ skinSettings }) {
|
||||
const data = useMemo(() => {
|
||||
const data = getData();
|
||||
data.tasks.forEach((d, i) => {
|
||||
d.tasks = !i
|
||||
? [
|
||||
{
|
||||
id: 1,
|
||||
content:
|
||||
'Research best practices for integrating third-party libraries with React',
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
content:
|
||||
'Explore modern approaches to building applications using React',
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
content:
|
||||
'Explore different methods for integrating React with existing JavaScript frameworks',
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
date: new Date(),
|
||||
content: 'Learn about routing in React using React Router',
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
content:
|
||||
'Understand principles and best practices for component development in React',
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
content:
|
||||
'Explore different methods for integrating React with existing JavaScript frameworks',
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
content: 'Optimize performance in React applications',
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
content:
|
||||
'Work with API requests and data handling in React applications',
|
||||
status: 0,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
});
|
||||
return data;
|
||||
}, []);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const keys = ['text', 'details'];
|
||||
const items = defaultEditorItems.filter((op) => keys.indexOf(op.key) >= 0);
|
||||
items.push({
|
||||
key: 'tasks',
|
||||
comp: 'tasks',
|
||||
label: 'Tasks',
|
||||
});
|
||||
return items;
|
||||
}, []);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{api && <Editor api={api} items={items} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
4
demos/cases/GanttEditorValidation.css
Normal file
4
demos/cases/GanttEditorValidation.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.wx-22L1T8uz {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
36
demos/cases/GanttEditorValidation.jsx
Normal file
36
demos/cases/GanttEditorValidation.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, defaultEditorItems } from '../../src';
|
||||
|
||||
function GanttEditorValidation({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
defaultEditorItems.map((ed) => ({
|
||||
...ed,
|
||||
...(ed.comp === 'text' && { required: true }),
|
||||
...(ed.comp === 'counter' && {
|
||||
validation: (v) => v <= 50,
|
||||
validationMessage: 'Task duration should not exceed 50 days',
|
||||
}),
|
||||
})),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{api && <Editor api={api} items={items} autoSave={false} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttEditorValidation;
|
||||
39
demos/cases/GanttFixedColumns.jsx
Normal file
39
demos/cases/GanttFixedColumns.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
|
||||
export default function GanttFixedColumns(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{ id: 'text', header: 'Task name', width: 120 },
|
||||
{
|
||||
id: 'start',
|
||||
header: 'Start date',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
header: 'Duration',
|
||||
width: 90,
|
||||
align: 'center',
|
||||
},
|
||||
{ id: 'add-task', header: 'Add task', width: 50, align: 'center' },
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
columns={columns}
|
||||
/>
|
||||
);
|
||||
}
|
||||
44
demos/cases/GanttFlexColumns.jsx
Normal file
44
demos/cases/GanttFlexColumns.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
|
||||
function GanttFlexColumns({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{ id: 'text', header: 'Task name', flexgrow: 2 },
|
||||
{
|
||||
id: 'start',
|
||||
header: 'Start date',
|
||||
flexgrow: 1,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
header: 'Duration',
|
||||
align: 'center',
|
||||
flexgrow: 1,
|
||||
},
|
||||
{
|
||||
id: 'add-task',
|
||||
header: 'Add task',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
columns={columns}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttFlexColumns;
|
||||
4
demos/cases/GanttForm.css
Normal file
4
demos/cases/GanttForm.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.wrapper.wx-0moLF6Ul {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
50
demos/cases/GanttForm.jsx
Normal file
50
demos/cases/GanttForm.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, defaultTaskTypes } from '../../src/';
|
||||
import Form from '../custom/Form.jsx';
|
||||
import './GanttForm.css';
|
||||
|
||||
export default function GanttForm({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const taskTypes = defaultTaskTypes;
|
||||
|
||||
const [task, setTask] = useState();
|
||||
const [gApi, setGApi] = useState();
|
||||
|
||||
const formAction = useCallback(
|
||||
({ action, data }) => {
|
||||
switch (action) {
|
||||
case 'close-form':
|
||||
setTask(null);
|
||||
break;
|
||||
default:
|
||||
gApi.exec(action, data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
[gApi],
|
||||
);
|
||||
|
||||
const init = useCallback((api) => {
|
||||
api.intercept('show-editor', ({ id }) => {
|
||||
if (id) setTask(api.getState().tasks.byId(id));
|
||||
return false;
|
||||
});
|
||||
setGApi(api);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="wx-0moLF6Ul wrapper">
|
||||
<Gantt
|
||||
init={init}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{task ? (
|
||||
<Form task={task} taskTypes={taskTypes} onAction={formAction} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
demos/cases/GanttFullscreen.css
Normal file
12
demos/cases/GanttFullscreen.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.demo.wx-0qqHrQ85 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gtcell.wx-0qqHrQ85 {
|
||||
overflow: hidden;
|
||||
border: var(--wx-gantt-border);
|
||||
height: calc(100% - 32px);
|
||||
}
|
||||
25
demos/cases/GanttFullscreen.jsx
Normal file
25
demos/cases/GanttFullscreen.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Fullscreen } from '../../src/';
|
||||
import './GanttFullscreen.css';
|
||||
|
||||
function GanttFullscreen({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<div className="wx-0qqHrQ85 demo">
|
||||
<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}
|
||||
/>
|
||||
</Fullscreen>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttFullscreen;
|
||||
37
demos/cases/GanttGrid.jsx
Normal file
37
demos/cases/GanttGrid.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import AvatarCell from '../custom/AvatarCell.jsx';
|
||||
import NameAndDateCell from '../custom/NameAndDateCell.jsx';
|
||||
import AddTaskCell from '../custom/AddTaskCell.jsx';
|
||||
|
||||
function GanttGrid({ skinSettings }) {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{ id: 'text', header: 'Task', width: 220, cell: NameAndDateCell },
|
||||
{ id: 'assigned', header: 'Assigned', width: 160, cell: AvatarCell },
|
||||
{
|
||||
id: 'add-task',
|
||||
header: { cell: AddTaskCell },
|
||||
align: 'center',
|
||||
width: 80,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
columns={columns}
|
||||
cellHeight={40}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttGrid;
|
||||
46
demos/cases/GanttHolidays.jsx
Normal file
46
demos/cases/GanttHolidays.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
|
||||
function GanttHolidays({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
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 */ }
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
function isDayOff(date) {
|
||||
const d = date.getDay();
|
||||
return d == 0 || d == 6;
|
||||
}
|
||||
|
||||
function isHourOff(date) {
|
||||
const h = date.getHours();
|
||||
return h < 8 || h == 12 || h > 17;
|
||||
}
|
||||
|
||||
function highlightTime(d, u) {
|
||||
if (u == 'day' && isDayOff(d)) return 'wx-weekend';
|
||||
if (u == 'hour' && (isDayOff(d) || isHourOff(d))) return 'wx-weekend';
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={scales}
|
||||
highlightTime={highlightTime}
|
||||
zoom
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttHolidays;
|
||||
18
demos/cases/GanttLengthUnit.css
Normal file
18
demos/cases/GanttLengthUnit.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.demo.wx-VOqDTkHq {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bar.wx-VOqDTkHq {
|
||||
padding: 20px;
|
||||
background-color: var(--wx-background);
|
||||
border: var(--wx-border);
|
||||
|
||||
--wx-input-width: 100px;
|
||||
}
|
||||
|
||||
.gantt.wx-VOqDTkHq {
|
||||
position: relative;
|
||||
height: calc(100% - 74px);
|
||||
}
|
||||
86
demos/cases/GanttLengthUnit.jsx
Normal file
86
demos/cases/GanttLengthUnit.jsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData, bigScales } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import { Select } from '@svar-ui/react-core';
|
||||
import './GanttLengthUnit.css';
|
||||
|
||||
function GanttLengthUnit({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const options = [
|
||||
{ id: 'minute', label: 'Minute' },
|
||||
{ id: 'hour', label: 'Hour' },
|
||||
{ id: 'day', label: 'Day' },
|
||||
{ id: 'week', label: 'Week' },
|
||||
{ id: 'month', label: 'Month' },
|
||||
{ id: 'quarter', label: 'Quarter' },
|
||||
];
|
||||
|
||||
const [lengthUnit, setLengthUnit] = useState('day');
|
||||
|
||||
const scales = useMemo(() => {
|
||||
let scales;
|
||||
switch (lengthUnit) {
|
||||
case 'minute':
|
||||
scales = [
|
||||
{ unit: 'day', step: 1, format: 'MMM d' },
|
||||
{ unit: 'hour', step: 1, format: 'HH:mm' },
|
||||
];
|
||||
break;
|
||||
case 'hour':
|
||||
scales = [
|
||||
{ unit: 'month', step: 1, format: 'MMM' },
|
||||
{ unit: 'day', step: 1, format: 'MMM d' },
|
||||
];
|
||||
break;
|
||||
case 'day':
|
||||
scales = [
|
||||
{ unit: 'month', step: 1, format: 'MMM' },
|
||||
{ unit: 'week', step: 1, format: 'w' },
|
||||
];
|
||||
break;
|
||||
case 'week':
|
||||
scales = [
|
||||
{ unit: 'year', step: 1, format: 'yyyy' },
|
||||
{ unit: 'month', step: 1, format: 'MMM' },
|
||||
];
|
||||
break;
|
||||
case 'month':
|
||||
scales = [
|
||||
{ unit: 'year', step: 1, format: 'yyyy' },
|
||||
{ unit: 'quarter', step: 1, format: 'QQQ' },
|
||||
];
|
||||
break;
|
||||
case 'quarter':
|
||||
scales = [{ unit: 'year', step: 1, format: 'yyyy' }];
|
||||
break;
|
||||
default:
|
||||
scales = bigScales;
|
||||
}
|
||||
return scales;
|
||||
}, [lengthUnit]);
|
||||
|
||||
return (
|
||||
<div className="wx-VOqDTkHq demo">
|
||||
<div className="wx-VOqDTkHq bar">
|
||||
<Select
|
||||
value={lengthUnit}
|
||||
options={options}
|
||||
onChange={({ value }) => setLengthUnit(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="wx-VOqDTkHq gantt">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={scales}
|
||||
lengthUnit={lengthUnit}
|
||||
cellWidth={300}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttLengthUnit;
|
||||
18
demos/cases/GanttLocale.css
Normal file
18
demos/cases/GanttLocale.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.rows.wx-ycv5Oz8L {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-ycv5Oz8L {
|
||||
padding: 12px;
|
||||
border-bottom: var(--wx-gantt-border);
|
||||
}
|
||||
.gtcell.wx-ycv5Oz8L {
|
||||
position: relative;
|
||||
height: calc(100% - 106px);
|
||||
border-top: var(--wx-gantt-border);
|
||||
}
|
||||
65
demos/cases/GanttLocale.jsx
Normal file
65
demos/cases/GanttLocale.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu, Toolbar, Editor } from '../../src/';
|
||||
import { Segmented, Locale } from '@svar-ui/react-core';
|
||||
import { cn } from '@svar-ui/gantt-locales';
|
||||
import { cn as cnCore } from '@svar-ui/core-locales';
|
||||
import './GanttLocale.css';
|
||||
|
||||
function GanttLocale({ skinSettings }) {
|
||||
const langs = [
|
||||
{ id: 'en', label: 'EN' },
|
||||
{ id: 'cn', label: 'CN' },
|
||||
];
|
||||
const [lang, setLang] = useState('en');
|
||||
|
||||
return (
|
||||
<div className="wx-ycv5Oz8L rows">
|
||||
<div className="wx-ycv5Oz8L bar">
|
||||
<Segmented
|
||||
options={langs}
|
||||
value={lang}
|
||||
onChange={({ value }) => setLang(value)}
|
||||
/>
|
||||
</div>
|
||||
{lang === 'en' && (
|
||||
<GanttWidget
|
||||
skinSettings={skinSettings}
|
||||
/>
|
||||
)}
|
||||
{lang === 'cn' && (
|
||||
<Locale words={{ ...cn, ...cnCore }}>
|
||||
<GanttWidget skinSettings={skinSettings} />
|
||||
</Locale>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GanttWidget(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const [api, setApi] = useState(null);
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
|
||||
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}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttLocale;
|
||||
18
demos/cases/GanttMinScaleUnit.css
Normal file
18
demos/cases/GanttMinScaleUnit.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.demo.wx-megyPaP4 {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bar.wx-megyPaP4 {
|
||||
padding: 20px;
|
||||
background-color: var(--wx-background);
|
||||
border: var(--wx-border);
|
||||
|
||||
--wx-input-width: 180px;
|
||||
}
|
||||
|
||||
.gantt.wx-megyPaP4 {
|
||||
position: relative;
|
||||
height: calc(100% - 74px);
|
||||
}
|
||||
138
demos/cases/GanttMinScaleUnit.jsx
Normal file
138
demos/cases/GanttMinScaleUnit.jsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, registerScaleUnit } from '../../src/';
|
||||
import { Select } from '@svar-ui/react-core';
|
||||
import {
|
||||
startOfMonth,
|
||||
endOfMonth,
|
||||
isSameMonth,
|
||||
addMonths,
|
||||
addDays,
|
||||
format,
|
||||
differenceInDays,
|
||||
} 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' },
|
||||
];
|
||||
|
||||
export default function GanttMinScaleUnit({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const getMidDate = (d) => {
|
||||
const m = d.getMonth();
|
||||
return m === 1 ? 15 : 16;
|
||||
};
|
||||
|
||||
const sprintStart = (d) => {
|
||||
const monthStart = startOfMonth(d);
|
||||
const midDate = getMidDate(d);
|
||||
if (d.getDate() >= midDate) monthStart.setDate(midDate);
|
||||
return monthStart;
|
||||
};
|
||||
|
||||
const sprintEnd = (d) => {
|
||||
const monthEnd = endOfMonth(d);
|
||||
const midDate = getMidDate(d);
|
||||
if (d.getDate() < midDate) monthEnd.setDate(midDate - 1);
|
||||
return monthEnd;
|
||||
};
|
||||
|
||||
const sprintFormat = (d) => {
|
||||
const monthStr = format(d, 'MMMM');
|
||||
const start = d.getDate();
|
||||
const end = sprintEnd(d).getDate();
|
||||
return `${monthStr} ${start} - ${end}`;
|
||||
};
|
||||
|
||||
const allScales = useMemo(
|
||||
() => [
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyy' },
|
||||
{ unit: 'sprint', step: 1, format: sprintFormat },
|
||||
{ unit: 'week', step: 1, format: 'w' },
|
||||
{ unit: 'day', step: 1, format: 'd' },
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const [scaleOption, setScaleOption] = useState(2);
|
||||
|
||||
const scales = useMemo(() => {
|
||||
if (scaleOption == 1) return [allScales[1]];
|
||||
if (scaleOption == 2) return allScales.slice(0, 2);
|
||||
if (scaleOption == 3) return allScales.slice(0, 3);
|
||||
return allScales;
|
||||
}, [scaleOption, allScales]);
|
||||
|
||||
const registeredRef = useRef(false);
|
||||
if (!registeredRef.current) {
|
||||
registerScaleUnit('sprint', {
|
||||
start: sprintStart,
|
||||
end: sprintEnd,
|
||||
isSame: (a, b) => {
|
||||
if (!a || !b) return true;
|
||||
const sameMonth = isSameMonth(a, b);
|
||||
if (!sameMonth) return false;
|
||||
const midDate = getMidDate(a);
|
||||
return a.getDate() < midDate == b.getDate() < midDate;
|
||||
},
|
||||
add: (d, amount) => {
|
||||
const date = d.getDate();
|
||||
const start = sprintStart(d);
|
||||
const diff = date - start.getDate();
|
||||
let newDate = addMonths(start, Math.floor(amount / 2));
|
||||
const midDate = getMidDate(newDate);
|
||||
if (amount % 2) {
|
||||
newDate = addDays(newDate, midDate);
|
||||
newDate = sprintStart(newDate);
|
||||
}
|
||||
return addDays(newDate, diff);
|
||||
},
|
||||
diff: (endDate, startDate) => {
|
||||
return Math.floor(differenceInDays(endDate, startDate) / 15);
|
||||
},
|
||||
smallerCount: {
|
||||
day: (d) => {
|
||||
if (!d) return 15;
|
||||
const start = sprintStart(d).getDate();
|
||||
const end = sprintEnd(d).getDate();
|
||||
return end - start + 1;
|
||||
},
|
||||
},
|
||||
biggerCount: {
|
||||
year: 24,
|
||||
quarter: 6,
|
||||
month: 2,
|
||||
},
|
||||
});
|
||||
registeredRef.current = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wx-megyPaP4 demo">
|
||||
<div className="wx-megyPaP4 bar">
|
||||
<Select
|
||||
value={scaleOption}
|
||||
options={options}
|
||||
onChange={({ value }) => setScaleOption(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="wx-megyPaP4 gantt">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={scales}
|
||||
zoom={true}
|
||||
start={new Date(2024, 3, 1)}
|
||||
end={new Date(2024, 5, 1)}
|
||||
cellWidth={60}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
26
demos/cases/GanttMultiple.css
Normal file
26
demos/cases/GanttMultiple.css
Normal file
@@ -0,0 +1,26 @@
|
||||
.rows.wx-NlISAKUQ {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.row.wx-NlISAKUQ {
|
||||
padding: 13px;
|
||||
}
|
||||
|
||||
.ganttCell.wx-NlISAKUQ {
|
||||
height: 410px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.ganttHeader.wx-NlISAKUQ {
|
||||
padding: 13px;
|
||||
border-bottom: var(--wx-gantt-border);
|
||||
}
|
||||
|
||||
.ganttBox.wx-NlISAKUQ {
|
||||
overflow: hidden;
|
||||
}
|
||||
72
demos/cases/GanttMultiple.jsx
Normal file
72
demos/cases/GanttMultiple.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import { Button } from '@svar-ui/react-core';
|
||||
import './GanttMultiple.css';
|
||||
|
||||
function GanttMultiple(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const counterRef = useRef(0);
|
||||
const [gantts, setGantts] = useState(() => {
|
||||
const initial = [];
|
||||
initial.push({
|
||||
id: counterRef.current,
|
||||
data: getData(),
|
||||
});
|
||||
counterRef.current += 1;
|
||||
initial.push({
|
||||
id: counterRef.current,
|
||||
data: getData(),
|
||||
});
|
||||
counterRef.current += 1;
|
||||
return initial;
|
||||
});
|
||||
|
||||
function addGantt() {
|
||||
setGantts((prev) => {
|
||||
const next = [
|
||||
...prev,
|
||||
{
|
||||
id: counterRef.current,
|
||||
data: getData(),
|
||||
},
|
||||
];
|
||||
counterRef.current += 1;
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
function removeGantt(id) {
|
||||
setGantts((prev) => prev.filter((a) => a.id !== id));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wx-NlISAKUQ rows">
|
||||
<div className="wx-NlISAKUQ row">
|
||||
<Button type="primary" onClick={addGantt}>
|
||||
Add Gantt
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{gantts.map((gantt, id) => (
|
||||
<div className="wx-NlISAKUQ ganttCell" key={id}>
|
||||
<div className="wx-NlISAKUQ ganttHeader">
|
||||
<Button type="secondary" onClick={() => removeGantt(gantt.id)}>
|
||||
Delete Gantt
|
||||
</Button>
|
||||
</div>
|
||||
<div className="wx-NlISAKUQ ganttBox">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={gantt.data.tasks}
|
||||
links={gantt.data.links}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttMultiple;
|
||||
22
demos/cases/GanttNoGrid.jsx
Normal file
22
demos/cases/GanttNoGrid.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
|
||||
function GanttNoGrid(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
columns={false}
|
||||
cellWidth={60}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttNoGrid;
|
||||
26
demos/cases/GanttPerformance.css
Normal file
26
demos/cases/GanttPerformance.css
Normal file
@@ -0,0 +1,26 @@
|
||||
.rows.wx-KB3Eoqwm {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.row.wx-KB3Eoqwm {
|
||||
padding: 13px;
|
||||
font-family: var(--wx-font-family);
|
||||
font-size: var(--wx-font-size);
|
||||
}
|
||||
|
||||
.gtcell.wx-KB3Eoqwm {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
border-top: var(--wx-gantt-border);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gtcell.wx-KB3Eoqwm:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
52
demos/cases/GanttPerformance.jsx
Normal file
52
demos/cases/GanttPerformance.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { getGeneratedData, complexScales } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import { Button } from '@svar-ui/react-core';
|
||||
import './GanttPerformance.css';
|
||||
|
||||
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
|
||||
</>
|
||||
) : (
|
||||
<Button type="primary" onClick={() => setStart(new Date())}>
|
||||
Press me to render Gantt chart with 1 000 tasks
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{start ? (
|
||||
<div className="wx-KB3Eoqwm gtcell">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={complexScales}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttPerformance;
|
||||
38
demos/cases/GanttPreventActions.css
Normal file
38
demos/cases/GanttPreventActions.css
Normal file
@@ -0,0 +1,38 @@
|
||||
.rows.wx-RPSbwjNq {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bar.wx-RPSbwjNq {
|
||||
display: flex;
|
||||
height: 60px;
|
||||
align-items: end;
|
||||
font-family: var(--wx-font-family);
|
||||
font-size: var(--wx-font-size);
|
||||
padding-top: 12px;
|
||||
--wx-label-width: 130px;
|
||||
}
|
||||
|
||||
.bar.wx-RPSbwjNq:first-child {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.gantt.wx-RPSbwjNq {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
border-top: var(--wx-gantt-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gantt.wx-RPSbwjNq.hide-progress > .wx-gantt .wx-bar .wx-progress-marker {
|
||||
display: none;
|
||||
}
|
||||
.gantt.wx-RPSbwjNq.hide-links > .wx-gantt .wx-bar .wx-link {
|
||||
display: none;
|
||||
}
|
||||
.gantt.wx-RPSbwjNq.hide-drag > .wx-gantt .wx-bar {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
122
demos/cases/GanttPreventActions.jsx
Normal file
122
demos/cases/GanttPreventActions.jsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, defaultColumns, Editor } 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 [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 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]);
|
||||
|
||||
useEffect(() => {
|
||||
orderRef.current = order;
|
||||
}, [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
|
||||
});
|
||||
}, []);
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
edit ? defaultColumns : defaultColumns.filter((a) => a.id != 'add-task'),
|
||||
[edit],
|
||||
);
|
||||
|
||||
// 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>
|
||||
<Field label="Creating links" position={'left'}>
|
||||
{({ id }) => (
|
||||
<Switch
|
||||
value={newLink}
|
||||
onChange={({ value }) => setNewLink(value)}
|
||||
id={id}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<Field label="Dragging tasks" position={'left'}>
|
||||
{({ id }) => (
|
||||
<Switch
|
||||
value={drag}
|
||||
onChange={({ value }) => setDrag(value)}
|
||||
id={id}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<Field label="Reordering tasks" position={'left'}>
|
||||
{({ id }) => (
|
||||
<Switch
|
||||
value={order}
|
||||
onChange={({ value }) => setOrder(value)}
|
||||
id={id}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
'wx-RPSbwjNq ' +
|
||||
('gantt' +
|
||||
(!edit ? ' hide-progress' : '') +
|
||||
(!newLink ? ' hide-links' : '') +
|
||||
(!drag ? ' hide-drag' : ''))
|
||||
}
|
||||
>
|
||||
<Gantt
|
||||
init={init}
|
||||
{...(skinSettings || {})}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
columns={columns}
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttPreventActions;
|
||||
51
demos/cases/GanttProvider.jsx
Normal file
51
demos/cases/GanttProvider.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { RestDataProvider } from '@svar-ui/gantt-data-provider';
|
||||
import { Gantt, ContextMenu, Editor } from '../../src';
|
||||
|
||||
export default function GanttProvider() {
|
||||
const restProvider = useMemo(
|
||||
() => new RestDataProvider('https://master--svar-gantt-go--dev.webix.io'),
|
||||
[],
|
||||
);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
const [tasks, setTasks] = useState();
|
||||
const [links, setLinks] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
restProvider.getData().then(({ tasks: t, links: l }) => {
|
||||
setTasks(t);
|
||||
setLinks(l);
|
||||
});
|
||||
}, [restProvider]);
|
||||
|
||||
const init = useCallback(
|
||||
(api) => {
|
||||
setApi(api);
|
||||
|
||||
api.setNext(restProvider);
|
||||
|
||||
api.on('request-data', (ev) => {
|
||||
restProvider.getData(ev.id).then(({ tasks, links }) => {
|
||||
api.exec('provide-data', {
|
||||
id: ev.id,
|
||||
data: {
|
||||
tasks,
|
||||
links,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
[restProvider],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu api={api}>
|
||||
<Gantt init={init} tasks={tasks} links={links} />
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
17
demos/cases/GanttReadOnly.jsx
Normal file
17
demos/cases/GanttReadOnly.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
|
||||
export default function GanttReadOnly({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
readonly={true}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
);
|
||||
}
|
||||
87
demos/cases/GanttScaleUnit.jsx
Normal file
87
demos/cases/GanttScaleUnit.jsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, registerScaleUnit } from '../../src/';
|
||||
import {
|
||||
startOfMonth,
|
||||
endOfMonth,
|
||||
isSameMonth,
|
||||
addMonths,
|
||||
addDays,
|
||||
format,
|
||||
} from 'date-fns';
|
||||
|
||||
export default function GanttScaleUnit(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const getMidDate = (d) => {
|
||||
const m = d.getMonth();
|
||||
return m === 1 ? 15 : 16;
|
||||
};
|
||||
|
||||
const sprintStart = (d) => {
|
||||
const monthStart = startOfMonth(d);
|
||||
const midDate = getMidDate(d);
|
||||
if (d.getDate() >= midDate) monthStart.setDate(midDate);
|
||||
return monthStart;
|
||||
};
|
||||
|
||||
const sprintEnd = (d) => {
|
||||
const monthEnd = endOfMonth(d);
|
||||
const midDate = getMidDate(d);
|
||||
if (d.getDate() < midDate) monthEnd.setDate(midDate - 1);
|
||||
return monthEnd;
|
||||
};
|
||||
|
||||
const sprintFormat = (d) => {
|
||||
const monthStr = format(d, 'MMMM');
|
||||
const start = d.getDate();
|
||||
const end = sprintEnd(d).getDate();
|
||||
return `${monthStr} ${start} - ${end}`;
|
||||
};
|
||||
|
||||
const registeredRef = useRef(false);
|
||||
if (!registeredRef.current) {
|
||||
registerScaleUnit('sprint', {
|
||||
start: sprintStart,
|
||||
end: sprintEnd,
|
||||
isSame: (a, b) => {
|
||||
const sameMonth = isSameMonth(a, b);
|
||||
if (!sameMonth) return false;
|
||||
const midDate = getMidDate(a);
|
||||
return a.getDate() < midDate == b.getDate() < midDate;
|
||||
},
|
||||
add: (d, amount) => {
|
||||
const date = d.getDate();
|
||||
const start = sprintStart(d);
|
||||
const diff = date - start.getDate();
|
||||
let newDate = addMonths(start, Math.floor(amount / 2));
|
||||
const midDate = getMidDate(newDate);
|
||||
if (amount % 2) {
|
||||
newDate = addDays(newDate, midDate);
|
||||
newDate = sprintStart(newDate);
|
||||
}
|
||||
return addDays(newDate, diff);
|
||||
},
|
||||
});
|
||||
registeredRef.current = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={[
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyy' },
|
||||
{ unit: 'sprint', step: 1, format: sprintFormat },
|
||||
{ unit: 'day', step: 1, format: 'd' },
|
||||
]}
|
||||
zoom={true}
|
||||
start={new Date(2024, 3, 1)}
|
||||
end={new Date(2024, 5, 1)}
|
||||
cellWidth={60}
|
||||
/>
|
||||
);
|
||||
}
|
||||
21
demos/cases/GanttScales.jsx
Normal file
21
demos/cases/GanttScales.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData, complexScales } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
|
||||
function GanttScales({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={complexScales}
|
||||
start={new Date(2024, 3, 1)}
|
||||
end={new Date(2024, 4, 12)}
|
||||
cellWidth={60}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttScales;
|
||||
19
demos/cases/GanttSizes.css
Normal file
19
demos/cases/GanttSizes.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.rows.wx-MZ5YXrqJ {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--wx-background);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-MZ5YXrqJ {
|
||||
padding: 10px;
|
||||
}
|
||||
.gtcell.wx-MZ5YXrqJ {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
border-top: var(--wx-gantt-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
54
demos/cases/GanttSizes.jsx
Normal file
54
demos/cases/GanttSizes.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData, complexScales } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import { Slider } from '@svar-ui/react-core';
|
||||
import './GanttSizes.css';
|
||||
|
||||
function GanttSizes({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const [cellWidth, setCellWidth] = useState(100);
|
||||
const [scaleHeight, setScaleHeight] = useState(38);
|
||||
const [cellHeight, setCellHeight] = useState(36);
|
||||
|
||||
return (
|
||||
<div className="wx-MZ5YXrqJ rows">
|
||||
<div className="wx-MZ5YXrqJ bar">
|
||||
<Slider
|
||||
label="Cell width"
|
||||
value={cellWidth}
|
||||
onChange={({ value }) => setCellWidth(value)}
|
||||
min={20}
|
||||
max={200}
|
||||
/>
|
||||
<Slider
|
||||
label="Cell height"
|
||||
value={cellHeight}
|
||||
onChange={({ value }) => setCellHeight(value)}
|
||||
min={20}
|
||||
max={60}
|
||||
/>
|
||||
<Slider
|
||||
label="Scale height"
|
||||
value={scaleHeight}
|
||||
onChange={({ value }) => setScaleHeight(value)}
|
||||
min={20}
|
||||
max={60}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="wx-MZ5YXrqJ gtcell">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={complexScales}
|
||||
cellWidth={cellWidth}
|
||||
cellHeight={cellHeight}
|
||||
scaleHeight={scaleHeight}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttSizes;
|
||||
12
demos/cases/GanttSort.css
Normal file
12
demos/cases/GanttSort.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.demo.wx-fr1PMpDW {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gtcell.wx-fr1PMpDW {
|
||||
overflow: hidden;
|
||||
border: var(--wx-gantt-border);
|
||||
height: calc(100% - 32px);
|
||||
}
|
||||
31
demos/cases/GanttSort.jsx
Normal file
31
demos/cases/GanttSort.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData, zoomConfig } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import './GanttSort.css';
|
||||
|
||||
function GanttSort({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
function init(api) {
|
||||
api.intercept('sort-tasks', (config) => {
|
||||
return config.key == 'text';
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wx-fr1PMpDW demo">
|
||||
<h4>Sorting by the "Task Name" column only</h4>
|
||||
<div className="wx-fr1PMpDW gtcell">
|
||||
<Gantt
|
||||
init={init}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
zoom={zoomConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttSort;
|
||||
21
demos/cases/GanttStartEnd.css
Normal file
21
demos/cases/GanttStartEnd.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.demo.wx-FJQN2sNt {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bar.wx-FJQN2sNt {
|
||||
display: flex;
|
||||
padding: 12px;
|
||||
border-bottom: var(--wx-gantt-border);
|
||||
}
|
||||
|
||||
.gantt.wx-FJQN2sNt {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.input.wx-FJQN2sNt {
|
||||
margin: 4px;
|
||||
}
|
||||
63
demos/cases/GanttStartEnd.jsx
Normal file
63
demos/cases/GanttStartEnd.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import { DatePicker, Field, Locale, Switch } from '@svar-ui/react-core';
|
||||
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 [autoScale, setAutoScale] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="wx-FJQN2sNt demo">
|
||||
<Locale>
|
||||
<div className="wx-FJQN2sNt bar">
|
||||
<Field label="Start" position="left">
|
||||
{({ id }) => (
|
||||
<DatePicker
|
||||
value={start}
|
||||
id={id}
|
||||
onChange={({ value }) => setStart(value)}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<Field label="End" position="left">
|
||||
{({ id }) => (
|
||||
<DatePicker
|
||||
value={end}
|
||||
id={id}
|
||||
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>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</Locale>
|
||||
|
||||
<div className="wx-FJQN2sNt gantt">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
autoScale={autoScale}
|
||||
zoom
|
||||
start={start}
|
||||
end={end}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
103
demos/cases/GanttSummariesConvert.jsx
Normal file
103
demos/cases/GanttSummariesConvert.jsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { useMemo, useCallback, useRef } from 'react';
|
||||
import { useStoreLater } from '@svar-ui/lib-react';
|
||||
import { getData } from '../data';
|
||||
import {
|
||||
Gantt,
|
||||
ContextMenu,
|
||||
Editor,
|
||||
defaultEditorItems,
|
||||
defaultTaskTypes,
|
||||
} from '../../src/';
|
||||
|
||||
export default function GanttSummariesConvert({ 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 defaultEditorItems.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>
|
||||
);
|
||||
}
|
||||
31
demos/cases/GanttSummariesNoDrag.jsx
Normal file
31
demos/cases/GanttSummariesNoDrag.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, ContextMenu, Editor } from '../../src/';
|
||||
|
||||
export default function GanttSummariesNoDrag({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const init = useCallback((api) => {
|
||||
setApi(api);
|
||||
api.intercept('drag-task', ({ id, top }) => {
|
||||
return !(typeof top === 'undefined' && api.getTask(id).type == 'summary');
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="wx-h4Sohvpn gt-cell">
|
||||
<ContextMenu api={api}>
|
||||
<Gantt
|
||||
init={init}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
demos/cases/GanttSummariesProgress.css
Normal file
3
demos/cases/GanttSummariesProgress.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.gt-cell > .wx-gantt .wx-summary .wx-progress-marker {
|
||||
display: none;
|
||||
}
|
||||
134
demos/cases/GanttSummariesProgress.jsx
Normal file
134
demos/cases/GanttSummariesProgress.jsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor, defaultEditorItems, ContextMenu } from '../../src';
|
||||
import './GanttSummariesProgress.css';
|
||||
|
||||
function GanttSummariesProgress({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const tasks = data.tasks;
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const [items, setItems] = useState(defaultEditorItems);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
// disabling progress slider for summary tasks
|
||||
api.on('show-editor', ({ id }) => {
|
||||
if (id) {
|
||||
const type = api.getTask(id).type;
|
||||
setItems(
|
||||
defaultEditorItems.map((ed) => ({
|
||||
...ed,
|
||||
...(ed.key == 'progress' && {
|
||||
config: { disabled: type === 'summary' },
|
||||
}),
|
||||
})),
|
||||
);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!api) return;
|
||||
api.getState().tasks.forEach((task) => {
|
||||
recalcSummaryProgress(task.id, true);
|
||||
});
|
||||
}, [api]);
|
||||
|
||||
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 && <Editor api={api} items={items} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttSummariesProgress;
|
||||
55
demos/cases/GanttTaskTypes.css
Normal file
55
demos/cases/GanttTaskTypes.css
Normal file
@@ -0,0 +1,55 @@
|
||||
.demo.wx-I1glfWSB {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.narrow {
|
||||
background-color: #676a81;
|
||||
color: transparent;
|
||||
height: 10px !important;
|
||||
margin-top: 10px;
|
||||
border: 1px solid #63667a;
|
||||
}
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.narrow .wx-progress-percent {
|
||||
background-color: #1a2630;
|
||||
}
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.narrow .wx-link {
|
||||
background-color: #384047;
|
||||
border-radius: 0px;
|
||||
}
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.narrow .wx-link .wx-inner {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.round {
|
||||
background-color: #ffeb3b;
|
||||
border: 1px solid #ffeb3b;
|
||||
color: #384047;
|
||||
border-radius: 50px;
|
||||
}
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.round .wx-progress-percent {
|
||||
background-color: #ffc107;
|
||||
}
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.round .wx-progress-wrapper {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.progress {
|
||||
background-color: transparent;
|
||||
color: var(--wx-color-font);
|
||||
border-radius: 50px;
|
||||
border: 1px solid #00bcd4;
|
||||
}
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.progress .wx-progress-percent {
|
||||
background-color: #00bcd4;
|
||||
}
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.progress .wx-progress-wrapper {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.urgent {
|
||||
background-color: #f49a82;
|
||||
border: 1px solid #f45e36;
|
||||
}
|
||||
.demo.wx-I1glfWSB > .wx-gantt .wx-bar.wx-task.urgent .wx-progress-percent {
|
||||
background-color: #f45e36;
|
||||
}
|
||||
27
demos/cases/GanttTaskTypes.jsx
Normal file
27
demos/cases/GanttTaskTypes.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getTypedData, taskTypes } from '../data';
|
||||
import { Gantt, Editor } from '../../src/';
|
||||
import './GanttTaskTypes.css';
|
||||
|
||||
function GanttTaskTypes(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const data = useMemo(() => getTypedData(), []);
|
||||
const [api, setApi] = useState(null);
|
||||
|
||||
return (
|
||||
<div className="wx-I1glfWSB demo">
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
taskTypes={taskTypes}
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttTaskTypes;
|
||||
30
demos/cases/GanttText.jsx
Normal file
30
demos/cases/GanttText.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useMemo, useRef, useCallback } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import MyTaskContent from '../custom/MyTaskContent.jsx';
|
||||
|
||||
export default function GanttText({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const api = useRef(null);
|
||||
|
||||
const doClick = useCallback((ev) => {
|
||||
api.current.exec('update-task', {
|
||||
id: ev.id,
|
||||
task: {
|
||||
clicked: ev.clicked,
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
ref={api}
|
||||
onCustomClick={doClick}
|
||||
taskTemplate={MyTaskContent}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
);
|
||||
}
|
||||
4
demos/cases/GanttToolbar.css
Normal file
4
demos/cases/GanttToolbar.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.gtcell.wx-2rSWAdWv {
|
||||
height: calc(100% - 50px);
|
||||
border-top: var(--wx-gantt-border);
|
||||
}
|
||||
28
demos/cases/GanttToolbar.jsx
Normal file
28
demos/cases/GanttToolbar.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Toolbar, Editor } from '../../src/';
|
||||
import './GanttToolbar.css';
|
||||
|
||||
export default function GanttToolbar(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [api, setApi] = useState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar api={api} />
|
||||
<div className="wx-2rSWAdWv gtcell">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
init={setApi}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
4
demos/cases/GanttToolbarButtons.css
Normal file
4
demos/cases/GanttToolbarButtons.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.gtcell.wx-vkht5Uhi {
|
||||
height: calc(100% - 50px);
|
||||
border-top: var(--wx-gantt-border);
|
||||
}
|
||||
47
demos/cases/GanttToolbarButtons.jsx
Normal file
47
demos/cases/GanttToolbarButtons.jsx
Normal file
@@ -0,0 +1,47 @@
|
||||
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 './GanttToolbarButtons.css';
|
||||
|
||||
export default function GanttToolbarButtons({ skinSettings }) {
|
||||
const helpers = useContext(context.helpers);
|
||||
const [api, setApi] = useState();
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const actionHandler = useCallback(() => {
|
||||
helpers.showNotice({ text: "'My action' clicked" });
|
||||
}, [helpers]);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const items = defaultToolbarButtons.filter((b) => {
|
||||
return b.id?.indexOf('indent') === -1;
|
||||
});
|
||||
|
||||
items.push({
|
||||
id: 'my-action',
|
||||
comp: 'icon',
|
||||
icon: 'wxi-cat',
|
||||
handler: actionHandler,
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [actionHandler]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar api={api} items={items} />
|
||||
<div className="wx-vkht5Uhi gtcell">
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
init={setApi}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
4
demos/cases/GanttToolbarCustom.css
Normal file
4
demos/cases/GanttToolbarCustom.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.gtcell.wx-7sTOb4gt {
|
||||
height: calc(100% - 50px);
|
||||
border-top: var(--wx-gantt-border);
|
||||
}
|
||||
102
demos/cases/GanttToolbarCustom.jsx
Normal file
102
demos/cases/GanttToolbarCustom.jsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Editor } from '../../src/';
|
||||
import { Toolbar } from '@svar-ui/react-toolbar';
|
||||
import { useStoreLater } from '@svar-ui/lib-react';
|
||||
import './GanttToolbarCustom.css';
|
||||
|
||||
export default function GanttToolbarCustom({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [api, setApi] = useState(null);
|
||||
const selectedValue = useStoreLater(api, "selected");
|
||||
|
||||
function handleAdd() {
|
||||
if (!api) return;
|
||||
api.exec('add-task', {
|
||||
task: {
|
||||
text: 'New task',
|
||||
},
|
||||
target: selectedValue[0],
|
||||
mode: 'after',
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
if (!api) return;
|
||||
const order = getActionOrder(true);
|
||||
order.forEach((id) => api.exec('delete-task', { id }));
|
||||
}
|
||||
|
||||
function handleMove(mode) {
|
||||
if (!api) return;
|
||||
const changeDir = mode === 'down';
|
||||
const order = getActionOrder(changeDir);
|
||||
order.forEach((id) => api.exec('move-task', { id, mode }));
|
||||
}
|
||||
|
||||
function getActionOrder(changeDir) {
|
||||
if (!api) return [];
|
||||
const tasks = selectedValue
|
||||
.map((id) => api.getTask(id))
|
||||
.sort((a, b) => {
|
||||
return a.$level - b.$level || a.$y - b.$y;
|
||||
});
|
||||
const idOrder = tasks.map((o) => o.id);
|
||||
if (changeDir) return idOrder.reverse();
|
||||
return idOrder;
|
||||
}
|
||||
|
||||
const allItems = [
|
||||
{
|
||||
comp: 'button',
|
||||
type: 'primary',
|
||||
text: 'Add task',
|
||||
handler: handleAdd,
|
||||
},
|
||||
{
|
||||
comp: 'button',
|
||||
text: 'Delete task',
|
||||
handler: handleDelete,
|
||||
},
|
||||
{
|
||||
comp: 'button',
|
||||
type: 'primary',
|
||||
text: 'Move task down',
|
||||
handler: () => handleMove('down'),
|
||||
},
|
||||
{
|
||||
comp: 'button',
|
||||
type: 'primary',
|
||||
text: 'Move task up',
|
||||
handler: () => handleMove('up'),
|
||||
},
|
||||
];
|
||||
|
||||
const items = useMemo(() => {
|
||||
if (api && selectedValue) {
|
||||
return selectedValue.length ? allItems : [allItems[0]];
|
||||
}
|
||||
return [allItems[0]];
|
||||
}, [api, selectedValue, allItems]);
|
||||
|
||||
const GanttInitialised = useMemo(() => {
|
||||
return <Gantt
|
||||
{...(skinSettings || {})}
|
||||
init={setApi}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
}, [skinSettings, data.tasks, data.links, data.scales]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar items={items} />
|
||||
<div className="wx-7sTOb4gt gtcell">
|
||||
{GanttInitialised}
|
||||
{api && <Editor api={api} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
23
demos/cases/GanttTooltips.jsx
Normal file
23
demos/cases/GanttTooltips.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, Tooltip } from '../../src/';
|
||||
import MyTooltipContent from '../custom/MyTooltipContent.jsx';
|
||||
|
||||
function GanttTooltips({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
const [api, setApi] = useState(null);
|
||||
|
||||
return (
|
||||
<Tooltip api={api} content={MyTooltipContent}>
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
init={setApi}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttTooltips;
|
||||
12
demos/cases/GanttZoom.css
Normal file
12
demos/cases/GanttZoom.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.demo.wx-HQBKHlAu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gtcell.wx-HQBKHlAu {
|
||||
overflow: hidden;
|
||||
border: var(--wx-gantt-border);
|
||||
height: calc(100% - 32px);
|
||||
}
|
||||
34
demos/cases/GanttZoom.jsx
Normal file
34
demos/cases/GanttZoom.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
import './GanttZoom.css';
|
||||
|
||||
function GanttZoom({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const init = useCallback((api) => {
|
||||
api.on('zoom-scale', () => {
|
||||
console.log('The current zoom level is', api.getState().zoom);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="wx-HQBKHlAu demo">
|
||||
<h4>
|
||||
Point over Gantt chart, then hold Ctrl and use mouse wheel to zoom
|
||||
</h4>
|
||||
<div className="wx-HQBKHlAu gtcell">
|
||||
<Gantt
|
||||
init={init}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
cellWidth={100}
|
||||
zoom
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GanttZoom;
|
||||
27
demos/cases/GridHeaderMenu.css
Normal file
27
demos/cases/GridHeaderMenu.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.rows.wx-DZzpn0qn {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--wx-background);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar.wx-DZzpn0qn {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gtcell.wx-DZzpn0qn {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
border-top: var(--wx-gantt-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
.label.wx-DZzpn0qn {
|
||||
padding-right: 20px;
|
||||
font-size: var(--wx-font-size);
|
||||
}
|
||||
53
demos/cases/GridHeaderMenu.jsx
Normal file
53
demos/cases/GridHeaderMenu.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt, HeaderMenu } from '../../src/';
|
||||
import { RadioButtonGroup } from '@svar-ui/react-core';
|
||||
import './GridHeaderMenu.css';
|
||||
|
||||
function GridHeaderMenu({ skinSettings }) {
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const [api, setApi] = useState(null);
|
||||
|
||||
const [selected, setSelected] = useState('all');
|
||||
const options = [
|
||||
{ id: 'all', label: 'All' },
|
||||
{ id: 'some', label: 'Some' },
|
||||
];
|
||||
const hidable = { start: true, duration: true };
|
||||
|
||||
const columns = useMemo(
|
||||
() => (selected === 'some' ? hidable : null),
|
||||
[selected],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="wx-DZzpn0qn rows">
|
||||
<div className="wx-DZzpn0qn bar">
|
||||
<div>Right-click the grid header and select visible columns</div>
|
||||
<div className="wx-DZzpn0qn bar">
|
||||
<div className="wx-DZzpn0qn label">Columns that can be hidden:</div>
|
||||
<RadioButtonGroup
|
||||
options={options}
|
||||
value={selected}
|
||||
onChange={({ value }) => setSelected(value)}
|
||||
type="inline"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="wx-DZzpn0qn gtcell">
|
||||
<HeaderMenu api={api} columns={columns}>
|
||||
<Gantt
|
||||
init={setApi}
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
/>
|
||||
</HeaderMenu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GridHeaderMenu;
|
||||
51
demos/cases/GridInlineEditors.jsx
Normal file
51
demos/cases/GridInlineEditors.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getData } from '../data';
|
||||
import { Gantt } from '../../src/';
|
||||
|
||||
function GridInlineEditors(props) {
|
||||
const { skinSettings } = props;
|
||||
|
||||
const data = useMemo(() => getData(), []);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'text',
|
||||
header: 'Task name',
|
||||
width: 170,
|
||||
sort: true,
|
||||
editor: 'text',
|
||||
},
|
||||
{
|
||||
id: 'start',
|
||||
header: 'Start date',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
sort: true,
|
||||
editor: 'datepicker',
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
header: 'Duration',
|
||||
width: 80,
|
||||
sort: true,
|
||||
align: 'center',
|
||||
editor: 'text',
|
||||
},
|
||||
{ id: 'add-task', header: 'Add task', width: 50, align: 'center' },
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Gantt
|
||||
{...skinSettings}
|
||||
tasks={data.tasks}
|
||||
links={data.links}
|
||||
scales={data.scales}
|
||||
columns={columns}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default GridInlineEditors;
|
||||
232
demos/common/Index.css
Normal file
232
demos/common/Index.css
Normal file
@@ -0,0 +1,232 @@
|
||||
.wx-demos.layout {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wx-demos.page-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.wx-demos.page-content-header {
|
||||
height: 70px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #ebebeb;
|
||||
}
|
||||
|
||||
.wx-demos.header-title-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.wx-demos.header-actions-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.wx-demos.layout.active {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.wx-demos.sidebar {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.wx-demos.sidebar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
background-color: #fbfbfb;
|
||||
padding: 19px 16px 19px 18px;
|
||||
}
|
||||
|
||||
.wx-demos.box-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.wx-demos.box-title img:hover,
|
||||
.wx-demos.box-title img:focus {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.wx-demos.box-title img:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.wx-demos.box-title .wx-demos.separator {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #ebebeb;
|
||||
}
|
||||
|
||||
.wx-demos.sidebar.active {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.wx-demos.sidebar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #ebebeb;
|
||||
overflow-y: auto;
|
||||
font-family: Roboto, Arial, Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
background-color: #fbfbfb;
|
||||
border-bottom: 1px solid #ebebeb;
|
||||
}
|
||||
|
||||
.wx-demos.btn-box button.toggle-btn {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
border: 1px solid #ebebeb;
|
||||
color: #42454d;
|
||||
}
|
||||
|
||||
.wx-demos.btn-box button.toggle-btn:hover,
|
||||
.wx-demos.btn-box button.toggle-btn:focus {
|
||||
border: 1px solid #ebebeb;
|
||||
color: #42454d;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.wx-demos.btn-box button.toggle-btn:active {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
.wx-demos a {
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wx-demos.wrapper-content {
|
||||
flex: 1;
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
|
||||
.wx-demos.wrapper-content .wx-demos.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition:
|
||||
transform 0.3s,
|
||||
width 0.3s;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.wx-demos.box-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.wx-demos.hint {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
color: #42454d;
|
||||
}
|
||||
|
||||
.wx-demos.title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
color: #42454d;
|
||||
}
|
||||
|
||||
.wx-demos.segmented-box div.segmented-themes {
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.wx-demos.segmented-box div.segmented-themes button {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
color: #595b66;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.wx-demos.segmented-box div.segmented-themes button.wx-selected {
|
||||
border-radius: 2px;
|
||||
font-weight: 500;
|
||||
color: #42454d;
|
||||
background: #fff;
|
||||
box-shadow: 0px 0px 7px 0px rgba(66, 69, 76, 0.07);
|
||||
}
|
||||
|
||||
.wx-demos.content h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: var(--wx-border);
|
||||
}
|
||||
|
||||
.wx-demos.content h3 {
|
||||
font-size: 18px;
|
||||
margin: 12px 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
.wx-demos.content .demo-box {
|
||||
margin: 20px;
|
||||
}
|
||||
.wx-demos.content .demo-box + .demo-box {
|
||||
margin-top: 40px;
|
||||
}
|
||||
.wx-demos.content .demo-code {
|
||||
font-family: monospace;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.wx-demos.content .demo-status {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.wx-demos.content .demo-toolbar {
|
||||
border: 2px solid var(--wx-background-alt);
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.wx-demos.demo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 37px;
|
||||
font-weight: 400;
|
||||
padding-left: 16px;
|
||||
border-left: 4px solid transparent;
|
||||
color: #595b66;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wx-demos.demo.active {
|
||||
border-left-color: #087a9f;
|
||||
}
|
||||
|
||||
.wx-demos.demo.active,
|
||||
.wx-demos.demo:hover {
|
||||
font-weight: 500;
|
||||
color: #42454d;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
183
demos/common/Index.jsx
Normal file
183
demos/common/Index.jsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { HashRouter, NavLink, useNavigate } from 'react-router-dom';
|
||||
|
||||
import Router from './Router';
|
||||
import { links } from '../routes';
|
||||
import { GitHubLogoIcon, LogoIcon } from '../assets/icons';
|
||||
import './Index.css';
|
||||
|
||||
function DemoExplorerContent({
|
||||
productTag,
|
||||
publicName,
|
||||
skins,
|
||||
Globals,
|
||||
Button,
|
||||
Segmented,
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const [skin, setSkin] = useState(skins[0].id);
|
||||
const [title, setTitle] = useState('');
|
||||
const [githubLink, setGithubLink] = useState('');
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
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];
|
||||
|
||||
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);
|
||||
const parts = currentPath.split('/');
|
||||
if (parts[1]) {
|
||||
navigate(`/${parts[1]}/${value}`);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setShow(!show);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`wx-demos layout ${show ? 'active' : ''}`}>
|
||||
<div
|
||||
className={`wx-demos sidebar ${show ? 'active' : ''}`}
|
||||
role="tabpanel"
|
||||
>
|
||||
<div className="wx-demos sidebar-content">
|
||||
<div className="wx-demos sidebar-header">
|
||||
<div className="wx-demos box-title">
|
||||
<a
|
||||
href="https://svar.dev/react/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img src={LogoIcon} alt="Logo icon" className="box-title-img" />
|
||||
</a>
|
||||
<div className="wx-demos separator"></div>
|
||||
<a
|
||||
href={`https://svar.dev/react/${productTag}/`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h1 className="wx-demos title">React {publicName}</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div className="wx-demos btn-box">
|
||||
<Button
|
||||
type="secondary"
|
||||
icon="wxi-angle-left"
|
||||
css="toggle-btn"
|
||||
onClick={toggleSidebar}
|
||||
></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="wx-demos box-links">
|
||||
{links.map((data) => (
|
||||
<NavLink
|
||||
key={data[0]}
|
||||
to={data[0].replace(':skin', skin)}
|
||||
className={({ isActive }) =>
|
||||
`wx-demos demo ${isActive ? 'active' : ''}`
|
||||
}
|
||||
>
|
||||
{data[1]}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wx-demos page-content">
|
||||
<div className="wx-demos page-content-header">
|
||||
<div className="wx-demos header-title-box">
|
||||
{!show && (
|
||||
<div className="wx-demos btn-box">
|
||||
<Button
|
||||
type="secondary"
|
||||
icon="wxi-angle-right"
|
||||
css="toggle-btn"
|
||||
onClick={toggleSidebar}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="wx-demos hint">{title}</div>
|
||||
</div>
|
||||
<div className="wx-demos header-actions-container">
|
||||
<div className="wx-demos segmented-box">
|
||||
<Segmented
|
||||
value={skin}
|
||||
options={skins}
|
||||
css="segmented-themes"
|
||||
onChange={handleSkinChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="wx-demos btn-box">
|
||||
<a href={githubLink} target="_blank" rel="noopener noreferrer">
|
||||
<Button type="secondary" css="toggle-btn">
|
||||
<img src={GitHubLogoIcon} alt="GitHub icon" />
|
||||
See code on GitHub
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="wx-demos wrapper-content"
|
||||
onClick={() => setShow(false)}
|
||||
role="none"
|
||||
>
|
||||
<div
|
||||
className={`wx-demos content wx-${skin}-theme`}
|
||||
role="none"
|
||||
data-wx-portal-root="true"
|
||||
>
|
||||
<Globals>
|
||||
<Router skin={skin} onRouteChange={handleRouteChange} />
|
||||
</Globals>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DemoExplorer(props) {
|
||||
const skins = props.skins;
|
||||
return (
|
||||
<>
|
||||
{skins.map((skin) => (
|
||||
<skin.Component key={skin.id} />
|
||||
))}
|
||||
<HashRouter>
|
||||
<DemoExplorerContent {...props} />
|
||||
</HashRouter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
31
demos/common/Router.jsx
Normal file
31
demos/common/Router.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
Routes,
|
||||
Route,
|
||||
Navigate,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
} from 'react-router-dom';
|
||||
import { links } from '../routes';
|
||||
|
||||
export default function Router({ onRouteChange }) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (location.pathname === '/') {
|
||||
navigate('/base/willow', { replace: true });
|
||||
} else {
|
||||
onRouteChange(location.pathname);
|
||||
}
|
||||
}, [location.pathname, onRouteChange, navigate]);
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/base/willow" replace />} />
|
||||
{links.map(([path, , Component]) => (
|
||||
<Route key={path} path={path} element={<Component />} />
|
||||
))}
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
9
demos/custom/AddTaskCell.css
Normal file
9
demos/custom/AddTaskCell.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.text.wx-4RuF7aAO {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.text.wx-4RuF7aAO:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--wx-color-link);
|
||||
}
|
||||
15
demos/custom/AddTaskCell.jsx
Normal file
15
demos/custom/AddTaskCell.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { context } from '@svar-ui/react-core';
|
||||
import './AddTaskCell.css';
|
||||
|
||||
export default function AddTaskCell() {
|
||||
const i18n = useContext(context.i18n);
|
||||
const _ = useMemo(() => i18n.getGroup('gantt'), []);
|
||||
const text = useMemo(() => _('Add task'), []);
|
||||
|
||||
return (
|
||||
<div className="wx-4RuF7aAO text" data-action="add-task">
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
demos/custom/AvatarCell.jsx
Normal file
16
demos/custom/AvatarCell.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useMemo } from 'react';
|
||||
import UserStub from './UserStub.jsx';
|
||||
import { users } from '../data';
|
||||
|
||||
function AvatarCell(props) {
|
||||
const { row } = props;
|
||||
|
||||
const user = useMemo(
|
||||
() => users.find((u) => u.id == row.assigned),
|
||||
[row.assigned, users],
|
||||
);
|
||||
|
||||
return <UserStub user={user} />;
|
||||
}
|
||||
|
||||
export default AvatarCell;
|
||||
66
demos/custom/Form.css
Normal file
66
demos/custom/Form.css
Normal file
@@ -0,0 +1,66 @@
|
||||
.backdrop.wx-QkE5vh0y {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
background: var(--wx-modal-backdrop);
|
||||
}
|
||||
|
||||
.modal.wx-QkE5vh0y {
|
||||
position: relative;
|
||||
width: 340px;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
box-shadow:
|
||||
0 4px 4px rgba(0, 0, 0, 0.12),
|
||||
0 0 10px rgba(0, 0, 0, 0.06);
|
||||
background-color: var(--wx-background);
|
||||
font-family: var(--wx-font-family);
|
||||
font-size: var(--wx-font-size);
|
||||
color: var(--wx-color-font);
|
||||
}
|
||||
|
||||
.title.wx-QkE5vh0y {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close.wx-QkE5vh0y {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
transition: color 0.15s ease-in;
|
||||
}
|
||||
|
||||
.close.wx-QkE5vh0y:hover {
|
||||
color: rgb(255, 122, 122);
|
||||
}
|
||||
|
||||
.body.wx-QkE5vh0y {
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
|
||||
.button.wx-QkE5vh0y {
|
||||
padding: 10px;
|
||||
margin: 1.5em 0 0 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
font-family: var(--wx-font-family);
|
||||
font-size: var(--wx-font-size);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button.wx-QkE5vh0y:focus {
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.danger.wx-QkE5vh0y {
|
||||
color: var(--wx-color-danger-font);
|
||||
background-color: var(--wx-color-danger);
|
||||
}
|
||||
141
demos/custom/Form.jsx
Normal file
141
demos/custom/Form.jsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Field,
|
||||
Text,
|
||||
TextArea,
|
||||
Select,
|
||||
Slider,
|
||||
DatePicker,
|
||||
} from '@svar-ui/react-core';
|
||||
import { useWritableProp } from '@svar-ui/lib-react';
|
||||
import './Form.css';
|
||||
|
||||
export default function Form(props) {
|
||||
const [task, setTask] = useWritableProp(props.task);
|
||||
const { taskTypes, onAction } = props;
|
||||
|
||||
const nodeRef = useRef(null);
|
||||
const [left, setLeft] = useState();
|
||||
const [top, setTop] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeRef.current) {
|
||||
setLeft((window.innerWidth - nodeRef.current.offsetWidth) / 2);
|
||||
setTop((window.innerHeight - nodeRef.current.offsetHeight) / 2);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function deleteTask() {
|
||||
onAction && onAction({ action: 'delete-task', data: { id: task.id } });
|
||||
onAction && onAction({ action: 'close-form' });
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
onAction && onAction({ action: 'close-form' });
|
||||
}
|
||||
|
||||
function handleChange({ value }, key) {
|
||||
let t = task;
|
||||
if (key === 'type' && value === 'milestone') {
|
||||
delete t.end;
|
||||
t.duration = 0;
|
||||
} else if (t.start > t.end) {
|
||||
t.start = t.end;
|
||||
t.duration = 1;
|
||||
t.end = 0;
|
||||
}
|
||||
t = {
|
||||
...t,
|
||||
[key]: value,
|
||||
};
|
||||
setTask(t);
|
||||
onAction &&
|
||||
onAction({
|
||||
action: 'update-task',
|
||||
data: { id: t.id, task: t },
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wx-QkE5vh0y backdrop">
|
||||
<div className="wx-QkE5vh0y modal" style={{ left, top }} ref={nodeRef}>
|
||||
<div className="wx-QkE5vh0y header">
|
||||
<h3 className="wx-QkE5vh0y title">Edit task</h3>
|
||||
<i className="wx-QkE5vh0y close wxi-close" onClick={onClose}></i>
|
||||
</div>
|
||||
<div className="wx-QkE5vh0y body">
|
||||
<Field label="Name">
|
||||
{({ id }) => (
|
||||
<Text
|
||||
id={id}
|
||||
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')}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{taskTypes.length > 1 ? (
|
||||
<Field label="Type">
|
||||
{({ id }) => (
|
||||
<Select
|
||||
id={id}
|
||||
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')}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{task.type !== 'milestone' ? (
|
||||
<>
|
||||
<Field label="End date">
|
||||
{({ id }) => (
|
||||
<DatePicker
|
||||
id={id}
|
||||
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')}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<button className="wx-QkE5vh0y button danger" onClick={deleteTask}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
demos/custom/MyTaskContent.css
Normal file
23
demos/custom/MyTaskContent.css
Normal file
@@ -0,0 +1,23 @@
|
||||
button.wx-BzRGIq8x {
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
font: var(--wx-gantt-bar-font);
|
||||
}
|
||||
|
||||
.text-right.wx-BzRGIq8x {
|
||||
left: 100%;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.text-left.wx-BzRGIq8x {
|
||||
right: 100%;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.text-right.wx-BzRGIq8x,
|
||||
.text-left.wx-BzRGIq8x,
|
||||
button.wx-BzRGIq8x {
|
||||
padding: 0 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
35
demos/custom/MyTaskContent.jsx
Normal file
35
demos/custom/MyTaskContent.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import './MyTaskContent.css';
|
||||
|
||||
function MyTaskContent({ data, onAction }) {
|
||||
function doClick(ev) {
|
||||
ev.stopPropagation();
|
||||
onAction({
|
||||
action: 'custom-click',
|
||||
data: {
|
||||
clicked: !data.clicked,
|
||||
id: data.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.type !== 'milestone' ? (
|
||||
<>
|
||||
<div className="wx-BzRGIq8x wx-text-out text-right">
|
||||
{data.text || ''}
|
||||
</div>
|
||||
<button className="wx-BzRGIq8x" onClick={doClick}>
|
||||
{data.clicked ? 'Was clicked' : 'Click Me'}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div className="wx-BzRGIq8x wx-text-out text-left">
|
||||
{data.text || ''}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyTaskContent;
|
||||
21
demos/custom/MyTooltipContent.css
Normal file
21
demos/custom/MyTooltipContent.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.data.wx-SfydHtKO {
|
||||
white-space: nowrap;
|
||||
background-color: var(--wx-tooltip-background);
|
||||
padding: 3px 8px;
|
||||
}
|
||||
|
||||
.text.wx-SfydHtKO {
|
||||
font-family: var(--wx-font-family);
|
||||
color: var(--wx-color-primary-font);
|
||||
font-size: 13px;
|
||||
text-transform: capitalize;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.text:last-child.wx-SfydHtKO {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.caption.wx-SfydHtKO {
|
||||
font-weight: 700;
|
||||
}
|
||||
32
demos/custom/MyTooltipContent.jsx
Normal file
32
demos/custom/MyTooltipContent.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { format } from 'date-fns';
|
||||
import './MyTooltipContent.css';
|
||||
|
||||
function MyTooltipContent(props) {
|
||||
const { data } = props;
|
||||
|
||||
const mask = 'yyyy.MM.dd';
|
||||
return (
|
||||
<>
|
||||
{data ? (
|
||||
<div className="wx-SfydHtKO data">
|
||||
<div className="wx-SfydHtKO text">
|
||||
<span className="wx-SfydHtKO caption">{data.type}: </span>
|
||||
{data.text}
|
||||
</div>
|
||||
<div className="wx-SfydHtKO text">
|
||||
<span className="wx-SfydHtKO caption">start: </span>
|
||||
{format(data.start, mask)}
|
||||
</div>
|
||||
{data.end ? (
|
||||
<div className="wx-SfydHtKO text">
|
||||
<span className="wx-SfydHtKO caption">end: </span>
|
||||
{format(data.end, mask)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyTooltipContent;
|
||||
10
demos/custom/NameAndDateCell.css
Normal file
10
demos/custom/NameAndDateCell.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.text.wx-4qJ64Gcp,
|
||||
.date.wx-4qJ64Gcp {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.date.wx-4qJ64Gcp {
|
||||
color: #5f5f5f;
|
||||
font-size: 12px;
|
||||
}
|
||||
13
demos/custom/NameAndDateCell.jsx
Normal file
13
demos/custom/NameAndDateCell.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useMemo } from 'react';
|
||||
import './NameAndDateCell.css';
|
||||
|
||||
export default function NameAndDateCell({ row }) {
|
||||
const date = useMemo(() => row.start.toLocaleDateString(), [row.start]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="wx-4qJ64Gcp text">{row.text}</div>
|
||||
<div className="wx-4qJ64Gcp date">{date}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
25
demos/custom/UserStub.css
Normal file
25
demos/custom/UserStub.css
Normal file
@@ -0,0 +1,25 @@
|
||||
.container.wx-Md6hA5Em {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.avatar.wx-Md6hA5Em {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.user-avatar.wx-Md6hA5Em {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background-color: #dfe2e6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-photo.wx-Md6hA5Em {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: #dfe2e6;
|
||||
}
|
||||
27
demos/custom/UserStub.jsx
Normal file
27
demos/custom/UserStub.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useMemo } from 'react';
|
||||
import './UserStub.css';
|
||||
|
||||
function UserStub(props) {
|
||||
const { user } = props;
|
||||
|
||||
const url = 'https://svar.dev/demos/grid/assets/avatars/';
|
||||
const avatar = useMemo(
|
||||
() => (user ? `${url}${user.label.replace(' ', '_')}.png` : ''),
|
||||
[user],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="wx-Md6hA5Em container">
|
||||
<div className="wx-Md6hA5Em avatar">
|
||||
{avatar ? (
|
||||
<div className="wx-Md6hA5Em user-avatar">
|
||||
<img className="wx-Md6hA5Em user-photo" alt="" src={avatar} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div>{user?.label ?? ''}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserStub;
|
||||
20
demos/custom/UsersCustomCombo.jsx
Normal file
20
demos/custom/UsersCustomCombo.jsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Combo } from '@svar-ui/react-core';
|
||||
import UserStub from './UserStub.jsx';
|
||||
|
||||
function UsersCustomCombo(props) {
|
||||
const { value, options, onChange } = props;
|
||||
|
||||
return (
|
||||
<Combo
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
clear
|
||||
placeholder="Assign to the person"
|
||||
>
|
||||
{({ option }) => <UserStub user={option} />}
|
||||
</Combo>
|
||||
);
|
||||
}
|
||||
|
||||
export default UsersCustomCombo;
|
||||
638
demos/data.js
Normal file
638
demos/data.js
Normal file
@@ -0,0 +1,638 @@
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export const weekScaleTemplate = (a, b) => {
|
||||
return `${a.getMonth()} - ${b.getMonth()}`;
|
||||
};
|
||||
|
||||
export const weekScaleAltTemplate = (a, b) => {
|
||||
return `${format(a, 'MMM d')} - ${format(b, 'MMM d')}`;
|
||||
};
|
||||
|
||||
export const dayStyle = (a) => {
|
||||
const day = a.getDay() == 5 || a.getDay() == 6;
|
||||
return day ? 'sday' : '';
|
||||
};
|
||||
|
||||
export const complexScales = [
|
||||
{ unit: 'year', step: 1, format: 'yyyy' },
|
||||
{ unit: 'month', step: 2, format: 'MMMM yyy' },
|
||||
{ unit: 'week', step: 1, format: 'w' },
|
||||
{ unit: 'day', step: 1, format: 'd', css: dayStyle },
|
||||
];
|
||||
|
||||
export const bigScales = [
|
||||
{ unit: 'year', step: 1, format: 'yyyy' },
|
||||
{ unit: 'quarter', step: 1, format: 'QQQQ yyyy' },
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyy' },
|
||||
{ unit: 'week', step: 1, format: weekScaleAltTemplate },
|
||||
];
|
||||
|
||||
const generatedLinks = [
|
||||
{ id: 1, source: 3, target: 4, type: 'e2s' },
|
||||
{ id: 2, source: 1, target: 2, type: 'e2s' },
|
||||
{ id: 24, source: 1, target: 13, type: 's2s' },
|
||||
{ id: 22, source: 1, target: 6, type: 's2s' },
|
||||
{ id: 23, source: 1, target: 3, type: 's2s' },
|
||||
{ id: 21, source: 8, target: 1, type: 's2s' },
|
||||
{ id: 25, source: 1, target: 14, type: 's2s' },
|
||||
{ id: 26, source: 1, target: 15, type: 's2s' },
|
||||
{ id: 27, source: 1, target: 16, type: 's2s' },
|
||||
{ id: 28, source: 1, target: 14, type: 's2s' },
|
||||
{ id: 3, source: 5, target: 6, type: 's2e' },
|
||||
{ id: 4, source: 8, target: 6, type: 's2s' },
|
||||
];
|
||||
|
||||
function getTasks(prefix, maxSize, maxYears) {
|
||||
maxYears = maxYears || 100;
|
||||
maxSize = maxSize || 50;
|
||||
prefix = prefix || '';
|
||||
const tasks = [];
|
||||
for (let i = 1; i <= maxSize; i++) {
|
||||
const ii = i % (365 * maxYears);
|
||||
|
||||
let start = 2 + ii - (ii >= 13 ? 12 : 0);
|
||||
let end = start + 1 + Math.round(Math.random() * 2);
|
||||
tasks.push({
|
||||
id: i,
|
||||
start: new Date(2020, 2, start),
|
||||
end: new Date(2020, 2, end),
|
||||
text: prefix + 'Task ' + i,
|
||||
progress: Math.round((100 * i) / maxSize),
|
||||
parent: 0,
|
||||
type: 'task',
|
||||
});
|
||||
}
|
||||
|
||||
tasks[3].parent = 3;
|
||||
tasks[4].parent = 3;
|
||||
tasks[5].parent = 3;
|
||||
tasks[6].parent = 6;
|
||||
tasks[7].parent = 6;
|
||||
tasks[8].parent = 6;
|
||||
tasks[9].parent = 9;
|
||||
tasks[10].parent = 9;
|
||||
tasks[11].parent = 9;
|
||||
|
||||
tasks[2].type = 'summary';
|
||||
tasks[5].type = 'summary';
|
||||
tasks[8].type = 'summary';
|
||||
tasks[11].type = 'summary';
|
||||
tasks[15].type = 'milestone';
|
||||
delete tasks[15].end;
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
const tasks = [
|
||||
{
|
||||
id: 1,
|
||||
start: new Date(2024, 3, 2),
|
||||
end: new Date(2024, 3, 17),
|
||||
text: 'Project planning',
|
||||
progress: 30,
|
||||
parent: 0,
|
||||
type: 'summary',
|
||||
open: true,
|
||||
details: "Outline the project's scope and resources.",
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
start: new Date(2024, 3, 2),
|
||||
end: new Date(2024, 3, 5),
|
||||
text: 'Marketing analysis',
|
||||
progress: 100,
|
||||
parent: 1,
|
||||
type: 'task',
|
||||
details: 'Analyze market trends and competitors.',
|
||||
assigned: 1,
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
start: new Date(2024, 3, 5),
|
||||
end: new Date(2024, 3, 7),
|
||||
text: 'Discussions',
|
||||
progress: 100,
|
||||
parent: 1,
|
||||
type: 'task',
|
||||
details: 'Team discussions on project strategies.',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 110,
|
||||
start: new Date(2024, 3, 6),
|
||||
end: new Date(2024, 3, 9),
|
||||
text: 'Initial design',
|
||||
progress: 60,
|
||||
parent: 11,
|
||||
type: 'task',
|
||||
details: 'Draft initial design concepts.',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 111,
|
||||
start: new Date(2024, 3, 9),
|
||||
text: 'Presentation',
|
||||
progress: 0,
|
||||
parent: 11,
|
||||
type: 'milestone',
|
||||
details: 'Present initial designs to stakeholders.',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 112,
|
||||
start: new Date(2024, 3, 7),
|
||||
end: new Date(2024, 3, 12),
|
||||
text: 'Prototyping',
|
||||
progress: 10,
|
||||
parent: 11,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 113,
|
||||
start: new Date(2024, 3, 8),
|
||||
end: new Date(2024, 3, 17),
|
||||
text: 'User testing',
|
||||
progress: 0,
|
||||
parent: 11,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
|
||||
{
|
||||
id: 12,
|
||||
start: new Date(2024, 3, 8),
|
||||
text: 'Approval of strategy',
|
||||
progress: 100,
|
||||
parent: 1,
|
||||
type: 'milestone',
|
||||
assigned: '',
|
||||
},
|
||||
|
||||
{
|
||||
id: 2,
|
||||
start: new Date(2024, 3, 2),
|
||||
end: new Date(2024, 3, 12),
|
||||
text: 'Project management',
|
||||
progress: 10,
|
||||
parent: 0,
|
||||
type: 'summary',
|
||||
open: true,
|
||||
assigned: 2,
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
start: new Date(2024, 3, 2),
|
||||
end: new Date(2024, 3, 6),
|
||||
text: 'Resource planning',
|
||||
progress: 10,
|
||||
parent: 2,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
start: new Date(2024, 3, 6),
|
||||
end: new Date(2024, 3, 8),
|
||||
text: 'Getting approval',
|
||||
progress: 10,
|
||||
parent: 2,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
start: new Date(2024, 3, 8),
|
||||
end: new Date(2024, 3, 10),
|
||||
text: 'Team introduction',
|
||||
progress: 0,
|
||||
parent: 2,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
start: new Date(2024, 3, 10),
|
||||
end: new Date(2024, 3, 12),
|
||||
text: 'Resource management',
|
||||
progress: 10,
|
||||
parent: 2,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
|
||||
{
|
||||
id: 3,
|
||||
start: new Date(2024, 3, 9),
|
||||
end: new Date(2024, 4, 15),
|
||||
text: 'Development',
|
||||
progress: 30,
|
||||
parent: 0,
|
||||
type: 'summary',
|
||||
open: true,
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 30,
|
||||
start: new Date(2024, 3, 9),
|
||||
end: new Date(2024, 3, 15),
|
||||
text: 'Prototyping',
|
||||
progress: 3,
|
||||
parent: 3,
|
||||
type: 'task',
|
||||
assigned: 3,
|
||||
},
|
||||
{
|
||||
id: 31,
|
||||
start: new Date(2024, 3, 15),
|
||||
end: new Date(2024, 3, 30),
|
||||
text: 'Basic functionality',
|
||||
progress: 0,
|
||||
parent: 3,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 32,
|
||||
start: new Date(2024, 3, 30),
|
||||
end: new Date(2024, 4, 15),
|
||||
text: 'Finalizing MVA',
|
||||
progress: 0,
|
||||
parent: 3,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
|
||||
{
|
||||
id: 4,
|
||||
start: new Date(2024, 3, 9),
|
||||
end: new Date(2024, 4, 25),
|
||||
text: 'Testing',
|
||||
progress: 3,
|
||||
parent: 0,
|
||||
type: 'summary',
|
||||
open: true,
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 40,
|
||||
start: new Date(2024, 3, 9),
|
||||
end: new Date(2024, 3, 15),
|
||||
text: 'Testing prototype',
|
||||
progress: 3,
|
||||
parent: 4,
|
||||
type: 'task',
|
||||
assigned: 4,
|
||||
},
|
||||
{
|
||||
id: 41,
|
||||
start: new Date(2024, 3, 15),
|
||||
end: new Date(2024, 3, 30),
|
||||
text: 'Testing basic features',
|
||||
progress: 0,
|
||||
parent: 4,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 42,
|
||||
start: new Date(2024, 3, 30),
|
||||
end: new Date(2024, 4, 15),
|
||||
text: 'Testing MVA',
|
||||
progress: 0,
|
||||
parent: 4,
|
||||
type: 'task',
|
||||
assigned: '',
|
||||
},
|
||||
{
|
||||
id: 43,
|
||||
start: new Date(2024, 4, 15),
|
||||
end: new Date(2024, 4, 25),
|
||||
text: 'Beta testing',
|
||||
progress: 0,
|
||||
parent: 4,
|
||||
type: 'task',
|
||||
details:
|
||||
'Comprehensive testing of the beta version before the final release.',
|
||||
assigned: '',
|
||||
},
|
||||
|
||||
{
|
||||
id: 5,
|
||||
start: new Date(2024, 4, 25),
|
||||
text: 'Release 1.0.0',
|
||||
progress: 0,
|
||||
parent: 0,
|
||||
type: 'milestone',
|
||||
details: 'Official release of version 1.0.0 to the public.',
|
||||
assigned: '',
|
||||
},
|
||||
];
|
||||
const links = [
|
||||
{
|
||||
id: 1,
|
||||
source: 10,
|
||||
target: 11,
|
||||
type: 'e2s',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
source: 11,
|
||||
target: 12,
|
||||
type: 'e2s',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
source: 110,
|
||||
target: 111,
|
||||
type: 'e2s',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
source: 20,
|
||||
target: 21,
|
||||
type: 'e2s',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
source: 21,
|
||||
target: 22,
|
||||
type: 'e2s',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
source: 22,
|
||||
target: 23,
|
||||
type: 'e2s',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
source: 42,
|
||||
target: 5,
|
||||
type: 'e2s',
|
||||
},
|
||||
];
|
||||
|
||||
const scales = [
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyy' },
|
||||
{ unit: 'day', step: 1, format: 'd', css: dayStyle },
|
||||
];
|
||||
|
||||
export function getGeneratedData(prefix, maxSize, maxYears) {
|
||||
const tasks = getTasks(prefix, maxSize, maxYears);
|
||||
return { tasks, generatedLinks, scales };
|
||||
}
|
||||
|
||||
const tasksHour = [
|
||||
{
|
||||
id: 1,
|
||||
start: new Date(2025, 3, 10, 8, 0),
|
||||
end: new Date(2025, 3, 10, 20, 0),
|
||||
text: 'Preparing Venue',
|
||||
progress: 90,
|
||||
parent: 0,
|
||||
type: 'summary',
|
||||
open: true,
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
start: new Date(2025, 3, 10, 8, 0),
|
||||
end: new Date(2025, 3, 10, 12, 0),
|
||||
text: 'Stage Setup',
|
||||
progress: 100,
|
||||
parent: 1,
|
||||
type: 'task',
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
start: new Date(2025, 3, 10, 12, 0),
|
||||
end: new Date(2025, 3, 10, 17, 0),
|
||||
text: 'Technical Setup',
|
||||
progress: 100,
|
||||
parent: 1,
|
||||
type: 'task',
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
start: new Date(2025, 3, 10, 14, 0),
|
||||
end: new Date(2025, 3, 10, 20, 0),
|
||||
text: 'Decoration',
|
||||
progress: 85,
|
||||
parent: 1,
|
||||
type: 'task',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
start: new Date(2025, 3, 10, 16, 0),
|
||||
end: new Date(2025, 3, 10, 22, 0),
|
||||
text: 'Rehearsal Session',
|
||||
progress: 60,
|
||||
parent: 0,
|
||||
type: 'summary',
|
||||
open: true,
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
start: new Date(2025, 3, 10, 16, 0),
|
||||
end: new Date(2025, 3, 10, 19, 0),
|
||||
text: 'Speakers Rehearsal',
|
||||
progress: 100,
|
||||
parent: 2,
|
||||
type: 'task',
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
start: new Date(2025, 3, 10, 19, 0),
|
||||
text: 'Sound Check',
|
||||
progress: 0,
|
||||
parent: 2,
|
||||
type: 'milestone',
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
start: new Date(2025, 3, 10, 19, 0),
|
||||
end: new Date(2025, 3, 10, 22, 0),
|
||||
text: 'Full Run-through',
|
||||
progress: 40,
|
||||
parent: 2,
|
||||
type: 'task',
|
||||
},
|
||||
|
||||
{
|
||||
id: 3,
|
||||
start: new Date(2025, 3, 11, 8, 0),
|
||||
end: new Date(2025, 3, 11, 21, 0),
|
||||
text: 'Conference Day',
|
||||
progress: 0,
|
||||
parent: 0,
|
||||
type: 'summary',
|
||||
open: true,
|
||||
},
|
||||
{
|
||||
id: 31,
|
||||
start: new Date(2025, 3, 11, 8, 0),
|
||||
end: new Date(2025, 3, 11, 12, 0),
|
||||
text: 'Catering setup',
|
||||
progress: 0,
|
||||
parent: 3,
|
||||
type: 'task',
|
||||
},
|
||||
{
|
||||
id: 32,
|
||||
start: new Date(2025, 3, 11, 10, 0),
|
||||
end: new Date(2025, 3, 11, 12, 0),
|
||||
text: 'Tech Check',
|
||||
progress: 0,
|
||||
parent: 3,
|
||||
type: 'task',
|
||||
},
|
||||
{
|
||||
id: 33,
|
||||
start: new Date(2025, 3, 11, 12, 0),
|
||||
end: new Date(2025, 3, 11, 16, 0),
|
||||
text: 'Registration',
|
||||
progress: 0,
|
||||
parent: 3,
|
||||
type: 'task',
|
||||
},
|
||||
{
|
||||
id: 34,
|
||||
start: new Date(2025, 3, 11, 16, 0),
|
||||
end: new Date(2025, 3, 11, 20, 0),
|
||||
text: 'Main Conference',
|
||||
progress: 0,
|
||||
parent: 3,
|
||||
type: 'task',
|
||||
},
|
||||
{
|
||||
id: 35,
|
||||
start: new Date(2025, 3, 11, 20, 0),
|
||||
text: 'Closing Ceremony',
|
||||
progress: 0,
|
||||
parent: 3,
|
||||
type: 'milestone',
|
||||
},
|
||||
];
|
||||
|
||||
const linksHour = [
|
||||
{ id: 1, source: 11, target: 12, type: 'e2s' },
|
||||
{ id: 2, source: 21, target: 22, type: 'e2s' },
|
||||
{ id: 3, source: 22, target: 23, type: 'e2s' },
|
||||
{ id: 4, source: 32, target: 33, type: 'e2s' },
|
||||
{ id: 5, source: 33, target: 34, type: 'e2s' },
|
||||
{ id: 6, source: 34, target: 35, type: 'e2s' },
|
||||
];
|
||||
|
||||
const scalesHour = [
|
||||
{ unit: 'day', step: 1, format: 'MMM d' },
|
||||
{ unit: 'hour', step: 1, format: 'HH:mm' },
|
||||
];
|
||||
|
||||
export const users = [
|
||||
{ id: 1, label: 'Laura Turner' },
|
||||
{ id: 2, label: 'Robert Williams' },
|
||||
{ id: 3, label: 'Mary Johnson' },
|
||||
{ id: 4, label: 'John Doe' },
|
||||
];
|
||||
|
||||
const datasets = {
|
||||
day: { tasks, links, scales },
|
||||
hour: { tasks: tasksHour, links: linksHour, scales: scalesHour },
|
||||
};
|
||||
|
||||
export function getData(name) {
|
||||
name = name || 'day';
|
||||
return datasets[name];
|
||||
}
|
||||
|
||||
export function getBaselinesData() {
|
||||
const t = tasks.map((t) => ({
|
||||
...t,
|
||||
base_start: t.start,
|
||||
base_end: t.end,
|
||||
}));
|
||||
|
||||
return { tasks: t, links, scales };
|
||||
}
|
||||
|
||||
export const taskTypes = [
|
||||
{ id: 'task', label: 'Task' },
|
||||
{ id: 'summary', label: 'Summary task' },
|
||||
{ id: 'milestone', label: 'Milestone' },
|
||||
{ id: 'urgent', label: 'Urgent' },
|
||||
{ id: 'narrow', label: 'Narrow' },
|
||||
{ id: 'progress', label: 'Progress' },
|
||||
{ id: 'round', label: 'Rounded' },
|
||||
];
|
||||
|
||||
export function getTypedData() {
|
||||
const t = tasks.map((task, i) => {
|
||||
const res = { ...task };
|
||||
if (res.type == 'task' && i % 3) {
|
||||
res.type = taskTypes[(i % 5) + 2].id;
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
return { tasks: t, links, scales };
|
||||
}
|
||||
|
||||
function hoursTemplate(a, b) {
|
||||
return `${format(a, 'HH:mm')} - ${format(b, 'HH:mm')}`;
|
||||
}
|
||||
export const zoomConfig = {
|
||||
level: 3,
|
||||
levels: [
|
||||
{
|
||||
minCellWidth: 200,
|
||||
maxCellWidth: 400,
|
||||
scales: [{ unit: 'year', step: 1, format: 'yyyy' }],
|
||||
},
|
||||
{
|
||||
minCellWidth: 150,
|
||||
maxCellWidth: 400,
|
||||
scales: [
|
||||
{ unit: 'year', step: 1, format: 'yyyy' },
|
||||
{ unit: 'quarter', step: 1, format: 'QQQQ' },
|
||||
],
|
||||
},
|
||||
{
|
||||
minCellWidth: 250,
|
||||
maxCellWidth: 350,
|
||||
scales: [
|
||||
{ unit: 'quarter', step: 1, format: 'QQQQ' },
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyy' },
|
||||
],
|
||||
},
|
||||
{
|
||||
minCellWidth: 100,
|
||||
scales: [
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyy' },
|
||||
{ unit: 'week', step: 1, format: "'week' w" },
|
||||
],
|
||||
},
|
||||
{
|
||||
maxCellWidth: 200,
|
||||
scales: [
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyy' },
|
||||
{ unit: 'day', step: 1, format: 'd', css: dayStyle },
|
||||
],
|
||||
},
|
||||
{
|
||||
minCellWidth: 25,
|
||||
maxCellWidth: 100,
|
||||
scales: [
|
||||
{ unit: 'day', step: 1, format: 'MMM d', css: dayStyle },
|
||||
{ unit: 'hour', step: 6, format: hoursTemplate },
|
||||
],
|
||||
},
|
||||
{
|
||||
maxCellWidth: 120,
|
||||
scales: [
|
||||
{ unit: 'day', step: 1, format: 'MMM d', css: dayStyle },
|
||||
{ unit: 'hour', step: 1, format: 'HH:mm' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
34
demos/index.jsx
Normal file
34
demos/index.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './common/Index.jsx';
|
||||
|
||||
import { Globals, Button, Segmented } from '@svar-ui/react-core';
|
||||
|
||||
import Willow from '../src/themes/Willow.jsx';
|
||||
import WillowDark from '../src/themes/WillowDark.jsx';
|
||||
|
||||
import '@svar-ui/react-core/style.css';
|
||||
import '@svar-ui/react-grid/style.css';
|
||||
import '@svar-ui/react-editor/style.css';
|
||||
import '@svar-ui/react-menu/style.css';
|
||||
import '@svar-ui/react-toolbar/style.css';
|
||||
import '@svar-ui/react-comments/style.css';
|
||||
import '@svar-ui/react-tasklist/style.css';
|
||||
|
||||
const skins = [
|
||||
{ id: 'willow', label: 'Willow', Component: Willow },
|
||||
{ id: 'willow-dark', label: 'Dark', Component: WillowDark },
|
||||
];
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App
|
||||
publicName="Gantt"
|
||||
skins={skins}
|
||||
productTag="gantt"
|
||||
Globals={Globals}
|
||||
Button={Button}
|
||||
Segmented={Segmented}
|
||||
/>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
251
demos/routes.js
Normal file
251
demos/routes.js
Normal file
@@ -0,0 +1,251 @@
|
||||
import BasicInit from './cases/BasicInit.jsx';
|
||||
import GanttProvider from './cases/GanttProvider.jsx';
|
||||
import GanttBatchProvider from './cases/GanttBatchProvider.jsx';
|
||||
import GanttBackend from './cases/GanttBackend.jsx';
|
||||
import GanttScales from './cases/GanttScales.jsx';
|
||||
import GanttGrid from './cases/GanttGrid.jsx';
|
||||
import GanttNoGrid from './cases/GanttNoGrid.jsx';
|
||||
import GanttFixedColumns from './cases/GanttFixedColumns.jsx';
|
||||
import GanttFlexColumns from './cases/GanttFlexColumns.jsx';
|
||||
import GanttReadOnly from './cases/GanttReadOnly.jsx';
|
||||
import GanttPreventActions from './cases/GanttPreventActions.jsx';
|
||||
import GanttForm from './cases/GanttForm.jsx';
|
||||
import GanttSizes from './cases/GanttSizes.jsx';
|
||||
import GanttMultiple from './cases/GanttMultiple.jsx';
|
||||
import GanttPerformance from './cases/GanttPerformance.jsx';
|
||||
|
||||
|
||||
import GanttTooltips from './cases/GanttTooltips.jsx';
|
||||
import GanttToolbar from './cases/GanttToolbar.jsx';
|
||||
import GanttToolbarCustom from './cases/GanttToolbarCustom.jsx';
|
||||
import GanttToolbarButtons from './cases/GanttToolbarButtons.jsx';
|
||||
import GanttText from './cases/GanttText.jsx';
|
||||
import GanttLocale from './cases/GanttLocale.jsx';
|
||||
import GanttStartEnd from './cases/GanttStartEnd.jsx';
|
||||
import GanttFullscreen from './cases/GanttFullscreen.jsx';
|
||||
import GanttZoom from './cases/GanttZoom.jsx';
|
||||
import GanttCustomZoom from './cases/GanttCustomZoom.jsx';
|
||||
import GanttLengthUnit from './cases/GanttLengthUnit.jsx';
|
||||
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 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 GanttEditor from './cases/GanttEditor.jsx';
|
||||
import GanttEditorConfig from './cases/GanttEditorConfig.jsx';
|
||||
import GanttEditorCustomControls from './cases/GanttEditorCustomControls.jsx';
|
||||
import GanttEditorComments from './cases/GanttEditorComments.jsx';
|
||||
import GanttEditorTasks from './cases/GanttEditorTasks.jsx';
|
||||
import GanttScaleUnit from './cases/GanttScaleUnit.jsx';
|
||||
import GanttDurationUnitHour from './cases/GanttDurationUnitHour.jsx';
|
||||
import GanttDurationUnitChanges from './cases/GanttDurationUnitChanges.jsx';
|
||||
import GanttMinScaleUnit from './cases/GanttMinScaleUnit.jsx';
|
||||
import HeaderMenu from './cases/GridHeaderMenu.jsx';
|
||||
import GridInlineEditors from './cases/GridInlineEditors.jsx';
|
||||
import GanttEditorReadonly from './cases/GanttEditorReadonly.jsx';
|
||||
import GanttEditorValidation from './cases/GanttEditorValidation.jsx';
|
||||
|
||||
export const links = [
|
||||
['/base/:skin', 'Basic Gantt', BasicInit, 'BasicInit'],
|
||||
|
||||
['/sizes/:skin', 'Scale / cell sizes', GanttSizes, 'GanttSizes'],
|
||||
[
|
||||
'/cell-borders/:skin',
|
||||
'Chart cell borders',
|
||||
ChartCellBorders,
|
||||
'ChartBorders',
|
||||
],
|
||||
['/scales/:skin', 'Custom scales', GanttScales, 'GanttScales'],
|
||||
['/start-end/:skin', 'Start/end dates', GanttStartEnd, 'GanttStartEnd'],
|
||||
[
|
||||
'/custom-scale/:skin',
|
||||
'Custom scale unit',
|
||||
GanttScaleUnit,
|
||||
'GanttScaleUnit',
|
||||
],
|
||||
[
|
||||
'/custom-min-scale/:skin',
|
||||
'Custom minimal scale unit',
|
||||
GanttMinScaleUnit,
|
||||
'GanttMinScaleUnit',
|
||||
],
|
||||
|
||||
['/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'],
|
||||
[
|
||||
'/length-unit/:skin',
|
||||
'Length unit (rounding)',
|
||||
GanttLengthUnit,
|
||||
'GanttLengthUnit',
|
||||
],
|
||||
[
|
||||
'/duration-unit/:skin',
|
||||
'Duration unit: hour',
|
||||
GanttDurationUnitHour,
|
||||
'GanttDurationUnitHour',
|
||||
],
|
||||
[
|
||||
'/duration-changes/:skin',
|
||||
'Duration unit: changes',
|
||||
GanttDurationUnitChanges,
|
||||
'GanttDurationUnitChanges',
|
||||
],
|
||||
['/no-grid/:skin', 'No grid', GanttNoGrid, 'GanttNoGrid'],
|
||||
[
|
||||
'/grid-fill-space-columns/:skin',
|
||||
'Flexible grid columns',
|
||||
GanttFlexColumns,
|
||||
'GanttFlexColumns',
|
||||
],
|
||||
[
|
||||
'/grid-fixed-columns/:skin',
|
||||
'Fixed grid columns',
|
||||
GanttFixedColumns,
|
||||
'GanttFixedColumns',
|
||||
],
|
||||
['/grid-custom-columns/:skin', 'Custom grid columns', GanttGrid, 'GanttGrid'],
|
||||
[
|
||||
'/grid-inline-editors/:skin',
|
||||
'Grid inline editors',
|
||||
GridInlineEditors,
|
||||
'GridInlineEditors',
|
||||
],
|
||||
|
||||
['/toolbar/:skin', 'Toolbar', GanttToolbar, 'GanttToolbar'],
|
||||
[
|
||||
'/toolbar-buttons/:skin',
|
||||
'Toolbar: limited buttons',
|
||||
GanttToolbarButtons,
|
||||
'GanttToolbarButtons',
|
||||
],
|
||||
[
|
||||
'/toolbar-custom/:skin',
|
||||
'Toolbar: custom buttons',
|
||||
GanttToolbarCustom,
|
||||
'GanttToolbarCustom',
|
||||
],
|
||||
['/context-menu/:skin', 'Context menu', ContextMenu, 'ContextMenu'],
|
||||
[
|
||||
'/menu-handler/:skin',
|
||||
'Context menu: limiting options',
|
||||
ContextMenuHandler,
|
||||
'ContextMenuHandler',
|
||||
],
|
||||
//["/outer-menu/:skin", "Dropdown menu", DropDownMenu, "DropDownMenu"],
|
||||
[
|
||||
'/menu-options/:skin',
|
||||
'Context menu: custom options',
|
||||
ContextMenuOptions,
|
||||
'ContextMenuOptions',
|
||||
],
|
||||
[
|
||||
'/header-menu/:skin',
|
||||
'Header menu: hiding columns',
|
||||
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',
|
||||
GanttMultiple,
|
||||
'GanttMultiple',
|
||||
],
|
||||
['/performance/:skin', 'Performance', GanttPerformance, 'GanttPerformance'],
|
||||
|
||||
['/sorting/:skin', 'Custom sorting', GanttSort, 'GanttSort'],
|
||||
['/sorting-api/:skin', 'Sort by API', GanttCustomSort, 'GanttCustomSort'],
|
||||
|
||||
['/backend/:skin', 'Backend data', GanttBackend, 'GanttBackend'],
|
||||
[
|
||||
'/backend-provider/:skin',
|
||||
'Saving to backend',
|
||||
GanttProvider,
|
||||
'GanttProvider',
|
||||
],
|
||||
[
|
||||
'/backend-provider-batch/:skin',
|
||||
'Saving to backend (batch)',
|
||||
GanttBatchProvider,
|
||||
'GanttBatchProvider',
|
||||
],
|
||||
['/editor/:skin', 'Editor', GanttEditor, 'GanttEditor'],
|
||||
[
|
||||
'/editor-config/:skin',
|
||||
'Editor: custom settings',
|
||||
GanttEditorConfig,
|
||||
'GanttEditorConfig',
|
||||
],
|
||||
[
|
||||
'/editor-custom-controls/:skin',
|
||||
'Editor: custom controls',
|
||||
GanttEditorCustomControls,
|
||||
'GanttEditorCustomControls',
|
||||
],
|
||||
[
|
||||
'/editor-comments/:skin',
|
||||
'Editor: custom comments',
|
||||
GanttEditorComments,
|
||||
'GanttEditorComments',
|
||||
],
|
||||
[
|
||||
'/editor-tasks/:skin',
|
||||
'Editor: custom tasks',
|
||||
GanttEditorTasks,
|
||||
'GanttEditorTasks',
|
||||
],
|
||||
[
|
||||
'/editor-readonly/:skin',
|
||||
'Editor: readonly',
|
||||
GanttEditorReadonly,
|
||||
'GanttEditorReadonly',
|
||||
],
|
||||
[
|
||||
'/editor-validation/:skin',
|
||||
'Editor: validation',
|
||||
GanttEditorValidation,
|
||||
'GanttEditorValidation',
|
||||
],
|
||||
];
|
||||
12
demos/skins.js
Normal file
12
demos/skins.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export const skins = [
|
||||
{
|
||||
id: 'willow',
|
||||
name: 'Willow',
|
||||
props: {},
|
||||
},
|
||||
{
|
||||
id: 'willow-dark',
|
||||
name: 'Dark',
|
||||
props: {},
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user