This commit is contained in:
Marta Kowalska
2025-10-22 08:13:59 +00:00
commit 80ca059fb8
162 changed files with 16816 additions and 0 deletions
+16
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File
@@ -0,0 +1,4 @@
.wx-22L1T8uz {
width: 100%;
height: 100%;
}
+36
View 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
View 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
View 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
View File
@@ -0,0 +1,4 @@
.wrapper.wx-0moLF6Ul {
height: 100%;
overflow: hidden;
}
+50
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File
@@ -0,0 +1,3 @@
.gt-cell > .wx-gantt .wx-summary .wx-progress-marker {
display: none;
}
+134
View 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
View 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
View 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
View 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
View File
@@ -0,0 +1,4 @@
.gtcell.wx-2rSWAdWv {
height: calc(100% - 50px);
border-top: var(--wx-gantt-border);
}
+28
View 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
View File
@@ -0,0 +1,4 @@
.gtcell.wx-vkht5Uhi {
height: calc(100% - 50px);
border-top: var(--wx-gantt-border);
}
+47
View 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
View File
@@ -0,0 +1,4 @@
.gtcell.wx-7sTOb4gt {
height: calc(100% - 50px);
border-top: var(--wx-gantt-border);
}
+102
View 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
View 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
View 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
View 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
View 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
View 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
View 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;