@object-ui/plugin-gantt 17.5.0 → 17.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3259 -0
- package/README.md +637 -137
- package/dist/GanttView.d.ts +65 -55
- package/dist/GanttView.d.ts.map +1 -1
- package/dist/ObjectGantt.d.ts +193 -6
- package/dist/ObjectGantt.d.ts.map +1 -1
- package/dist/QuickFilterBar.d.ts +16 -4
- package/dist/QuickFilterBar.d.ts.map +1 -1
- package/dist/ResourceWorkload.d.ts +2 -2
- package/dist/ResourceWorkload.d.ts.map +1 -1
- package/dist/index.d.ts +55 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1974 -1686
- package/dist/index.umd.cjs +12 -12
- package/dist/scheduling.d.ts +17 -9
- package/dist/scheduling.d.ts.map +1 -1
- package/dist/shifts.d.ts +16 -13
- package/dist/shifts.d.ts.map +1 -1
- package/dist/tzShift.d.ts +55 -0
- package/dist/tzShift.d.ts.map +1 -0
- package/dist/useGanttTranslation.d.ts.map +1 -1
- package/dist/workload.d.ts +1 -1
- package/package.json +17 -14
- package/dist/tsconfig.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Gantt chart plugin for Object UI - Visualize project timelines and task dependen
|
|
|
19
19
|
### Create / Edit / Delete / View
|
|
20
20
|
|
|
21
21
|
When used through `ObjectGantt` (the wiring the framework uses for the
|
|
22
|
-
`gantt` view type) the full CRUD lifecycle is wired automatically:
|
|
22
|
+
`gantt` stored view type) the full CRUD lifecycle is wired automatically:
|
|
23
23
|
|
|
24
24
|
- **Create** — click the toolbar "+ New Task" button. A small dialog opens
|
|
25
25
|
pre-filled with start/end (today → +7 days). On submit the component calls
|
|
@@ -44,33 +44,80 @@ When used through `ObjectGantt` (the wiring the framework uses for the
|
|
|
44
44
|
fetched by `DetailView` itself when `dataSource.getObjectSchema` is
|
|
45
45
|
available).
|
|
46
46
|
|
|
47
|
-
Override
|
|
48
|
-
`{ mode
|
|
49
|
-
|
|
47
|
+
Override it with `navigation` in the `properties` bag — the spec's
|
|
48
|
+
`NavigationConfig`, `{ mode, size, openNewTab, preventNavigation }`. What a
|
|
49
|
+
task click does with each member, measured through the real `SchemaRenderer`
|
|
50
|
+
(objectui#11168 slice 4):
|
|
51
|
+
|
|
52
|
+
- **absent** — the drawer above, this renderer's own default;
|
|
53
|
+
- `drawer`, `modal`, `popover` — the task's record in that overlay; `split` —
|
|
54
|
+
the record beside the chart, which stays drawn;
|
|
55
|
+
- `page`, and a block written without `mode` (the spec's `page` default) —
|
|
56
|
+
the record page in the same tab; `new_window` — the same page in a new tab;
|
|
57
|
+
- `none` — nothing; `preventNavigation: true` — nothing, whatever the mode;
|
|
58
|
+
`openNewTab: true` — the record page in a new tab, outranking every mode
|
|
59
|
+
except `none`; `size` — the overlay width.
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{ "type": "object-gantt", "properties": { "objectName": "project_task", "gantt": { "startDateField": "start_date", "endDateField": "end_date", "titleField": "name" }, "navigation": { "mode": "page" } } }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The record-page address is the gantt's own. It derives it from the page it
|
|
66
|
+
is on and does not use a record navigator the host publishes
|
|
67
|
+
(objectui#11293), so mounting one changes nothing. On the object's own list
|
|
68
|
+
or view route (`…/project_task/view/all`) the address is the record page
|
|
69
|
+
`…/project_task/record/ID`; on any other page, such as a custom page, the
|
|
70
|
+
gantt appends `/project_task/record/ID` to the current address. The object is
|
|
71
|
+
`data.object` when `data` is the object provider, else `objectName`; on
|
|
72
|
+
inline rows that name neither, no mode opens anything, the default drawer
|
|
73
|
+
included.
|
|
74
|
+
|
|
75
|
+
*Which* detail layout opens is not authorable here: assign a `record` page
|
|
76
|
+
to the object and let `isDefault` pick the one that opens. Page assignment
|
|
77
|
+
is what resolves a detail layout; this block only decides **how** that
|
|
78
|
+
detail is surfaced. `navigation`'s schema refuses any key it does not
|
|
79
|
+
declare: an undeclared key rejects the whole config, so the `mode` beside it
|
|
80
|
+
never takes effect either. `@objectstack/spec`'s `NavigationConfigSchema`
|
|
81
|
+
owns the member list.
|
|
50
82
|
|
|
51
83
|
|
|
52
84
|
### Drag-and-drop rescheduling
|
|
53
85
|
|
|
54
86
|
When the renderer is used through `ObjectGantt` (the standard wiring used by
|
|
55
|
-
the framework's `gantt` view type) drag is enabled automatically: each bar
|
|
87
|
+
the framework's `gantt` stored view type) drag is enabled automatically: each bar
|
|
56
88
|
shows a grab cursor; the body drags the entire task, and the two thin edge
|
|
57
89
|
zones (≈6px) resize start or end. Pointer motion snaps to whole days using
|
|
58
|
-
the current column width.
|
|
59
|
-
|
|
90
|
+
the current column width. A move shifts the start and the end by the same
|
|
91
|
+
number of calendar days and keeps each one's time of day, so across a
|
|
92
|
+
daylight-saving change a day task keeps its length in days and a 10:00 start
|
|
93
|
+
stays at 10:00; dragging a summary moves every task beneath it the same way.
|
|
94
|
+
With shift bands configured (`timeSegments`), a day-view drag moves by bands
|
|
95
|
+
instead and keeps the bar's elapsed length. On release `ObjectGantt` issues an
|
|
96
|
+
optimistic local patch and a `dataSource.update(objectName, recordId, { [startDateField]: …,
|
|
60
97
|
[endDateField]: … })`. If the request fails the local state is reverted.
|
|
61
98
|
|
|
62
99
|
When you embed the lower-level `<GanttView>` directly, pass `onTaskUpdate`
|
|
63
100
|
to opt in:
|
|
64
101
|
|
|
65
102
|
```tsx
|
|
66
|
-
|
|
103
|
+
import { GanttView } from '@object-ui/plugin-gantt';
|
|
104
|
+
import type { GanttTask } from '@object-ui/plugin-gantt';
|
|
105
|
+
|
|
106
|
+
// `tasks` is whatever you loaded; `save` stands in for your own write path.
|
|
107
|
+
declare const tasks: GanttTask[];
|
|
108
|
+
declare const save: (id: GanttTask['id'], changes: { start: Date; end: Date }) => void;
|
|
109
|
+
|
|
110
|
+
const editableChart = <GanttView
|
|
67
111
|
tasks={tasks}
|
|
68
112
|
onTaskUpdate={(task, { start, end }) => {
|
|
69
|
-
//
|
|
70
|
-
//
|
|
113
|
+
// `changes` is a Partial<Pick<GanttTask, 'title' | 'start' | 'end' | 'progress'>>:
|
|
114
|
+
// only the keys the edit actually touched are present. A bar drag/resize
|
|
115
|
+
// sends start + end, but the progress grip sends just { progress } — so
|
|
116
|
+
// guard, or a progress drag writes both dates back as undefined.
|
|
117
|
+
if (!start || !end) return;
|
|
71
118
|
save(task.id, { start, end });
|
|
72
119
|
}}
|
|
73
|
-
|
|
120
|
+
/>;
|
|
74
121
|
```
|
|
75
122
|
|
|
76
123
|
## Installation
|
|
@@ -87,30 +134,116 @@ pnpm add @object-ui/plugin-gantt
|
|
|
87
134
|
// In your app entry point (e.g., App.tsx or main.tsx)
|
|
88
135
|
import '@object-ui/plugin-gantt';
|
|
89
136
|
|
|
90
|
-
// Now you can use gantt types in your schemas
|
|
137
|
+
// Now you can use gantt types in your schemas.
|
|
138
|
+
// The gantt is RECORD-DRIVEN: it names a data source and the fields to read.
|
|
139
|
+
// It does not take a task array — see "Schema API" below. Its props go in the
|
|
140
|
+
// node's `properties` bag, and its field mapping in the bag's `gantt` block.
|
|
91
141
|
const schema = {
|
|
92
|
-
type: 'gantt',
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
142
|
+
type: 'object-gantt',
|
|
143
|
+
properties: {
|
|
144
|
+
objectName: 'project_tasks',
|
|
145
|
+
gantt: {
|
|
146
|
+
titleField: 'name',
|
|
147
|
+
startDateField: 'start_date',
|
|
148
|
+
endDateField: 'end_date',
|
|
149
|
+
progressField: 'completion_percentage'
|
|
100
150
|
}
|
|
101
|
-
|
|
151
|
+
}
|
|
102
152
|
};
|
|
103
153
|
```
|
|
104
154
|
|
|
105
|
-
###
|
|
155
|
+
### What the side-effect import registers
|
|
156
|
+
|
|
157
|
+
Registration is *only* a side effect of importing the package — the single
|
|
158
|
+
`import '@object-ui/plugin-gantt'` above is the whole of it. There is no
|
|
159
|
+
components map to iterate over: importing the entry point runs the
|
|
160
|
+
`ComponentRegistry.register(...)` calls at the bottom of `src/index.tsx`, which
|
|
161
|
+
claim these schema types:
|
|
162
|
+
|
|
163
|
+
| Schema `type` | Namespaced key | Renderer |
|
|
164
|
+
| --- | --- | --- |
|
|
165
|
+
| `object-gantt` | `plugin-gantt:object-gantt` | `ObjectGanttRenderer` |
|
|
166
|
+
|
|
167
|
+
Both spellings of the surviving key resolve — `register` stores the namespaced key
|
|
168
|
+
*and* a bare-`type` fallback. It declares four inputs: `objectName`, the `gantt`
|
|
169
|
+
configuration object, and the two other record sources, `data` and `staticData`.
|
|
170
|
+
`objectName` is not a required input: the record source is one of `data`,
|
|
171
|
+
`staticData` and `objectName` (read in that order), and the `object-gantt` schema
|
|
172
|
+
refuses a block that declares none of them, unless the node's `dataSource.object`
|
|
173
|
+
names the object, which the registration then lands on `objectName`. `data` is a
|
|
174
|
+
`{ provider, … }` data-source configuration, never a bare array; inline rows go
|
|
175
|
+
under `staticData`.
|
|
176
|
+
|
|
177
|
+
> **The bare `gantt` key is retired** (objectui#8008, ruled 2026-09-09). This
|
|
178
|
+
> table used to carry a second row, `gantt` / `view:gantt`, on the same renderer.
|
|
179
|
+
> The registry accepted both spellings while the published declaration admitted
|
|
180
|
+
> only one — `ObjectGanttSchema.type` is the literal `'object-gantt'` — so an
|
|
181
|
+
> author who annotated their node could not write the key the registry took.
|
|
182
|
+
> `object-gantt` is now the one spelling.
|
|
183
|
+
>
|
|
184
|
+
> ⚠️ The **stored view type** `"gantt"` — what a saved `listViews[].type` holds —
|
|
185
|
+
> is a **different layer and is unchanged**. `ObjectView` maps a stored `gantt`
|
|
186
|
+
> view onto the `object-gantt` node type, so no saved view moves.
|
|
187
|
+
|
|
188
|
+
`ObjectGanttRenderer` is a thin wrapper: it pulls `dataSource` off the renderer
|
|
189
|
+
context and hands the schema to `ObjectGantt`.
|
|
190
|
+
|
|
191
|
+
### Public exports
|
|
192
|
+
|
|
193
|
+
The package exports components, helpers and their types — not a registry map:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import {
|
|
197
|
+
ObjectGantt, // ObjectQL-integrated gantt: loads records, writes back edits
|
|
198
|
+
ObjectGanttRenderer, // the registered renderer for `object-gantt`
|
|
199
|
+
GanttView, // the standalone timeline component
|
|
200
|
+
QuickFilterBar, // the toolbar's quick-filter dropdowns
|
|
201
|
+
ResourceWorkload, // resource × period workload grid
|
|
202
|
+
normalizeDependencies, // raw dependency-field value -> GanttDependency[]
|
|
203
|
+
normalizeTaskType, // raw type-field value -> GanttTaskType
|
|
204
|
+
normalizeShiftSegments, // raw timeSegments config -> NormShiftSegments
|
|
205
|
+
parseHHMM,
|
|
206
|
+
shiftDayStart,
|
|
207
|
+
bandAt,
|
|
208
|
+
computeWorkload,
|
|
209
|
+
} from '@object-ui/plugin-gantt';
|
|
210
|
+
|
|
211
|
+
import type {
|
|
212
|
+
ObjectGanttProps,
|
|
213
|
+
GanttViewProps,
|
|
214
|
+
GanttTask,
|
|
215
|
+
GanttTaskType,
|
|
216
|
+
GanttViewMode,
|
|
217
|
+
GanttDependency,
|
|
218
|
+
GanttDependencyObject,
|
|
219
|
+
GanttLinkType,
|
|
220
|
+
GanttMarker,
|
|
221
|
+
QuickFilterDef,
|
|
222
|
+
QuickFilterBarProps,
|
|
223
|
+
QuickFilterField,
|
|
224
|
+
QuickFilterOption,
|
|
225
|
+
QuickFilterLabels,
|
|
226
|
+
ShiftSegmentsConfig,
|
|
227
|
+
ShiftBandConfig,
|
|
228
|
+
NormShiftSegments,
|
|
229
|
+
NormShiftBand,
|
|
230
|
+
ResourceWorkloadProps,
|
|
231
|
+
WorkloadColumn,
|
|
232
|
+
WorkloadOptions,
|
|
233
|
+
ResourceCell,
|
|
234
|
+
ResourceLoad,
|
|
235
|
+
} from '@object-ui/plugin-gantt';
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
To serve the gantt under a registry key of your own, register the exported
|
|
239
|
+
renderer under that key:
|
|
106
240
|
|
|
107
241
|
```typescript
|
|
108
|
-
import { ganttComponents } from '@object-ui/plugin-gantt';
|
|
109
242
|
import { ComponentRegistry } from '@object-ui/core';
|
|
243
|
+
import { ObjectGanttRenderer } from '@object-ui/plugin-gantt';
|
|
110
244
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
ComponentRegistry.register(type, component);
|
|
245
|
+
ComponentRegistry.register('my-gantt', ObjectGanttRenderer, {
|
|
246
|
+
namespace: 'my-app',
|
|
114
247
|
});
|
|
115
248
|
```
|
|
116
249
|
|
|
@@ -118,111 +251,378 @@ Object.entries(ganttComponents).forEach(([type, component]) => {
|
|
|
118
251
|
|
|
119
252
|
### Gantt Chart
|
|
120
253
|
|
|
121
|
-
|
|
254
|
+
The gantt node is **record-driven**: it names *where records come from* and
|
|
255
|
+
*which fields* carry the schedule. It does not carry a task array — the task
|
|
256
|
+
objects further down this page are the component's runtime shape, produced by
|
|
257
|
+
`ObjectGantt` from each record.
|
|
258
|
+
|
|
259
|
+
An authored node takes its props in its `properties` bag, whose members are
|
|
260
|
+
`@objectstack/spec`'s `ComponentPropsMap['object-gantt']` row, and its field
|
|
261
|
+
mapping in the bag's `gantt` block. `objectui validate` judges the bag against
|
|
262
|
+
that row and refuses a prop written flat on the node by name, naming where it
|
|
263
|
+
goes (`Did you mean objectName → properties.objectName?`, or
|
|
264
|
+
`startDateField → properties.gantt.startDateField`), as the spec's own page
|
|
265
|
+
component does (objectui#10859). `SchemaRenderer` hoists the bag onto the node
|
|
266
|
+
before `ObjectGantt` runs, so the reads below are the same for both: the
|
|
267
|
+
renderer sees the hoisted node.
|
|
268
|
+
|
|
269
|
+
`ObjectGantt` decides what to render from exactly two reads
|
|
270
|
+
(`src/ObjectGantt.tsx`):
|
|
271
|
+
|
|
272
|
+
**1. Where the records come from — `getDataConfig`.** The first of these three
|
|
273
|
+
that is present wins; if none is present there is no data config at all and the
|
|
274
|
+
chart renders empty:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const recordSource = {
|
|
278
|
+
type: 'object-gantt',
|
|
279
|
+
properties: {
|
|
280
|
+
// Pick ONE of the three:
|
|
281
|
+
objectName: 'project_tasks', // load through the host DataSource
|
|
282
|
+
// data: { provider: 'value', items: [ /* records */ ] }, // inline records
|
|
283
|
+
// staticData: [ /* records */ ], // shorthand for the above
|
|
284
|
+
},
|
|
285
|
+
// …or name the object in the node's `dataSource` binding instead of `objectName`:
|
|
286
|
+
// dataSource: { object: 'project_tasks' },
|
|
287
|
+
};
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`data` is the spec's `ViewData` union. This chart reads three of its arms:
|
|
291
|
+
`{ provider: 'object', object }`, `{ provider: 'value', items }` and
|
|
292
|
+
`{ provider: 'api', read, write }`. The fourth, `{ provider: 'schema', schemaId }`,
|
|
293
|
+
is accepted by the spec and is not a source this chart reads: measured, a gantt
|
|
294
|
+
carrying it queries `objectName` instead.
|
|
295
|
+
|
|
296
|
+
**The provider does not change which query keys apply.** An authored `filter`
|
|
297
|
+
and `sort` narrow and order the rows on **every** provider, inline ones
|
|
298
|
+
included, and the platform row ceiling (2,000 drawn rows, with a footnote
|
|
299
|
+
naming both numbers) applies to all of them — inline rows cost the browser what
|
|
300
|
+
fetched rows cost. Inline rows go through the same in-memory adapter the other
|
|
301
|
+
providers go through, so `filter` is evaluated with the same matcher and the
|
|
302
|
+
ceiling is applied to the **filtered** set, never to the raw one. Before
|
|
303
|
+
objectui#8769 the inline provider skipped that query and drew every authored
|
|
304
|
+
row with an authored `filter` silently dropped.
|
|
305
|
+
|
|
306
|
+
The same holds for the full-text pair: `search` is sent as `$search`, and
|
|
307
|
+
`searchableFields` as `$searchFields` alongside it (never without a term). A
|
|
308
|
+
list view's toolbar Search box writes both onto the gantt node it composes — the
|
|
309
|
+
chart runs its own query, so the node is the only way the term reaches it. They
|
|
310
|
+
are not authored keys: the spec's row does not declare them, so `objectui
|
|
311
|
+
validate` refuses them in the bag.
|
|
312
|
+
|
|
313
|
+
**2. How the fields map — `getGanttConfig`.** Two spellings, checked in order.
|
|
314
|
+
The **`gantt` block wins whenever it is present**, and it is taken WHOLE — the
|
|
315
|
+
flat top-level keys are not merged into it. The flat spelling is read only when
|
|
316
|
+
there is no `gantt` block, and then only when `startDateField` and
|
|
317
|
+
`endDateField` are both present. A node carrying both spellings renders the
|
|
318
|
+
block's values and gets a dev-mode warning naming the ignored top-level keys.
|
|
319
|
+
**Author the `gantt` block**: the flat spelling is the runtime handoff
|
|
320
|
+
`ObjectView` and `ListView` write onto the node they compose, and an authored
|
|
321
|
+
node cannot carry it — `objectui validate` refuses each flat key and names
|
|
322
|
+
`properties.gantt.KEY`, and the spec's row refuses it inside the bag too.
|
|
323
|
+
|
|
324
|
+
Precedence follows the maintainer ruling on objectui#5018 (2026-08-17), which
|
|
325
|
+
settled the identical two-faces shape for `plugin-map`; objectui#6469 inherited
|
|
326
|
+
it here. Before that flip the flat branch returned first, so an authored `gantt`
|
|
327
|
+
block was discarded silently.
|
|
122
328
|
|
|
123
329
|
```typescript
|
|
330
|
+
const fieldMapping = {
|
|
331
|
+
type: 'object-gantt',
|
|
332
|
+
properties: {
|
|
333
|
+
objectName: 'project_tasks',
|
|
334
|
+
gantt: {
|
|
335
|
+
startDateField: 'start_date',
|
|
336
|
+
endDateField: 'end_date',
|
|
337
|
+
titleField: 'name',
|
|
338
|
+
progressField: 'completion_percentage',
|
|
339
|
+
dependenciesField: 'dependent_task_ids',
|
|
340
|
+
colorField: 'bar_color',
|
|
341
|
+
parentField: 'parent_task',
|
|
342
|
+
typeField: 'task_kind',
|
|
343
|
+
viewMode: 'week', // 'day'|'week'|'month'|'quarter'|'year'
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
`viewMode` is real authoring surface (a member of the spec's `GanttConfigSchema`,
|
|
350
|
+
so of the `gantt` block) and is honoured by **both** renderer branches — the
|
|
351
|
+
timeline and the resource-workload grid. It reaches the renderer through
|
|
352
|
+
`getGanttConfig`, so it only takes effect alongside a taken gantt config.
|
|
353
|
+
Omitting it is meaningful — a persisted layout then seeds the granularity before
|
|
354
|
+
the renderer's `'day'` fallback.
|
|
355
|
+
|
|
356
|
+
#### Keys this page used to teach that the renderer never reads
|
|
357
|
+
|
|
358
|
+
Earlier revisions of this README showed a task-array schema. Those keys have no
|
|
359
|
+
read site anywhere in `src/` — a schema built from them renders an **empty
|
|
360
|
+
chart with no diagnostic**, because `type: 'object-gantt'` *is* a registered type, so
|
|
361
|
+
the node mounts and simply finds nothing to draw:
|
|
362
|
+
|
|
363
|
+
| Key shown before | Status | Use instead |
|
|
364
|
+
| --- | --- | --- |
|
|
365
|
+
| `tasks` | never read off the schema | `objectName` / `data` / `staticData` |
|
|
366
|
+
| `object` | never read | `objectName` |
|
|
367
|
+
| `nameField` | never read | `titleField` |
|
|
368
|
+
| `startField` | never read | `startDateField` |
|
|
369
|
+
| `endField` | never read | `endDateField` |
|
|
370
|
+
| `fields: { name, start, end, … }` | never read | the `gantt` block |
|
|
371
|
+
| `onTaskClick`, `onTaskUpdate` | never read *off the schema* | React props on `<ObjectGantt>` / `<GanttView>` — functions do not belong in serializable metadata |
|
|
372
|
+
| `className` | never read off the schema | a React prop on `<ObjectGantt>` |
|
|
373
|
+
|
|
374
|
+
The keys the renderer reads beside `gantt` — `readOnly`, `mobileReadOnly`,
|
|
375
|
+
`markers`, `navigation`, `skipWeekends`, `holidays`, `criticalPath`,
|
|
376
|
+
`showBaselines`, `persistLayout` / `viewName` and `label` — are all optional,
|
|
377
|
+
all declared by the spec's `object-gantt` row, and all published by the
|
|
378
|
+
registration since objectui#11168 slice 4. Each is described in the table
|
|
379
|
+
below.
|
|
380
|
+
|
|
381
|
+
#### Full field-mapping reference (`GanttConfig` members)
|
|
382
|
+
|
|
383
|
+
`titleField` / `startDateField` / `endDateField` are required; everything below
|
|
384
|
+
is optional. Set them INSIDE the `gantt` block — beside a `gantt` block a
|
|
385
|
+
top-level copy is ignored (with a dev-mode warning naming the losing keys), and
|
|
386
|
+
on an authored node `objectui validate` refuses it.
|
|
387
|
+
|
|
388
|
+
| Key (under `gantt`) | Effect |
|
|
389
|
+
| --- | --- |
|
|
390
|
+
| `parentField` | Builds the summary tree: parents roll up their children's span and weighted progress. |
|
|
391
|
+
| `typeField` | Distinguishes `task` / `summary` (aliases `project`, `phase`) / `milestone` / `group` (alias `folder`). A `group` row renders as a **pure tree header with no timeline bar** — expandable like a summary but never scheduled, for grouping-only levels that have no dates of their own. `summary` still renders a bar-carrying rollup bracket. |
|
|
392
|
+
| `lockField` | Marks a row **view-only** when the field is truthy: no bar drag/resize, no progress drag, no dependency connector dot, and inline-edit plus context-menu edit/delete are hidden. Clicking still works (open drawer / jump). Independent of `readOnly`, so one level of a tree can be frozen while its siblings stay editable. |
|
|
393
|
+
| `defaultCollapsedDepth` | Auto-collapse every tree node at or below this 0-indexed depth that has children, on first render. Roots are depth 0; the user can still expand any of them, this only seeds the initial state. In a 4-level tree, `2` starts with every level-2 node (and its children) folded. Omit to start fully expanded. |
|
|
394
|
+
| `dependenciesField` | Draws the dependency arrows. Accepts CSV, an id array, or `[{ id, type: 'fs'\|'ss'\|'ff'\|'sf' }]`. Setting it also makes links **editable** unless `readOnly`: drag a bar's connector dot to create an FS link, right-click a link to switch its type or remove it, right-click a bar to add a predecessor or successor. Every change is written back to the field, which is auto-promoted to the `[{ id, type }]` form the moment a non-FS link is stored. |
|
|
395
|
+
| `tooltipFields` | The hover detail. Each entry is a field name or `{ field, label }`, formatted by field type. |
|
|
396
|
+
| `baselineStartField` / `baselineEndField` | Draw a thin planned-vs-actual baseline strip under each bar. |
|
|
397
|
+
| `groupByField` | Swimlane the rows by any field — a select/lookup label or a raw value; empty values fall into an "ungrouped" bucket. |
|
|
398
|
+
| `colorField` | Field whose value picks the bar colour. |
|
|
399
|
+
| `resourceView: true` | Render the **resource / workload view** instead of the task grid: one row per resource with a per-column load histogram. Requires `assigneeField` to bucket tasks. |
|
|
400
|
+
| `assigneeField` / `effortField` / `capacity` | Resource bucketing (required for `resourceView`), per-task workload weight (default `1`), and the per-resource capacity ceiling (default `1`). Any column whose summed load exceeds `capacity` is painted as over-allocated. |
|
|
401
|
+
| `quickFilters: [{ field, label?, options? }]` | Render a quick-filter bar above the grid — one multi-select dropdown per entry, narrowing the visible bars by that field (AND across dimensions). Option lists resolve in priority order: explicit `options` → the object schema's `select`/`enum` options (the full domain) → a `lookup` / `master_detail`'s referenced records (pulled in full through the data source, so values with **no** tasks still appear) → distinct values from the loaded data. Lookup values match on the embedded record id. Selecting every option of a dimension collapses to "no constraint". |
|
|
402
|
+
| `autoZoomToFilter` | When a quick filter narrows the set, re-derive the timeline range from the **remaining** tasks so the axis zooms to the filtered span (default `true`). Set `false` to pin the axis to the full task span, so bars keep their absolute position while filtering. |
|
|
403
|
+
| `viewMode` | Initial granularity, `'day'` (default) / `'week'` / `'month'` / `'quarter'` / `'year'`; the toolbar switches it live. `year` widens the axis to one column per year with a decade band above. |
|
|
404
|
+
|
|
405
|
+
With links present, dragging a bar into a position that violates a dependency
|
|
406
|
+
raises a reschedule confirmation: accepting runs a topological forward pass
|
|
407
|
+
(link-type aware; summaries stay fixed rollups) over the affected tasks,
|
|
408
|
+
declining keeps the manual placement. This is on by default whenever
|
|
409
|
+
`dependenciesField` is set, and suppressed under `readOnly`.
|
|
410
|
+
|
|
411
|
+
#### Node-level display and behaviour options
|
|
412
|
+
|
|
413
|
+
True siblings of `gantt` in the `properties` bag, not `GanttConfig` members.
|
|
414
|
+
|
|
415
|
+
| Key (in `properties`, beside `gantt`) | Effect |
|
|
416
|
+
| --- | --- |
|
|
417
|
+
| `criticalPath: true` | Start with the critical-path (zero-slack chain) highlight on; the toolbar toggle stays available. |
|
|
418
|
+
| `showBaselines: false` | Hide the baseline strips even when baseline fields are mapped (default `true`). |
|
|
419
|
+
| `skipWeekends: true` | Working-calendar math: auto-schedule and critical path count working days only, and reschedules snap off Sat/Sun. In **day mode** this also folds weekend columns out of the timeline — Friday sits against Monday and a one-column drag advances one working day. Coarser scales stay linear. |
|
|
420
|
+
| `holidays: ["yyyy-mm-dd", …]` | Extra non-working days for the working calendar, combined with or instead of `skipWeekends`. In day mode these columns fold out of the axis too. |
|
|
421
|
+
| `markers: [{ date, label?, color? }]` | Extra vertical marker lines, like the Today line. `label` is drawn against the line and `color` paints it (the theme's primary colour when omitted); a date outside the chart's range draws no line. |
|
|
422
|
+
| `persistLayout: false` | Disable layout persistence. By default the toolbar's save-layout button snapshots the current granularity, zoom and task-list collapse state, plus the quick-filter chips, to `localStorage` (key `gantt-layout:OBJECT:VIEW`) and restores it on the next load. `OBJECT` is `objectName`, else `data.object`, and `gantt` on inline rows that name neither. With `false` there is no save-layout button and nothing is stored. |
|
|
423
|
+
| `viewName: "board"` | The `VIEW` half of that key (default `default`). Two gantts on the same object with different names keep separate layouts, and each restores only its own. |
|
|
424
|
+
| `readOnly: true` | **Disable every edit path** — no bar drag/resize/progress, no inline edit, no delete, no dependency-link drag, no reorder, no auto-schedule, and the Undo/Redo buttons are hidden. A read-only badge shows in the toolbar and the right-click menu drops to view-only (or is suppressed when nothing is actionable). Task click and granularity switching still work. Use it for dashboards and shared read-only views. |
|
|
425
|
+
| `mobileReadOnly: false` | When the chart is narrow (under 640 px of its own measured width, or of the viewport until that is measured) it auto-enters read-only, giving touch users a clean scrollable thumbnail — the chart's write paths are gated as under `readOnly`, applied only while narrow. The record drawer is not: it locks under `readOnly` alone, so a task's record still opens writable on a narrow chart. Enabled by default; `false` keeps a narrow chart editable, and `readOnly: true` locks the chart and the drawer at any width. |
|
|
426
|
+
| `label` | Names the exported PNG/PDF file: the link after `gantt.exportFileName` and before the object's own label, then `objectName`. A string, or an inline locale map (`{ "en": …, "zh-CN": … }`) resolved to the display locale. The chart draws it nowhere else. |
|
|
427
|
+
| `navigation` | What a task click opens — see **Create / Edit / Delete / View** above. |
|
|
428
|
+
|
|
429
|
+
The toolbar also carries **navigation** (jump-to-today / this-week / this-month
|
|
430
|
+
buttons that scroll the timeline to the start of that period) and **export**
|
|
431
|
+
(PNG, plus a dependency-free single-page PDF of the whole chart), always
|
|
432
|
+
available regardless of `readOnly`. Each task-list row carries a **locate** icon
|
|
433
|
+
by its End cell that smooth-scrolls the timeline to centre that row's bar and
|
|
434
|
+
pulses it — useful in deep or long trees.
|
|
435
|
+
|
|
436
|
+
#### A maximal node, for reference
|
|
437
|
+
|
|
438
|
+
```json
|
|
124
439
|
{
|
|
125
|
-
type:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
440
|
+
"type": "object-gantt",
|
|
441
|
+
"properties": {
|
|
442
|
+
"objectName": "project_task",
|
|
443
|
+
"gantt": {
|
|
444
|
+
"titleField": "name",
|
|
445
|
+
"startDateField": "start_date",
|
|
446
|
+
"endDateField": "end_date",
|
|
447
|
+
"progressField": "progress",
|
|
448
|
+
"parentField": "parent_id",
|
|
449
|
+
"dependenciesField": "depends_on",
|
|
450
|
+
"typeField": "item_type",
|
|
451
|
+
"lockField": "is_locked",
|
|
452
|
+
"defaultCollapsedDepth": 2,
|
|
453
|
+
"colorField": "status",
|
|
454
|
+
"baselineStartField": "planned_start",
|
|
455
|
+
"baselineEndField": "planned_end",
|
|
456
|
+
"tooltipFields": [{ "field": "owner", "label": "Owner" }, "status", "effort"],
|
|
457
|
+
"groupByField": "owner",
|
|
458
|
+
"assigneeField": "owner",
|
|
459
|
+
"effortField": "effort",
|
|
460
|
+
"quickFilters": [
|
|
461
|
+
{ "field": "status", "label": "Status" },
|
|
462
|
+
{ "field": "project", "label": "Project" },
|
|
463
|
+
{ "field": "priority", "label": "Priority", "options": ["high", "medium", "low"] }
|
|
464
|
+
],
|
|
465
|
+
"autoZoomToFilter": true
|
|
466
|
+
},
|
|
467
|
+
"criticalPath": true,
|
|
468
|
+
"skipWeekends": true,
|
|
469
|
+
"holidays": ["2026-01-01", "2026-12-25"],
|
|
470
|
+
"readOnly": false
|
|
471
|
+
},
|
|
472
|
+
"bind": "project_task"
|
|
132
473
|
}
|
|
133
474
|
```
|
|
134
475
|
|
|
135
476
|
### Task Structure
|
|
136
477
|
|
|
478
|
+
`GanttTask` is the **component's runtime** task — what `<GanttView>` renders and
|
|
479
|
+
what `ObjectGantt` produces from each record. Dates are real `Date` objects and
|
|
480
|
+
the label field is `title` (`src/GanttView.tsx`):
|
|
481
|
+
|
|
482
|
+
<!-- readme-exports: partial GanttTask — deliberate excerpt: `fields` and `hasOwnDates` are populated by ObjectGantt itself, as the paragraph below the block says -->
|
|
137
483
|
```typescript
|
|
484
|
+
import type { GanttDependency, GanttTaskType } from '@object-ui/plugin-gantt';
|
|
485
|
+
|
|
138
486
|
interface GanttTask {
|
|
139
|
-
id: string;
|
|
140
|
-
|
|
141
|
-
start:
|
|
142
|
-
end:
|
|
143
|
-
progress: number;
|
|
144
|
-
dependencies?:
|
|
145
|
-
|
|
146
|
-
|
|
487
|
+
id: string | number;
|
|
488
|
+
title: string;
|
|
489
|
+
start: Date;
|
|
490
|
+
end: Date;
|
|
491
|
+
progress: number; // 0-100
|
|
492
|
+
dependencies?: GanttDependency[]; // predecessor id, optionally with a link type
|
|
493
|
+
parent?: string | number | null; // builds the hierarchy; unknown ids render as roots
|
|
494
|
+
type?: GanttTaskType; // 'task' | 'summary' | 'milestone' | 'group'
|
|
495
|
+
color?: string; // bar fill — any CSS color, e.g. '#3b82f6'
|
|
496
|
+
borderColor?: string; // alert outline, fill untouched
|
|
497
|
+
locked?: boolean; // view-only row (still clickable)
|
|
498
|
+
baselineStart?: Date; // planned-vs-actual reference bar
|
|
499
|
+
baselineEnd?: Date;
|
|
500
|
+
data?: any; // the source record behind the row
|
|
147
501
|
}
|
|
148
502
|
```
|
|
149
503
|
|
|
504
|
+
A few more optional fields (`fields` for tooltip rows, `hasOwnDates`) are
|
|
505
|
+
populated by `ObjectGantt` itself — see `GanttTask` in `src/GanttView.tsx` for
|
|
506
|
+
the full declaration.
|
|
507
|
+
|
|
150
508
|
## Examples
|
|
151
509
|
|
|
152
510
|
### Basic Gantt Chart
|
|
153
511
|
|
|
512
|
+
Inline records, no backend — `data.items` carries the records and the `*Field`
|
|
513
|
+
keys of the `gantt` block say which of their fields the chart reads. Note that
|
|
514
|
+
the record field names are yours; only the `*Field` keys are fixed vocabulary.
|
|
515
|
+
|
|
154
516
|
```typescript
|
|
155
517
|
const schema = {
|
|
156
|
-
type: 'gantt',
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
id: '2',
|
|
169
|
-
name: 'Design Phase',
|
|
170
|
-
start: '2024-01-08',
|
|
171
|
-
end: '2024-01-21',
|
|
172
|
-
progress: 75,
|
|
173
|
-
dependencies: ['1'],
|
|
174
|
-
color: 'bg-purple-500'
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
id: '3',
|
|
178
|
-
name: 'Development',
|
|
179
|
-
start: '2024-01-22',
|
|
180
|
-
end: '2024-02-15',
|
|
181
|
-
progress: 30,
|
|
182
|
-
dependencies: ['2'],
|
|
183
|
-
color: 'bg-green-500'
|
|
518
|
+
type: 'object-gantt',
|
|
519
|
+
properties: {
|
|
520
|
+
gantt: {
|
|
521
|
+
viewMode: 'week',
|
|
522
|
+
startDateField: 'start',
|
|
523
|
+
endDateField: 'end',
|
|
524
|
+
titleField: 'name',
|
|
525
|
+
progressField: 'progress',
|
|
526
|
+
dependenciesField: 'dependencies',
|
|
527
|
+
colorField: 'color',
|
|
184
528
|
},
|
|
185
|
-
{
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
529
|
+
data: {
|
|
530
|
+
provider: 'value',
|
|
531
|
+
items: [
|
|
532
|
+
{
|
|
533
|
+
id: '1',
|
|
534
|
+
name: 'Project Planning',
|
|
535
|
+
start: '2024-01-01',
|
|
536
|
+
end: '2024-01-07',
|
|
537
|
+
progress: 100,
|
|
538
|
+
// bar fill goes straight into an inline `backgroundColor` —
|
|
539
|
+
// it must be a CSS color, NOT a Tailwind class
|
|
540
|
+
color: '#3b82f6'
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
id: '2',
|
|
544
|
+
name: 'Design Phase',
|
|
545
|
+
start: '2024-01-08',
|
|
546
|
+
end: '2024-01-21',
|
|
547
|
+
progress: 75,
|
|
548
|
+
dependencies: ['1'],
|
|
549
|
+
color: '#a855f7'
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
id: '3',
|
|
553
|
+
name: 'Development',
|
|
554
|
+
start: '2024-01-22',
|
|
555
|
+
end: '2024-02-15',
|
|
556
|
+
progress: 30,
|
|
557
|
+
dependencies: ['2'],
|
|
558
|
+
color: '#22c55e'
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
id: '4',
|
|
562
|
+
name: 'Testing',
|
|
563
|
+
start: '2024-02-16',
|
|
564
|
+
end: '2024-02-28',
|
|
565
|
+
progress: 0,
|
|
566
|
+
dependencies: ['3'],
|
|
567
|
+
color: '#f97316'
|
|
568
|
+
}
|
|
569
|
+
]
|
|
193
570
|
}
|
|
194
|
-
|
|
571
|
+
}
|
|
195
572
|
};
|
|
196
573
|
```
|
|
197
574
|
|
|
198
575
|
### Interactive Gantt
|
|
199
576
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
577
|
+
Callbacks are **not** schema keys — the schema is serializable metadata, and a
|
|
578
|
+
function cannot survive it. Through the registered `gantt` / `object-gantt`
|
|
579
|
+
types the whole CRUD lifecycle is already wired to the host `DataSource`
|
|
580
|
+
(create/edit/drag/delete/detail drawer), so there is usually nothing to pass.
|
|
581
|
+
When you render the component yourself, hand the callbacks in as React props.
|
|
582
|
+
A component mounted without `SchemaRenderer` receives the node as it reads it,
|
|
583
|
+
so its `schema` prop keeps its props on the node: nothing hoists a `properties`
|
|
584
|
+
bag there.
|
|
585
|
+
|
|
586
|
+
```tsx
|
|
587
|
+
import { ObjectGantt } from '@object-ui/plugin-gantt';
|
|
588
|
+
import type { DataSource } from '@object-ui/types';
|
|
589
|
+
|
|
590
|
+
// The adapter your host already talks to — see "Integration with Data
|
|
591
|
+
// Sources" below for where it comes from.
|
|
592
|
+
declare const dataSource: DataSource;
|
|
593
|
+
|
|
594
|
+
const interactiveChart = <ObjectGantt
|
|
595
|
+
schema={{
|
|
596
|
+
type: 'object-gantt',
|
|
597
|
+
objectName: 'project_tasks',
|
|
598
|
+
startDateField: 'start_date',
|
|
599
|
+
endDateField: 'end_date',
|
|
600
|
+
titleField: 'name',
|
|
601
|
+
}}
|
|
602
|
+
dataSource={dataSource}
|
|
603
|
+
onTaskClick={(record) => console.log('Task clicked:', record)}
|
|
604
|
+
/>;
|
|
213
605
|
```
|
|
214
606
|
|
|
607
|
+
To turn editing off from the metadata instead, set `readOnly: true` — in the
|
|
608
|
+
`properties` bag of an authored node, or on the schema you mount — that one *is*
|
|
609
|
+
read.
|
|
610
|
+
|
|
215
611
|
### With ObjectQL Integration
|
|
216
612
|
|
|
217
613
|
```typescript
|
|
218
614
|
const schema = {
|
|
219
615
|
type: 'object-gantt',
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
616
|
+
properties: {
|
|
617
|
+
objectName: 'project_tasks',
|
|
618
|
+
gantt: {
|
|
619
|
+
titleField: 'name',
|
|
620
|
+
startDateField: 'start_date',
|
|
621
|
+
endDateField: 'end_date',
|
|
622
|
+
progressField: 'completion_percentage',
|
|
623
|
+
dependenciesField: 'dependent_task_ids'
|
|
624
|
+
}
|
|
625
|
+
}
|
|
226
626
|
};
|
|
227
627
|
```
|
|
228
628
|
|
|
@@ -234,6 +634,7 @@ The Gantt chart renders one timeline column per unit of the active scale:
|
|
|
234
634
|
- **week** - one column per week (starting Monday)
|
|
235
635
|
- **month** - one column per calendar month
|
|
236
636
|
- **quarter** - one column per quarter (Q1–Q4)
|
|
637
|
+
- **year** - one column per year (decade band above)
|
|
237
638
|
|
|
238
639
|
A two-row header shows the grouping above the units (months above days/weeks,
|
|
239
640
|
years above months/quarters). The toolbar's segmented control switches scales
|
|
@@ -243,11 +644,36 @@ Drag snapping follows the active scale: bars snap to days in day view, weeks
|
|
|
243
644
|
in week view, and whole calendar months/quarters (duration preserved) in the
|
|
244
645
|
coarse views.
|
|
245
646
|
|
|
647
|
+
### The toolbar period label and its steppers
|
|
648
|
+
|
|
649
|
+
The label between the `‹` / `›` buttons names the period **currently on screen**,
|
|
650
|
+
not the extent of the data: it reads the date at the left edge of the viewport
|
|
651
|
+
and snaps it to the same tier the band header groups by — a month under day and
|
|
652
|
+
week view, a year under month and quarter view, a decade under year view, and
|
|
653
|
+
the shift-day under shift-segmented day view. So the label and the band header
|
|
654
|
+
directly beneath it always name the same period, and the label moves as the
|
|
655
|
+
chart is scrolled.
|
|
656
|
+
|
|
657
|
+
`‹` / `›` step the visible window by one of those periods (one month in day and
|
|
658
|
+
week view, one year in month and quarter view, a decade in year view), clamped
|
|
659
|
+
to the ends of the timeline. They step the *label's* tier rather than a single
|
|
660
|
+
column, so one click always changes what the label says.
|
|
661
|
+
|
|
662
|
+
Set the initial scale with `viewMode`, a member of the `gantt` block, beside
|
|
663
|
+
the field mapping:
|
|
664
|
+
|
|
246
665
|
```typescript
|
|
247
666
|
const schema = {
|
|
248
|
-
type: 'gantt',
|
|
249
|
-
|
|
250
|
-
|
|
667
|
+
type: 'object-gantt',
|
|
668
|
+
properties: {
|
|
669
|
+
objectName: 'project_tasks',
|
|
670
|
+
gantt: {
|
|
671
|
+
viewMode: 'month',
|
|
672
|
+
startDateField: 'start_date',
|
|
673
|
+
endDateField: 'end_date',
|
|
674
|
+
titleField: 'name'
|
|
675
|
+
}
|
|
676
|
+
}
|
|
251
677
|
};
|
|
252
678
|
```
|
|
253
679
|
|
|
@@ -266,12 +692,19 @@ Zero-duration tasks (`end <= start`) — or tasks whose `type` is
|
|
|
266
692
|
can be dragged to move but not resized; dependency arrows anchor at the
|
|
267
693
|
diamond center.
|
|
268
694
|
|
|
695
|
+
These are runtime `GanttTask` objects (what `<GanttView>` takes directly) —
|
|
696
|
+
dates are real `Date`s and the label field is `title`. Coming from records
|
|
697
|
+
instead, the same tree is configured with `parentField` / `typeField`.
|
|
698
|
+
|
|
269
699
|
```typescript
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
{ id: '
|
|
700
|
+
import type { GanttTask } from '@object-ui/plugin-gantt';
|
|
701
|
+
|
|
702
|
+
const tasks: GanttTask[] = [
|
|
703
|
+
// summary (has children) — its span and progress are rolled up
|
|
704
|
+
{ id: 'phase1', title: 'Phase 1', start: new Date('2024-06-01'), end: new Date('2024-06-30'), progress: 0 },
|
|
705
|
+
{ id: 't1', title: 'Design', parent: 'phase1', start: new Date('2024-06-01'), end: new Date('2024-06-14'), progress: 80 },
|
|
706
|
+
{ id: 't2', title: 'Build', parent: 'phase1', start: new Date('2024-06-15'), end: new Date('2024-06-30'), progress: 20 },
|
|
707
|
+
{ id: 'launch', title: 'Launch', type: 'milestone', start: new Date('2024-07-01'), end: new Date('2024-07-01'), progress: 0 },
|
|
275
708
|
];
|
|
276
709
|
```
|
|
277
710
|
|
|
@@ -313,49 +746,60 @@ Two more chrome features ship with it:
|
|
|
313
746
|
- **Custom markers** — vertical reference lines beyond the Today marker:
|
|
314
747
|
|
|
315
748
|
```tsx
|
|
316
|
-
|
|
749
|
+
import { GanttView } from '@object-ui/plugin-gantt';
|
|
750
|
+
import type { GanttTask } from '@object-ui/plugin-gantt';
|
|
751
|
+
|
|
752
|
+
declare const tasks: GanttTask[];
|
|
753
|
+
|
|
754
|
+
const chartWithMarkers = <GanttView
|
|
317
755
|
tasks={tasks}
|
|
318
756
|
markers={[
|
|
319
757
|
{ date: '2026-07-01', label: 'Code freeze', color: '#ef4444' },
|
|
320
758
|
{ date: '2026-07-15', label: 'Release' }, // defaults to the primary theme color
|
|
321
759
|
]}
|
|
322
|
-
|
|
760
|
+
/>;
|
|
323
761
|
```
|
|
324
762
|
|
|
325
|
-
Through the schema, pass the same array as `markers`
|
|
763
|
+
Through the schema, pass the same array as `markers` in the gantt node's
|
|
764
|
+
`properties` bag.
|
|
326
765
|
|
|
327
766
|
## Task Dependencies
|
|
328
767
|
|
|
329
768
|
Link tasks to show dependencies:
|
|
330
769
|
|
|
331
770
|
```typescript
|
|
332
|
-
|
|
771
|
+
import type { GanttTask } from '@object-ui/plugin-gantt';
|
|
772
|
+
|
|
773
|
+
const tasks: GanttTask[] = [
|
|
333
774
|
{
|
|
334
775
|
id: 'task-1',
|
|
335
|
-
|
|
336
|
-
start: '2024-01-01',
|
|
337
|
-
end: '2024-01-10',
|
|
776
|
+
title: 'Foundation',
|
|
777
|
+
start: new Date('2024-01-01'),
|
|
778
|
+
end: new Date('2024-01-10'),
|
|
338
779
|
progress: 100
|
|
339
780
|
},
|
|
340
781
|
{
|
|
341
782
|
id: 'task-2',
|
|
342
|
-
|
|
343
|
-
start: '2024-01-11',
|
|
344
|
-
end: '2024-01-25',
|
|
783
|
+
title: 'Building',
|
|
784
|
+
start: new Date('2024-01-11'),
|
|
785
|
+
end: new Date('2024-01-25'),
|
|
345
786
|
progress: 50,
|
|
346
787
|
dependencies: ['task-1'] // Depends on task-1
|
|
347
788
|
},
|
|
348
789
|
{
|
|
349
790
|
id: 'task-3',
|
|
350
|
-
|
|
351
|
-
start: '2024-01-26',
|
|
352
|
-
end: '2024-02-05',
|
|
791
|
+
title: 'Finishing',
|
|
792
|
+
start: new Date('2024-01-26'),
|
|
793
|
+
end: new Date('2024-02-05'),
|
|
353
794
|
progress: 0,
|
|
354
795
|
dependencies: ['task-2'] // Depends on task-2
|
|
355
796
|
}
|
|
356
797
|
];
|
|
357
798
|
```
|
|
358
799
|
|
|
800
|
+
Through a data source the same links come from `dependenciesField` on the
|
|
801
|
+
schema, and the tree/label fields from `parentField` / `titleField`.
|
|
802
|
+
|
|
359
803
|
Dependencies render as arrows from the predecessor bar to the dependent bar.
|
|
360
804
|
Arrows follow bars live while dragging, and hovering a bar highlights its links.
|
|
361
805
|
|
|
@@ -380,48 +824,104 @@ be a CSV string (`"task1, task2"`), an array of ids, or an array of objects —
|
|
|
380
824
|
|
|
381
825
|
## Integration with Data Sources
|
|
382
826
|
|
|
383
|
-
|
|
827
|
+
The adapter is **not** a schema key — it reaches the renderer through the
|
|
828
|
+
renderer context (or as an explicit `dataSource` prop), while the schema names
|
|
829
|
+
the object and the fields. Mounted directly as below, `ObjectGantt` takes the
|
|
830
|
+
node as it reads it, typed `ObjectGanttSchema`, with its props on the node;
|
|
831
|
+
through `SchemaRenderer` the same props go in the authored node's `properties`
|
|
832
|
+
bag:
|
|
833
|
+
|
|
834
|
+
```tsx
|
|
384
835
|
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
|
|
836
|
+
import { ObjectGantt } from '@object-ui/plugin-gantt';
|
|
837
|
+
import type { ObjectGanttSchema } from '@object-ui/types';
|
|
385
838
|
|
|
386
839
|
const dataSource = createObjectStackAdapter({
|
|
387
840
|
baseUrl: 'https://api.example.com',
|
|
388
841
|
token: 'your-auth-token'
|
|
389
842
|
});
|
|
390
843
|
|
|
391
|
-
const schema = {
|
|
844
|
+
const schema: ObjectGanttSchema = {
|
|
392
845
|
type: 'object-gantt',
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
end: 'end_date',
|
|
399
|
-
progress: 'progress_percent'
|
|
400
|
-
}
|
|
846
|
+
objectName: 'tasks',
|
|
847
|
+
titleField: 'task_name',
|
|
848
|
+
startDateField: 'start_date',
|
|
849
|
+
endDateField: 'end_date',
|
|
850
|
+
progressField: 'progress_percent'
|
|
401
851
|
};
|
|
852
|
+
|
|
853
|
+
const ganttWithAdapter = <ObjectGantt schema={schema} dataSource={dataSource} />;
|
|
402
854
|
```
|
|
403
855
|
|
|
856
|
+
⚠️ A `dataSource` key **on the schema node** is a different thing with the same
|
|
857
|
+
name: it is the spec's `PageComponentSchema.dataSource` *binding* —
|
|
858
|
+
`{ object, view?, filter?, sort?, limit? }`, a declarative reference resolved
|
|
859
|
+
against the host — **not** an adapter instance. The renderer guards against the
|
|
860
|
+
confusion explicitly, so putting a live adapter there does not wire anything up.
|
|
861
|
+
|
|
404
862
|
## TypeScript Support
|
|
405
863
|
|
|
864
|
+
Two different vocabularies, two different packages — don't mix them up.
|
|
865
|
+
|
|
866
|
+
**The component's runtime types** come from this package. Use them when you
|
|
867
|
+
render `<GanttView>` (or `ObjectGantt`) yourself in React:
|
|
868
|
+
|
|
406
869
|
```typescript
|
|
407
|
-
import type {
|
|
870
|
+
import type { GanttTask, GanttViewProps } from '@object-ui/plugin-gantt';
|
|
408
871
|
|
|
409
872
|
const task: GanttTask = {
|
|
410
873
|
id: '1',
|
|
411
|
-
|
|
412
|
-
start: '2024-01-01',
|
|
413
|
-
end: '2024-01-10',
|
|
874
|
+
title: 'My Task',
|
|
875
|
+
start: new Date('2024-01-01'),
|
|
876
|
+
end: new Date('2024-01-10'),
|
|
414
877
|
progress: 50,
|
|
415
|
-
dependencies: []
|
|
878
|
+
dependencies: [],
|
|
416
879
|
};
|
|
417
880
|
|
|
418
|
-
const
|
|
419
|
-
|
|
881
|
+
const props: GanttViewProps = {
|
|
882
|
+
tasks: [task],
|
|
420
883
|
viewMode: 'week',
|
|
421
|
-
tasks: [task]
|
|
422
884
|
};
|
|
423
885
|
```
|
|
424
886
|
|
|
887
|
+
**The authored (JSON metadata) node** is `{ type: 'object-gantt', properties }`,
|
|
888
|
+
and the members of `properties` are `@objectstack/spec`'s
|
|
889
|
+
`ComponentPropsMap['object-gantt']` row (its `ObjectGanttProps`). It is
|
|
890
|
+
**record-driven**: it names an object and the fields to read, it does not carry
|
|
891
|
+
a task array. There is no `GanttSchema`. `ObjectGanttSchema` in `@object-ui/types`
|
|
892
|
+
— which this package imports and does not re-export — types the node as
|
|
893
|
+
`ObjectGantt` reads it: after `SchemaRenderer` hoists the bag, or as you mount it
|
|
894
|
+
yourself.
|
|
895
|
+
|
|
896
|
+
```typescript
|
|
897
|
+
import type { ObjectGanttBlockNode } from '@object-ui/types';
|
|
898
|
+
|
|
899
|
+
const gantt: ObjectGanttBlockNode = {
|
|
900
|
+
type: 'object-gantt',
|
|
901
|
+
properties: {
|
|
902
|
+
objectName: 'project_tasks',
|
|
903
|
+
gantt: {
|
|
904
|
+
titleField: 'name',
|
|
905
|
+
startDateField: 'start_date',
|
|
906
|
+
endDateField: 'end_date',
|
|
907
|
+
progressField: 'completion_percentage',
|
|
908
|
+
dependenciesField: 'dependent_task_ids',
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
};
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
`dependenciesField` (plural) is the spec's spelling and the one to author. The
|
|
915
|
+
singular `dependencyField` is a `@deprecated` legacy alias: `ObjectGantt` still
|
|
916
|
+
reads it (`dependenciesField || dependencyField`) on a node built in code, but an
|
|
917
|
+
authored node cannot carry it — `objectui validate` refuses it and names
|
|
918
|
+
`properties.gantt.dependenciesField`.
|
|
919
|
+
|
|
920
|
+
For a list view served under the `gantt` view type, the same configuration is a
|
|
921
|
+
`gantt` block on `ListViewSchema` (typed by `GanttConfig`, also from
|
|
922
|
+
`@object-ui/types`) rather than top-level keys — `ObjectGantt` reads either
|
|
923
|
+
spelling.
|
|
924
|
+
|
|
425
925
|
## Links
|
|
426
926
|
|
|
427
927
|
- 📚 [Documentation](https://www.objectui.org/docs/plugins/plugin-gantt)
|