@object-ui/plugin-gantt 3.1.5 → 3.3.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GanttView.d.ts","sourceRoot":"","sources":["../../../../src/GanttView.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA0CH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,IAAI,CAAA;IACX,GAAG,EAAE,IAAI,CAAA;IACT,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,GAAG,CAAA;IACV,YAAY,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;CACnC;AAED,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,SAAS,CAAC,EAAE,IAAI,CAAA;IAChB,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,CAAC,CAAC,KAAK,IAAI,CAAA;IACnH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAkB,EAClB,SAAS,EACT,OAAO,EACP,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,SAAS,EACT,UAAkB,GACnB,EAAE,cAAc,2CAqVhB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectGantt.d.ts","sourceRoot":"","sources":["../../../../src/ObjectGantt.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAyB,MAAM,kBAAkB,CAAC;AAO5F,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAClC;AAyFD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAkMlD,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5E,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,MAAM,EAAE,GAAG,CAAA;CAAE,CAGzD,CAAC"}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-gantt",
3
- "version": "3.1.5",
3
+ "version": "3.3.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Gantt chart plugin for Object UI",
7
- "homepage": "https://www.objectui.org",
7
+ "homepage": "https://www.objectui.org/docs/plugins/plugin-gantt",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/objectstack-ai/objectui.git",
10
+ "url": "git+https://github.com/objectstack-ai/objectui.git",
11
11
  "directory": "packages/plugin-gantt"
12
12
  },
13
13
  "bugs": {
@@ -24,13 +24,13 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@objectstack/spec": "^3.3.0",
28
- "lucide-react": "^0.577.0",
29
- "@object-ui/components": "3.1.5",
30
- "@object-ui/core": "3.1.5",
31
- "@object-ui/fields": "3.1.5",
32
- "@object-ui/react": "3.1.5",
33
- "@object-ui/types": "3.1.5"
27
+ "@objectstack/spec": "^4.0.4",
28
+ "lucide-react": "^1.8.0",
29
+ "@object-ui/components": "3.3.1",
30
+ "@object-ui/core": "3.3.1",
31
+ "@object-ui/fields": "3.3.1",
32
+ "@object-ui/react": "3.3.1",
33
+ "@object-ui/types": "3.3.1"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": "^18.0.0 || ^19.0.0",
@@ -40,10 +40,33 @@
40
40
  "@types/react": "19.2.14",
41
41
  "@types/react-dom": "19.2.3",
42
42
  "@vitejs/plugin-react": "^6.0.1",
43
- "typescript": "^5.9.3",
44
- "vite": "^8.0.1",
43
+ "typescript": "^6.0.3",
44
+ "vite": "^8.0.9",
45
45
  "vite-plugin-dts": "^4.5.4"
46
46
  },
