@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.
- package/CHANGELOG.md +31 -0
- package/README.md +21 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +360 -362
- package/dist/index.umd.cjs +3 -3
- package/dist/packages/plugin-gantt/src/GanttView.d.ts.map +1 -0
- package/dist/packages/plugin-gantt/src/ObjectGantt.d.ts.map +1 -0
- package/dist/packages/plugin-gantt/src/index.d.ts.map +1 -0
- package/package.json +35 -12
- package/.turbo/turbo-build.log +0 -22
- package/dist/src/GanttView.d.ts.map +0 -1
- package/dist/src/ObjectGantt.d.ts.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/src/GanttView.tsx +0 -428
- package/src/ObjectGantt.stories.tsx +0 -112
- package/src/ObjectGantt.test.tsx +0 -88
- package/src/ObjectGantt.tsx +0 -324
- package/src/__tests__/GanttView.test.tsx +0 -69
- package/src/index.test.tsx +0 -25
- package/src/index.tsx +0 -45
- package/tsconfig.json +0 -18
- package/vite.config.ts +0 -53
- package/vitest.config.ts +0 -13
- package/vitest.setup.ts +0 -1
- /package/dist/{src → packages/plugin-gantt/src}/GanttView.d.ts +0 -0
- /package/dist/{src → packages/plugin-gantt/src}/ObjectGantt.d.ts +0 -0
- /package/dist/{src → packages/plugin-gantt/src}/index.d.ts +0 -0
|
@@ -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
|
|
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": "^
|
|
28
|
-
"lucide-react": "^
|
|
29
|
-
"@object-ui/components": "3.1
|
|
30
|
-
"@object-ui/core": "3.1
|
|
31
|
-
"@object-ui/fields": "3.1
|
|
32
|
-
"@object-ui/react": "3.1
|
|
33
|
-
"@object-ui/types": "3.1
|
|
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": "^
|
|
44
|
-
"vite": "^8.0.
|
|
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",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -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
|
-
[36mvite v8.0.1 [32mbuilding client environment for production...[36m[39m
|
|
6
|
-
[2K
|
|
7
|
-
rendering chunks...
|
|
8
|
-
[32m
|
|
9
|
-
[36m[vite:dts][32m Start generate declaration files...[39m
|
|
10
|
-
[32m[36m[vite:dts][32m Declaration files built in 18359ms.
|
|
11
|
-
[39m
|
|
12
|
-
computing gzip size...
|
|
13
|
-
dist/index.js 220.74 kB │ gzip: 54.39 kB
|
|
14
|
-
|
|
15
|
-
[33m[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugin `vite:dts`. See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
16
|
-
[39m
|
|
17
|
-
[2K
|
|
18
|
-
rendering chunks...
|
|
19
|
-
computing gzip size...
|
|
20
|
-
dist/index.umd.cjs 181.78 kB │ gzip: 49.74 kB
|
|
21
|
-
|
|
22
|
-
[32m✓ built in 20.42s[39m
|
|
@@ -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"}
|
package/dist/src/index.d.ts.map
DELETED
|
@@ -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
|
-
};
|