47
+ "keywords": [
48
+ "objectui",
49
+ "sdui",
50
+ "schema-driven-ui",
51
+ "react",
52
+ "tailwind",
53
+ "shadcn",
54
+ "objectstack",
55
+ "plugin",
56
+ "gantt",
57
+ "timeline",
58
+ "project-management"
59
+ ],
60
+ "author": "ObjectStack Team <team@objectstack.ai>",
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "files": [
65
+ "dist",
66
+ "README.md",
67
+ "CHANGELOG.md",
68
+ "LICENSE"
69
+ ],
47
70
  "scripts": {
48
71
  "build": "vite build",
49
72
  "test": "vitest run",
@@ -1,22 +0,0 @@
1
-
2
- > @object-ui/plugin-gantt@3.1.5 build /home/runner/work/objectui/objectui/packages/plugin-gantt
3
- > vite build
4
-
5
- vite v8.0.1 building client environment for production...
6
- 
7
- rendering chunks...
8
- 
9
- [vite:dts] Start generate declaration files...
10
- [vite:dts] Declaration files built in 18359ms.
11
- 
12
- computing gzip size...
13
- dist/index.js 220.74 kB │ gzip: 54.39 kB
14
-
15
- [PLUGIN_TIMINGS] Warning: Your build spent significant time in plugin `vite:dts`. See https://rolldown.rs/options/checks#plugintimings for more details.
16
- 
17
- 
18
- rendering chunks...
19
- computing gzip size...
20
- dist/index.umd.cjs 181.78 kB │ gzip: 49.74 kB
21
-
22
- ✓ built in 20.42s
@@ -1 +0,0 @@
1
- {"version":3,"file":"GanttView.d.ts","sourceRoot":"","sources":["../../src/GanttView.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA0CH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,IAAI,CAAA;IACX,GAAG,EAAE,IAAI,CAAA;IACT,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,GAAG,CAAA;IACV,YAAY,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;CACnC;AAED,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,SAAS,CAAC,EAAE,IAAI,CAAA;IAChB,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,CAAC,CAAC,KAAK,IAAI,CAAA;IACnH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAkB,EAClB,SAAS,EACT,OAAO,EACP,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,SAAS,EACT,UAAkB,GACnB,EAAE,cAAc,2CAqVhB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ObjectGantt.d.ts","sourceRoot":"","sources":["../../src/ObjectGantt.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAyB,MAAM,kBAAkB,CAAC;AAO5F,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAClC;AAyFD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAkMlD,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5E,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,MAAM,EAAE,GAAG,CAAA;CAAE,CAGzD,CAAC"}
package/src/GanttView.tsx DELETED
@@ -1,428 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- "use client"
10
-
11
- import * as React from "react"
12
- import {
13
- ChevronLeft,
14
- ChevronRight,
15
- ZoomIn,
16
- ZoomOut,
17
- Calendar as CalendarIcon,
18
- MoreHorizontal,
19
- Plus
20
- } from "lucide-react"
21
- import {
22
- cn,
23
- Button,
24
- Select,
25
- SelectContent,
26
- SelectItem,
27
- SelectTrigger,
28
- SelectValue,
29
- Separator
30
- } from "@object-ui/components"
31
-
32
- const HEADER_HEIGHT = 50;
33
- const COLUMN_WIDTH = 100; // Time column width
34
-
35
- function getResponsiveColumnWidth() {
36
- const w = typeof window !== 'undefined' ? window.innerWidth : 1024;
37
- if (w < 640) return 35;
38
- if (w < 1024) return 50;
39
- return 60;
40
- }
41
-
42
- function getResponsiveTaskListWidth() {
43
- const w = typeof window !== 'undefined' ? window.innerWidth : 1024;
44
- if (w < 640) return 120;
45
- if (w < 1024) return 200;
46
- return 300;
47
- }
48
-
49
- export interface GanttTask {
50
- id: string | number
51
- title: string
52
- start: Date
53
- end: Date
54
- progress: number
55
- color?: string
56
- data?: any
57
- dependencies?: (string | number)[]
58
- }
59
-
60
- export type GanttViewMode = 'day' | 'week' | 'month' | 'quarter';
61
-
62
- export interface GanttViewProps {
63
- tasks: GanttTask[]
64
- viewMode?: GanttViewMode
65
- startDate?: Date
66
- endDate?: Date
67
- onTaskClick?: (task: GanttTask) => void
68
- onTaskUpdate?: (task: GanttTask, changes: Partial<Pick<GanttTask, 'title' | 'start' | 'end' | 'progress'>>) => void
69
- onViewChange?: (view: GanttViewMode) => void
70
- onAddClick?: () => void
71
- className?: string
72
- /** Enable inline editing of task fields */
73
- inlineEdit?: boolean
74
- }
75
-
76
- export function GanttView({
77
- tasks,
78
- viewMode = 'month',
79
- startDate,
80
- endDate,
81
- onTaskClick,
82
- onTaskUpdate,
83
- onViewChange,
84
- onAddClick,
85
- className,
86
- inlineEdit = false,
87
- }: GanttViewProps) {
88
- const [currentDate, setCurrentDate] = React.useState(new Date());
89
- const [rowHeight, setRowHeight] = React.useState(
90
- typeof window !== 'undefined' && window.innerWidth < 640 ? 32 : 40
91
- );
92
- const [columnWidth, setColumnWidth] = React.useState(getResponsiveColumnWidth());
93
- const [editingTask, setEditingTask] = React.useState<string | number | null>(null);
94
- const [editValues, setEditValues] = React.useState<Record<string, string>>({});
95
-
96
- React.useEffect(() => {
97
- const handleResize = () => {
98
- setRowHeight(window.innerWidth < 640 ? 32 : 40);
99
- setColumnWidth(getResponsiveColumnWidth());
100
- };
101
- window.addEventListener('resize', handleResize);
102
- return () => window.removeEventListener('resize', handleResize);
103
- }, []);
104
-
105
- // Calculate timeline range
106
- const timelineRange = React.useMemo(() => {
107
- let start = startDate ? new Date(startDate) : new Date();
108
- let end = endDate ? new Date(endDate) : new Date();
109
-
110
- if (!startDate && tasks.length > 0) {
111
- // Find min start date
112
- start = new Date(Math.min(...tasks.map(t => t.start.getTime())));
113
- // Add padding
114
- start.setDate(start.getDate() - 7);
115
- }
116
-
117
- if (!endDate && tasks.length > 0) {
118
- // Find max end date
119
- end = new Date(Math.max(...tasks.map(t => t.end.getTime())));
120
- // Add padding
121
- end.setDate(end.getDate() + 14);
122
- }
123
-
124
- // Normalize to start of day
125
- start.setHours(0,0,0,0);
126
- end.setHours(23,59,59,999);
127
-
128
- return { start, end };
129
- }, [startDate, endDate, tasks]);
130
-
131
- // Generate timeline columns
132
- const timeColumns = React.useMemo(() => {
133
- const cols: { date: Date; label: string; isWeekend: boolean }[] = [];
134
- const current = new Date(timelineRange.start);
135
-
136
- while (current <= timelineRange.end) {
137
- cols.push({
138
- date: new Date(current),
139
- label: current.getDate().toString(),
140
- isWeekend: current.getDay() === 0 || current.getDay() === 6
141
- });
142
- current.setDate(current.getDate() + 1);
143
- }
144
-
145
- return cols;
146
- }, [timelineRange]);
147
-
148
- const taskListWidth = getResponsiveTaskListWidth();
149
-
150
- const headerRef = React.useRef<HTMLDivElement>(null);
151
- const listRef = React.useRef<HTMLDivElement>(null);
152
- const timelineRef = React.useRef<HTMLDivElement>(null);
153
-
154
- const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
155
- // Sync horizontal scroll to header
156
- if (headerRef.current) {
157
- headerRef.current.scrollLeft = e.currentTarget.scrollLeft;
158
- }
159
- // Sync vertical scroll to task list
160
- if (listRef.current) {
161
- listRef.current.scrollTop = e.currentTarget.scrollTop;
162
- }
163
- };
164
-
165
- const getTaskStyle = (task: GanttTask) => {
166
- const totalDuration = timelineRange.end.getTime() - timelineRange.start.getTime();
167
- const tickWidth = columnWidth; // px per day
168
- const msPerDay = 1000 * 60 * 60 * 24;
169
-
170
- const startOffsetMs = task.start.getTime() - timelineRange.start.getTime();
171
- const durationMs = task.end.getTime() - task.start.getTime();
172
-
173
- const left = (startOffsetMs / msPerDay) * tickWidth;
174
- const width = Math.max((durationMs / msPerDay) * tickWidth, tickWidth); // Min 1 day width
175
-
176
- return { left, width };
177
- };
178
-
179
- return (
180
- <div className={cn("flex flex-col h-full bg-background border rounded-lg overflow-hidden min-w-0", className)}>
181
- {/* Toolbar */}
182
- <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 p-2 border-b bg-card">
183
- <div className="flex items-center gap-2">
184
- <Button variant="outline" size="sm" onClick={() => onAddClick?.()} aria-label="Create new task">
185
- <Plus className="h-4 w-4 mr-1 sm:mr-2" />
186
- <span className="hidden sm:inline">New Task</span>
187
- <span className="sm:hidden">New</span>
188
- </Button>
189
- <div className="h-4 w-px bg-border mx-1 sm:mx-2" />
190
- <Button variant="ghost" size="icon" className="h-8 w-8" aria-label="Previous period">
191
- <ChevronLeft className="h-4 w-4" />
192
- </Button>
193
- <Button variant="ghost" size="icon" className="h-8 w-8" aria-label="Next period">
194
- <ChevronRight className="h-4 w-4" />
195
- </Button>
196
- <span className="font-semibold text-xs sm:text-sm">
197
- {timelineRange.start.toLocaleDateString(undefined, { month: 'long', year: 'numeric' })}
198
- </span>
199
- </div>
200
-
201
- <div className="flex items-center gap-2">
202
- <Select value={viewMode} onValueChange={(v) => onViewChange?.(v as GanttViewMode)}>
203
- <SelectTrigger className="w-[100px] sm:w-[120px] h-8">
204
- <SelectValue />
205
- </SelectTrigger>
206
- <SelectContent>
207
- <SelectItem value="day">Day View</SelectItem>
208
- <SelectItem value="week">Week View</SelectItem>
209
- <SelectItem value="month">Month View</SelectItem>
210
- <SelectItem value="quarter">Quarter View</SelectItem>
211
- </SelectContent>
212
- </Select>
213
- <div className="flex bg-muted rounded-md p-1">
214
- <Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => setColumnWidth(prev => Math.max(15, prev - 10))} aria-label="Zoom out">
215
- <ZoomOut className="h-3 w-3" />
216
- </Button>
217
- <Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => setColumnWidth(prev => Math.min(120, prev + 10))} aria-label="Zoom in">
218
- <ZoomIn className="h-3 w-3" />
219
- </Button>
220
- </div>
221
- </div>
222
- </div>
223
-
224
- {/* Gantt Body */}
225
- <div className="flex flex-col flex-1 overflow-hidden">
226
- {/* Headers Row */}
227
- <div className="flex border-b bg-muted/30 shrink-0 h-10 sm:h-[50px]">
228
- {/* List Header */}
229
- <div
230
- className="flex items-center font-medium text-xs text-muted-foreground px-2 sm:px-4 border-r bg-card z-20 shadow-sm"
231
- style={{ width: taskListWidth, minWidth: taskListWidth }}
232
- >
233
- <div className="flex-1">Task Name</div>
234
- <div className="w-16 sm:w-20 text-right hidden sm:block">Start</div>
235
- <div className="w-16 sm:w-20 text-right hidden sm:block">End</div>
236
- </div>
237
-
238
- {/* Timeline Header */}
239
- <div className="flex-1 overflow-hidden" ref={headerRef}>
240
- <div className="flex h-full" style={{ width: timeColumns.length * columnWidth }}>
241
- {timeColumns.map((col, i) => (
242
- <div
243
- key={i}
244
- className={cn(
245
- "flex flex-col items-center justify-center border-r text-xs text-muted-foreground h-full",
246
- col.isWeekend && "bg-muted/50"
247
- )}
248
- style={{ width: columnWidth, minWidth: columnWidth }}
249
- >
250
- <span className="font-medium text-foreground">{col.label}</span>
251
- <span className="text-[10px] opacity-70">
252
- {col.date.toLocaleDateString(undefined, { weekday: 'narrow' })}
253
- </span>
254
- </div>
255
- ))}
256
- </div>
257
- </div>
258
- </div>
259
-
260
- {/* Content Row */}
261
- <div className="flex flex-1 overflow-hidden">
262
- {/* Left Side: Task List (Grid) */}
263
- <div
264
- className="overflow-hidden border-r bg-card z-10 shadow-sm"
265
- ref={listRef}
266
- style={{ width: taskListWidth, minWidth: taskListWidth }}
267
- >
268
- {tasks.map((task) => {
269
- const isEditing = inlineEdit && editingTask === task.id;
270
- return (
271
- <div
272
- key={task.id}
273
- className="flex items-center border-b px-2 sm:px-4 hover:bg-accent/50 cursor-pointer transition-colors touch-manipulation"
274
- style={{ height: rowHeight }}
275
- onClick={() => !isEditing && onTaskClick?.(task)}
276
- onDoubleClick={() => {
277
- if (inlineEdit && onTaskUpdate) {
278
- setEditingTask(task.id);
279
- setEditValues({
280
- title: task.title,
281
- start: task.start.toLocaleDateString('en-CA'),
282
- end: task.end.toLocaleDateString('en-CA'),
283
- progress: String(task.progress),
284
- });
285
- }
286
- }}
287
- >
288
- <div className="flex-1 truncate font-medium text-xs sm:text-sm flex items-center gap-2">
289
- <div
290
- className="w-2 h-2 rounded-full shrink-0"
291
- style={{ backgroundColor: task.color || '#3b82f6' }}
292
- />
293
- {isEditing ? (
294
- <input
295
- className="border rounded px-1 py-0.5 text-xs w-full bg-background"
296
- value={editValues.title || ''}
297
- onChange={(e) => setEditValues(prev => ({ ...prev, title: e.target.value }))}
298
- onKeyDown={(e) => {
299
- if (e.key === 'Enter') {
300
- onTaskUpdate?.(task, {
301
- title: editValues.title,
302
- start: new Date(editValues.start),
303
- end: new Date(editValues.end),
304
- progress: Number(editValues.progress) || 0,
305
- });
306
- setEditingTask(null);
307
- } else if (e.key === 'Escape') {
308
- setEditingTask(null);
309
- }
310
- }}
311
- onClick={(e) => e.stopPropagation()}
312
- autoFocus
313
- />
314
- ) : (
315
- <span className="flex flex-col min-w-0">
316
- <span className="truncate">{task.title}</span>
317
- <span className="text-[10px] text-muted-foreground sm:hidden">
318
- {task.start.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })} → {task.end.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })}
319
- </span>
320
- </span>
321
- )}
322
- </div>
323
- <div className="w-16 sm:w-20 text-right text-xs text-muted-foreground hidden sm:block">
324
- {isEditing ? (
325
- <input
326
- type="date"
327
- className="border rounded px-1 py-0.5 text-xs w-full bg-background"
328
- value={editValues.start || ''}
329
- onChange={(e) => setEditValues(prev => ({ ...prev, start: e.target.value }))}
330
- onClick={(e) => e.stopPropagation()}
331
- />
332
- ) : (
333
- task.start.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })
334
- )}
335
- </div>
336
- <div className="w-16 sm:w-20 text-right text-xs text-muted-foreground hidden sm:block">
337
- {isEditing ? (
338
- <input
339
- type="date"
340
- className="border rounded px-1 py-0.5 text-xs w-full bg-background"
341
- value={editValues.end || ''}
342
- onChange={(e) => setEditValues(prev => ({ ...prev, end: e.target.value }))}
343
- onClick={(e) => e.stopPropagation()}
344
- />
345
- ) : (
346
- task.end.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })
347
- )}
348
- </div>
349
- </div>
350
- );
351
- })}
352
- </div>
353
-
354
- {/* Right Side: Timeline */}
355
- <div
356
- className="flex-1 overflow-auto bg-background/50 relative [-webkit-overflow-scrolling:touch]"
357
- ref={timelineRef}
358
- onScroll={handleScroll}
359
- >
360
- <div style={{ width: timeColumns.length * columnWidth }}>
361
- {/* Timeline Task Rows */}
362
- <div className="relative">
363
- {/* Background Grid */}
364
- <div className="absolute inset-0 flex pointer-events-none z-0">
365
- {timeColumns.map((col, i) => (
366
- <div
367
- key={i}
368
- className={cn(
369
- "border-r h-full",
370
- col.isWeekend && "bg-muted/20"
371
- )}
372
- style={{ width: columnWidth, minWidth: columnWidth }}
373
- />
374
- ))}
375
- </div>
376
-
377
- {/* Task Bars */}
378
- {tasks.map((task) => {
379
- const style = getTaskStyle(task);
380
- return (
381
- <div
382
- key={task.id}
383
- className="relative border-b hover:bg-black/5"
384
- style={{ height: rowHeight }}
385
- >
386
- <div
387
- className="absolute top-1 sm:top-2 h-[calc(100%-8px)] sm:h-[calc(100%-16px)] rounded-sm bg-primary border border-primary-foreground/20 shadow-sm cursor-pointer hover:brightness-110 flex items-center px-2 group"
388
- style={{
389
- left: style.left,
390
- width: style.width,
391
- backgroundColor: task.color || '#3b82f6'
392
- }}
393
- onClick={() => onTaskClick?.(task)}
394
- >
395
- {/* Progress Filter */}
396
- {task.progress > 0 && (
397
- <div
398
- className="absolute left-0 top-0 bottom-0 bg-black/20 rounded-l-sm"
399
- style={{ width: `${task.progress}%` }}
400
- />
401
- )}
402
-
403
- {/* Hover Details */}
404
- <span className="text-[10px] text-white font-medium truncate opacity-0 group-hover:opacity-100 transition-opacity">
405
- {Math.round(task.progress)}%
406
- </span>
407
- </div>
408
- </div>
409
- )
410
- })}
411
-
412
- {/* Current Time Indicator */}
413
- <div
414
- className="absolute top-0 bottom-0 w-px bg-red-500 z-20 pointer-events-none"
415
- style={{
416
- left: (new Date().getTime() - timelineRange.start.getTime()) / (1000 * 60 * 60 * 24) * columnWidth
417
- }}
418
- >
419
- <div className="w-2 h-2 rounded-full bg-red-500 -ml-[3px]" />
420
- </div>
421
- </div>
422
- </div>
423
- </div>
424
- </div>
425
- </div>
426
- </div>
427
- )
428
- }
@@ -1,112 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react';
3
- import type { BaseSchema } from '@object-ui/types';
4
- import { createStorybookDataSource } from '@storybook-config/datasource';
5
-
6
- const meta = {
7
- title: 'Plugins/ObjectGantt',
8
- component: SchemaRenderer,
9
- parameters: {
10
- layout: 'padded',
11
- },
12
- tags: ['autodocs'],
13
- argTypes: {
14
- schema: { table: { disable: true } },
15
- },
16
- } satisfies Meta<any>;
17
-
18
- export default meta;
19
- type Story = StoryObj<typeof meta>;
20
-
21
- const dataSource = createStorybookDataSource();
22
-
23
- const renderStory = (args: any) => (
24
- <SchemaRendererProvider dataSource={dataSource}>
25
- <SchemaRenderer schema={args as unknown as BaseSchema} />
26
- </SchemaRendererProvider>
27
- );
28
-
29
- export const Default: Story = {
30
- render: renderStory,
31
- args: {
32
- type: 'object-gantt',
33
- objectName: 'Project',
34
- gantt: {
35
- startDateField: 'startDate',
36
- endDateField: 'endDate',
37
- titleField: 'name',
38
- progressField: 'progress',
39
- dependenciesField: 'dependencies',
40
- },
41
- tasks: [
42
- {
43
- id: '1',
44
- name: 'Requirements Gathering',
45
- startDate: '2024-01-01',
46
- endDate: '2024-01-20',
47
- progress: 100,
48
- },
49
- {
50
- id: '2',
51
- name: 'System Design',
52
- startDate: '2024-01-15',
53
- endDate: '2024-02-10',
54
- progress: 80,
55
- dependencies: ['1'],
56
- },
57
- {
58
- id: '3',
59
- name: 'Frontend Development',
60
- startDate: '2024-02-01',
61
- endDate: '2024-04-15',
62
- progress: 45,
63
- dependencies: ['2'],
64
- },
65
- {
66
- id: '4',
67
- name: 'Backend Development',
68
- startDate: '2024-02-01',
69
- endDate: '2024-04-30',
70
- progress: 35,
71
- dependencies: ['2'],
72
- },
73
- {
74
- id: '5',
75
- name: 'QA Testing',
76
- startDate: '2024-04-15',
77
- endDate: '2024-05-30',
78
- progress: 0,
79
- dependencies: ['3', '4'],
80
- },
81
- {
82
- id: '6',
83
- name: 'Deployment',
84
- startDate: '2024-05-25',
85
- endDate: '2024-06-10',
86
- progress: 0,
87
- dependencies: ['5'],
88
- },
89
- ],
90
- className: 'w-full',
91
- } as any,
92
- };
93
-
94
- export const SimpleTimeline: Story = {
95
- render: renderStory,
96
- args: {
97
- type: 'object-gantt',
98
- objectName: 'Milestone',
99
- gantt: {
100
- startDateField: 'start',
101
- endDateField: 'end',
102
- titleField: 'title',
103
- },
104
- tasks: [
105
- { id: '1', title: 'Phase 1: Planning', start: '2024-01-01', end: '2024-02-28' },
106
- { id: '2', title: 'Phase 2: Development', start: '2024-03-01', end: '2024-06-30' },
107
- { id: '3', title: 'Phase 3: Testing', start: '2024-07-01', end: '2024-08-31' },
108
- { id: '4', title: 'Phase 4: Launch', start: '2024-09-01', end: '2024-09-30' },
109
- ],
110
- className: 'w-full',
111
- } as any,
112
- };