@memberjunction/ng-tasks 5.14.0 → 5.16.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +25 -0
- package/dist/lib/components/gantt-task-viewer.component.d.ts +3 -1
- package/dist/lib/components/gantt-task-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/gantt-task-viewer.component.js +129 -89
- package/dist/lib/components/gantt-task-viewer.component.js.map +1 -1
- package/dist/lib/ng-tasks.module.js +2 -1
- package/dist/lib/ng-tasks.module.js.map +1 -1
- package/package.json +6 -5
- package/src/lib/components/gantt-task-viewer.component.ts +107 -73
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @memberjunction/ng-tasks
|
|
2
2
|
|
|
3
|
+
## 5.16.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [2387400]
|
|
8
|
+
- Updated dependencies [11dba07]
|
|
9
|
+
- @memberjunction/core@5.16.0
|
|
10
|
+
- @memberjunction/ai-engine-base@5.16.0
|
|
11
|
+
- @memberjunction/ai-core-plus@5.16.0
|
|
12
|
+
- @memberjunction/ng-shared-generic@5.16.0
|
|
13
|
+
- @memberjunction/core-entities@5.16.0
|
|
14
|
+
|
|
15
|
+
## 5.15.0
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [662d56b]
|
|
20
|
+
- Updated dependencies [d01f697]
|
|
21
|
+
- Updated dependencies [c3e8b94]
|
|
22
|
+
- @memberjunction/core@5.15.0
|
|
23
|
+
- @memberjunction/ai-core-plus@5.15.0
|
|
24
|
+
- @memberjunction/ai-engine-base@5.15.0
|
|
25
|
+
- @memberjunction/ng-shared-generic@5.15.0
|
|
26
|
+
- @memberjunction/core-entities@5.15.0
|
|
27
|
+
|
|
3
28
|
## 5.14.0
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
|
@@ -16,11 +16,13 @@ export declare class GanttTaskViewerComponent implements OnChanges, AfterViewIni
|
|
|
16
16
|
ganttContainer: ElementRef<HTMLDivElement>;
|
|
17
17
|
selectedTask: MJTaskEntity | null;
|
|
18
18
|
detailPanelWidth: number;
|
|
19
|
+
IsGanttLoading: boolean;
|
|
20
|
+
private ganttLib;
|
|
19
21
|
private ganttInitialized;
|
|
20
22
|
private isResizing;
|
|
21
23
|
private resizeStartX;
|
|
22
24
|
private resizeStartWidth;
|
|
23
|
-
ngAfterViewInit(): void
|
|
25
|
+
ngAfterViewInit(): Promise<void>;
|
|
24
26
|
ngOnChanges(): void;
|
|
25
27
|
ngOnDestroy(): void;
|
|
26
28
|
private initGantt;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gantt-task-viewer.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/gantt-task-viewer.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAa,SAAS,EAAgB,MAAM,eAAe,CAAC;AAEjJ,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;;
|
|
1
|
+
{"version":3,"file":"gantt-task-viewer.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/gantt-task-viewer.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAa,SAAS,EAAgB,MAAM,eAAe,CAAC;AAEjJ,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;;AAMrF;;GAEG;AACH,qBA6Ha,wBAAyB,YAAW,SAAS,EAAE,aAAa,EAAE,SAAS;IACzE,KAAK,EAAE,YAAY,EAAE,CAAM;IAC3B,gBAAgB,EAAE,sBAAsB,EAAE,CAAM;IAChD,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,6BAAoC;IAC/C,gBAAgB;oBAAkC,MAAM;kBAAY,MAAM;OAAM;IAE1C,cAAc,EAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IAErF,YAAY,EAAE,YAAY,GAAG,IAAI,CAAQ;IACzC,gBAAgB,EAAE,MAAM,CAAO;IAC/B,cAAc,UAAQ;IAE7B,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,gBAAgB,CAAK;IAEvB,eAAe;IAoBrB,WAAW;IAkBX,WAAW;IAOX,OAAO,CAAC,SAAS;IAiFjB,OAAO,CAAC,eAAe;IAiCvB,OAAO,CAAC,oBAAoB;IA8J5B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,mBAAmB;IAOpB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI;IAIhD,gBAAgB,IAAI,IAAI;IAIxB,kBAAkB,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAIzE,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAQ3C,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAWrC,UAAU,IAAI,IAAI;IAYlB,OAAO,CAAC,sBAAsB;yCA3YnB,wBAAwB;2CAAxB,wBAAwB;CA4apC"}
|
|
@@ -1,46 +1,61 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, ViewChild, HostListener } from '@angular/core';
|
|
2
|
-
import { gantt } from 'dhtmlx-gantt';
|
|
3
2
|
import { TaskDetailPanelComponent } from './task-detail-panel.component';
|
|
3
|
+
import { SharedGenericModule } from '@memberjunction/ng-shared-generic';
|
|
4
4
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
5
5
|
import * as i0 from "@angular/core";
|
|
6
|
+
import * as i1 from "@memberjunction/ng-shared-generic";
|
|
6
7
|
const _c0 = ["ganttContainer"];
|
|
7
8
|
function GanttTaskViewerComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
8
9
|
i0.ɵɵelementStart(0, "div", 2);
|
|
9
|
-
i0.ɵɵelement(1, "
|
|
10
|
+
i0.ɵɵelement(1, "mj-loading", 3);
|
|
11
|
+
i0.ɵɵelementEnd();
|
|
12
|
+
} }
|
|
13
|
+
function GanttTaskViewerComponent_Conditional_2_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
14
|
+
i0.ɵɵelementStart(0, "div", 4);
|
|
15
|
+
i0.ɵɵelement(1, "i", 6);
|
|
10
16
|
i0.ɵɵelementStart(2, "p");
|
|
11
17
|
i0.ɵɵtext(3, "No tasks to display in Gantt view");
|
|
12
18
|
i0.ɵɵelementEnd()();
|
|
13
19
|
} }
|
|
14
|
-
function
|
|
20
|
+
function GanttTaskViewerComponent_Conditional_2_Conditional_1_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
15
21
|
const _r1 = i0.ɵɵgetCurrentView();
|
|
16
|
-
i0.ɵɵelementStart(0, "div",
|
|
17
|
-
i0.ɵɵlistener("mousedown", function
|
|
22
|
+
i0.ɵɵelementStart(0, "div", 10);
|
|
23
|
+
i0.ɵɵlistener("mousedown", function GanttTaskViewerComponent_Conditional_2_Conditional_1_Conditional_3_Template_div_mousedown_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.startResize($event)); });
|
|
18
24
|
i0.ɵɵelementEnd();
|
|
19
25
|
} }
|
|
20
|
-
function
|
|
26
|
+
function GanttTaskViewerComponent_Conditional_2_Conditional_1_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
21
27
|
const _r3 = i0.ɵɵgetCurrentView();
|
|
22
|
-
i0.ɵɵelementStart(0, "div",
|
|
23
|
-
i0.ɵɵlistener("closePanel", function
|
|
28
|
+
i0.ɵɵelementStart(0, "div", 11)(1, "mj-task-detail-panel", 12);
|
|
29
|
+
i0.ɵɵlistener("closePanel", function GanttTaskViewerComponent_Conditional_2_Conditional_1_Conditional_4_Template_mj_task_detail_panel_closePanel_1_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.closeDetailPanel()); })("openEntityRecord", function GanttTaskViewerComponent_Conditional_2_Conditional_1_Conditional_4_Template_mj_task_detail_panel_openEntityRecord_1_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onOpenEntityRecord($event)); });
|
|
24
30
|
i0.ɵɵelementEnd()();
|
|
25
31
|
} if (rf & 2) {
|
|
26
|
-
const ctx_r1 = i0.ɵɵnextContext(
|
|
32
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
27
33
|
i0.ɵɵstyleProp("width", ctx_r1.detailPanelWidth, "px");
|
|
28
34
|
i0.ɵɵadvance();
|
|
29
35
|
i0.ɵɵproperty("task", ctx_r1.selectedTask)("agentRunId", ctx_r1.getAgentRunId(ctx_r1.selectedTask));
|
|
30
36
|
} }
|
|
31
|
-
function
|
|
32
|
-
i0.ɵɵelementStart(0, "div",
|
|
33
|
-
i0.ɵɵelement(1, "div",
|
|
34
|
-
i0.ɵɵconditionalCreate(3,
|
|
35
|
-
i0.ɵɵconditionalCreate(4,
|
|
37
|
+
function GanttTaskViewerComponent_Conditional_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
38
|
+
i0.ɵɵelementStart(0, "div", 5);
|
|
39
|
+
i0.ɵɵelement(1, "div", 7, 0);
|
|
40
|
+
i0.ɵɵconditionalCreate(3, GanttTaskViewerComponent_Conditional_2_Conditional_1_Conditional_3_Template, 1, 0, "div", 8);
|
|
41
|
+
i0.ɵɵconditionalCreate(4, GanttTaskViewerComponent_Conditional_2_Conditional_1_Conditional_4_Template, 2, 4, "div", 9);
|
|
36
42
|
i0.ɵɵelementEnd();
|
|
37
43
|
} if (rf & 2) {
|
|
38
|
-
const ctx_r1 = i0.ɵɵnextContext();
|
|
44
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
39
45
|
i0.ɵɵadvance(3);
|
|
40
46
|
i0.ɵɵconditional(ctx_r1.selectedTask ? 3 : -1);
|
|
41
47
|
i0.ɵɵadvance();
|
|
42
48
|
i0.ɵɵconditional(ctx_r1.selectedTask ? 4 : -1);
|
|
43
49
|
} }
|
|
50
|
+
function GanttTaskViewerComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
51
|
+
i0.ɵɵconditionalCreate(0, GanttTaskViewerComponent_Conditional_2_Conditional_0_Template, 4, 0, "div", 4);
|
|
52
|
+
i0.ɵɵconditionalCreate(1, GanttTaskViewerComponent_Conditional_2_Conditional_1_Template, 5, 2, "div", 5);
|
|
53
|
+
} if (rf & 2) {
|
|
54
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
55
|
+
i0.ɵɵconditional(!ctx_r1.tasks || ctx_r1.tasks.length === 0 ? 0 : -1);
|
|
56
|
+
i0.ɵɵadvance();
|
|
57
|
+
i0.ɵɵconditional(ctx_r1.tasks && ctx_r1.tasks.length > 0 ? 1 : -1);
|
|
58
|
+
} }
|
|
44
59
|
/**
|
|
45
60
|
* Gantt chart view for tasks using DHTMLX Gantt
|
|
46
61
|
*/
|
|
@@ -53,17 +68,28 @@ export class GanttTaskViewerComponent {
|
|
|
53
68
|
ganttContainer;
|
|
54
69
|
selectedTask = null;
|
|
55
70
|
detailPanelWidth = 400;
|
|
71
|
+
IsGanttLoading = true;
|
|
72
|
+
ganttLib = null;
|
|
56
73
|
ganttInitialized = false;
|
|
57
74
|
isResizing = false;
|
|
58
75
|
resizeStartX = 0;
|
|
59
76
|
resizeStartWidth = 0;
|
|
60
|
-
ngAfterViewInit() {
|
|
77
|
+
async ngAfterViewInit() {
|
|
61
78
|
console.log('🔧 ngAfterViewInit called', {
|
|
62
79
|
taskCount: this.tasks?.length || 0,
|
|
63
80
|
hasContainer: !!this.ganttContainer
|
|
64
81
|
});
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
try {
|
|
83
|
+
const module = await import('dhtmlx-gantt');
|
|
84
|
+
this.ganttLib = module.gantt;
|
|
85
|
+
this.IsGanttLoading = false;
|
|
86
|
+
if (this.tasks && this.tasks.length > 0 && this.ganttContainer) {
|
|
87
|
+
this.initGantt();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error('Failed to load dhtmlx-gantt:', error);
|
|
92
|
+
this.IsGanttLoading = false;
|
|
67
93
|
}
|
|
68
94
|
}
|
|
69
95
|
ngOnChanges() {
|
|
@@ -72,6 +98,8 @@ export class GanttTaskViewerComponent {
|
|
|
72
98
|
hasContainer: !!this.ganttContainer,
|
|
73
99
|
taskCount: this.tasks?.length || 0
|
|
74
100
|
});
|
|
101
|
+
if (!this.ganttLib)
|
|
102
|
+
return; // Library not yet loaded
|
|
75
103
|
if (this.ganttInitialized && this.ganttContainer) {
|
|
76
104
|
this.updateGanttData();
|
|
77
105
|
}
|
|
@@ -82,34 +110,35 @@ export class GanttTaskViewerComponent {
|
|
|
82
110
|
}
|
|
83
111
|
}
|
|
84
112
|
ngOnDestroy() {
|
|
85
|
-
if (this.ganttInitialized) {
|
|
86
|
-
|
|
113
|
+
if (this.ganttInitialized && this.ganttLib) {
|
|
114
|
+
this.ganttLib.clearAll();
|
|
87
115
|
this.ganttInitialized = false;
|
|
88
116
|
}
|
|
89
117
|
}
|
|
90
118
|
initGantt() {
|
|
91
119
|
try {
|
|
92
120
|
console.log('🎨 Initializing DHTMLX Gantt');
|
|
121
|
+
const g = this.ganttLib;
|
|
93
122
|
// IMPORTANT: Clear any previous configuration
|
|
94
|
-
|
|
123
|
+
g.clearAll();
|
|
95
124
|
// Configure Gantt layout and appearance
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
g.config.date_format = '%Y-%m-%d %H:%i';
|
|
126
|
+
g.config.scale_unit = 'day';
|
|
127
|
+
g.config.date_scale = '%d %M';
|
|
128
|
+
g.config.subscales = [];
|
|
129
|
+
g.config.show_progress = true;
|
|
130
|
+
g.config.show_links = true;
|
|
131
|
+
g.config.auto_types = true;
|
|
132
|
+
g.config.readonly = true; // Read-only for now
|
|
133
|
+
g.config.fit_tasks = true; // Auto-fit timeline to tasks
|
|
105
134
|
// Disable auto-scheduling - we calculate dates ourselves based on dependencies
|
|
106
|
-
|
|
135
|
+
g.config.auto_scheduling = false;
|
|
107
136
|
// Grid configuration
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
g.config.grid_width = 350;
|
|
138
|
+
g.config.row_height = 36;
|
|
139
|
+
g.config.scale_height = 0; // Hide date scale - dates are just for positioning
|
|
111
140
|
// Layout configuration - ensure timeline is visible
|
|
112
|
-
|
|
141
|
+
g.config.layout = {
|
|
113
142
|
css: "gantt_container",
|
|
114
143
|
rows: [
|
|
115
144
|
{
|
|
@@ -124,14 +153,14 @@ export class GanttTaskViewerComponent {
|
|
|
124
153
|
]
|
|
125
154
|
};
|
|
126
155
|
// Column configuration - only show task names, hide dates
|
|
127
|
-
|
|
156
|
+
g.config.columns = [
|
|
128
157
|
{ name: 'text', label: 'Task name', tree: true, width: '*' }
|
|
129
158
|
];
|
|
130
159
|
// Initialize Gantt in the container
|
|
131
|
-
|
|
160
|
+
g.init(this.ganttContainer.nativeElement);
|
|
132
161
|
this.ganttInitialized = true;
|
|
133
162
|
// Attach click event
|
|
134
|
-
|
|
163
|
+
g.attachEvent('onTaskClick', (id) => {
|
|
135
164
|
const originalTask = this.tasks.find(t => UUIDsEqual(t.ID, id));
|
|
136
165
|
if (originalTask) {
|
|
137
166
|
this.selectedTask = originalTask;
|
|
@@ -143,7 +172,7 @@ export class GanttTaskViewerComponent {
|
|
|
143
172
|
this.updateGanttData();
|
|
144
173
|
// Force resize after data load to ensure proper rendering
|
|
145
174
|
setTimeout(() => {
|
|
146
|
-
|
|
175
|
+
g.setSizes();
|
|
147
176
|
}, 0);
|
|
148
177
|
// Expand and select after render completes
|
|
149
178
|
setTimeout(() => {
|
|
@@ -160,17 +189,18 @@ export class GanttTaskViewerComponent {
|
|
|
160
189
|
return;
|
|
161
190
|
try {
|
|
162
191
|
console.log('📊 Updating Gantt data with', this.tasks.length, 'tasks');
|
|
192
|
+
const g = this.ganttLib;
|
|
163
193
|
const ganttData = this.convertToGanttFormat(this.tasks);
|
|
164
|
-
|
|
165
|
-
|
|
194
|
+
g.clearAll();
|
|
195
|
+
g.parse(ganttData);
|
|
166
196
|
// Log final parsed data for debugging
|
|
167
197
|
console.log('📋 Gantt data after parse:', {
|
|
168
|
-
tasks:
|
|
169
|
-
links:
|
|
198
|
+
tasks: g.getTaskByTime(),
|
|
199
|
+
links: g.getLinks()
|
|
170
200
|
});
|
|
171
201
|
// Force resize after data update
|
|
172
202
|
setTimeout(() => {
|
|
173
|
-
|
|
203
|
+
g.setSizes();
|
|
174
204
|
}, 0);
|
|
175
205
|
// Expand and select after render completes
|
|
176
206
|
setTimeout(() => {
|
|
@@ -260,15 +290,16 @@ export class GanttTaskViewerComponent {
|
|
|
260
290
|
}
|
|
261
291
|
});
|
|
262
292
|
// Add 1 day padding before and after
|
|
293
|
+
const g = this.ganttLib;
|
|
263
294
|
if (minDate) {
|
|
264
295
|
const paddedStart = new Date(minDate);
|
|
265
296
|
paddedStart.setDate(paddedStart.getDate() - 1);
|
|
266
|
-
|
|
297
|
+
g.config.start_date = paddedStart;
|
|
267
298
|
}
|
|
268
299
|
if (maxDate) {
|
|
269
300
|
const paddedEnd = new Date(maxDate);
|
|
270
301
|
paddedEnd.setDate(paddedEnd.getDate() + 1);
|
|
271
|
-
|
|
302
|
+
g.config.end_date = paddedEnd;
|
|
272
303
|
}
|
|
273
304
|
// Now create Gantt tasks with calculated dates
|
|
274
305
|
tasks.forEach((task) => {
|
|
@@ -357,36 +388,37 @@ export class GanttTaskViewerComponent {
|
|
|
357
388
|
this.isResizing = false;
|
|
358
389
|
// Resize gantt chart after panel resize completes
|
|
359
390
|
setTimeout(() => {
|
|
360
|
-
if (this.ganttInitialized) {
|
|
361
|
-
|
|
391
|
+
if (this.ganttInitialized && this.ganttLib) {
|
|
392
|
+
this.ganttLib.setSizes();
|
|
362
393
|
}
|
|
363
394
|
}, 0);
|
|
364
395
|
}
|
|
365
396
|
}
|
|
366
397
|
expandAllAndSelectRoot() {
|
|
367
398
|
try {
|
|
399
|
+
const g = this.ganttLib;
|
|
368
400
|
// Expand all tasks
|
|
369
|
-
|
|
370
|
-
|
|
401
|
+
g.eachTask((task) => {
|
|
402
|
+
g.open(task.id);
|
|
371
403
|
});
|
|
372
404
|
// Find and select the root task (task with parent = 0)
|
|
373
|
-
let
|
|
374
|
-
|
|
405
|
+
let rootTaskId = null;
|
|
406
|
+
g.eachTask((task) => {
|
|
375
407
|
if (task.parent === 0 || task.parent === '0') {
|
|
376
|
-
|
|
408
|
+
rootTaskId = task.id;
|
|
377
409
|
return false; // Stop iteration
|
|
378
410
|
}
|
|
379
411
|
});
|
|
380
|
-
if (
|
|
381
|
-
|
|
412
|
+
if (rootTaskId != null) {
|
|
413
|
+
g.selectTask(rootTaskId);
|
|
382
414
|
// Trigger task click event to open detail panel
|
|
383
|
-
const originalTask = this.tasks.find(t => UUIDsEqual(t.ID,
|
|
415
|
+
const originalTask = this.tasks.find(t => UUIDsEqual(t.ID, String(rootTaskId)));
|
|
384
416
|
if (originalTask) {
|
|
385
417
|
this.selectedTask = originalTask;
|
|
386
418
|
this.taskClicked.emit(originalTask);
|
|
387
419
|
}
|
|
388
420
|
}
|
|
389
|
-
console.log('✅ Expanded all tasks and selected root:',
|
|
421
|
+
console.log('✅ Expanded all tasks and selected root:', rootTaskId);
|
|
390
422
|
}
|
|
391
423
|
catch (error) {
|
|
392
424
|
console.error('❌ Error expanding/selecting tasks:', error);
|
|
@@ -400,50 +432,58 @@ export class GanttTaskViewerComponent {
|
|
|
400
432
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.ganttContainer = _t.first);
|
|
401
433
|
} }, hostBindings: function GanttTaskViewerComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
402
434
|
i0.ɵɵlistener("mousemove", function GanttTaskViewerComponent_mousemove_HostBindingHandler($event) { return ctx.handleResize($event); }, i0.ɵɵresolveDocument)("mouseup", function GanttTaskViewerComponent_mouseup_HostBindingHandler() { return ctx.stopResize(); }, i0.ɵɵresolveDocument);
|
|
403
|
-
} }, inputs: { tasks: "tasks", taskDependencies: "taskDependencies", agentRunMap: "agentRunMap" }, outputs: { taskClicked: "taskClicked", openEntityRecord: "openEntityRecord" }, features: [i0.ɵɵNgOnChangesFeature], decls: 3, vars: 2, consts: [["ganttContainer", ""], [1, "gantt-task-viewer"], [1, "no-tasks"], [1, "gantt-layout"], [1, "fas", "fa-chart-gantt"], [1, "gantt-container"], [1, "gantt-resizer"], [1, "task-detail-panel", 3, "width"], [1, "gantt-resizer", 3, "mousedown"], [1, "task-detail-panel"], [3, "closePanel", "openEntityRecord", "task", "agentRunId"]], template: function GanttTaskViewerComponent_Template(rf, ctx) { if (rf & 1) {
|
|
435
|
+
} }, inputs: { tasks: "tasks", taskDependencies: "taskDependencies", agentRunMap: "agentRunMap" }, outputs: { taskClicked: "taskClicked", openEntityRecord: "openEntityRecord" }, features: [i0.ɵɵNgOnChangesFeature], decls: 3, vars: 2, consts: [["ganttContainer", ""], [1, "gantt-task-viewer"], [1, "loading-container"], ["text", "Loading Gantt chart..."], [1, "no-tasks"], [1, "gantt-layout"], [1, "fas", "fa-chart-gantt"], [1, "gantt-container"], [1, "gantt-resizer"], [1, "task-detail-panel", 3, "width"], [1, "gantt-resizer", 3, "mousedown"], [1, "task-detail-panel"], [3, "closePanel", "openEntityRecord", "task", "agentRunId"]], template: function GanttTaskViewerComponent_Template(rf, ctx) { if (rf & 1) {
|
|
404
436
|
i0.ɵɵelementStart(0, "div", 1);
|
|
405
|
-
i0.ɵɵconditionalCreate(1, GanttTaskViewerComponent_Conditional_1_Template,
|
|
406
|
-
i0.ɵɵconditionalCreate(2, GanttTaskViewerComponent_Conditional_2_Template,
|
|
437
|
+
i0.ɵɵconditionalCreate(1, GanttTaskViewerComponent_Conditional_1_Template, 2, 0, "div", 2);
|
|
438
|
+
i0.ɵɵconditionalCreate(2, GanttTaskViewerComponent_Conditional_2_Template, 2, 2);
|
|
407
439
|
i0.ɵɵelementEnd();
|
|
408
440
|
} if (rf & 2) {
|
|
409
441
|
i0.ɵɵadvance();
|
|
410
|
-
i0.ɵɵconditional(
|
|
442
|
+
i0.ɵɵconditional(ctx.IsGanttLoading ? 1 : -1);
|
|
411
443
|
i0.ɵɵadvance();
|
|
412
|
-
i0.ɵɵconditional(ctx.
|
|
413
|
-
} }, dependencies: [TaskDetailPanelComponent], styles: [".gantt-task-viewer[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n\n .gantt-layout[_ngcontent-%COMP%] {\n display: flex;\n height: 600px;\n position: relative;\n }\n\n .gantt-container[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 400px;\n height: 100%;\n position: relative;\n }\n\n .gantt-resizer[_ngcontent-%COMP%] {\n width: 4px;\n background: var(--mj-border-default);\n cursor: col-resize;\n flex-shrink: 0;\n transition: background 0.2s;\n }\n\n .gantt-resizer[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n }\n\n .task-detail-panel[_ngcontent-%COMP%] {\n min-width: 300px;\n max-width: 600px;\n height: 100%;\n border-left: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n \n\n [_nghost-%COMP%] .gantt_container {\n font-family: inherit;\n font-size: 13px;\n }\n\n [_nghost-%COMP%] .gantt_grid_scale, \n [_nghost-%COMP%] .gantt_task_scale {\n background: var(--mj-bg-surface-sunken);\n border-bottom: 2px solid var(--mj-border-default);\n }\n\n [_nghost-%COMP%] .gantt_task .gantt_task_content {\n font-weight: 500;\n }\n\n .no-tasks[_ngcontent-%COMP%] {\n text-align: center;\n padding: 80px 20px;\n color: var(--mj-text-disabled);\n }\n\n .no-tasks[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 64px;\n opacity: 0.3;\n margin-bottom: 16px;\n }\n\n .no-tasks[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n }"] });
|
|
444
|
+
i0.ɵɵconditional(!ctx.IsGanttLoading ? 2 : -1);
|
|
445
|
+
} }, dependencies: [TaskDetailPanelComponent, SharedGenericModule, i1.LoadingComponent], styles: [".gantt-task-viewer[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n\n .gantt-layout[_ngcontent-%COMP%] {\n display: flex;\n height: 600px;\n position: relative;\n }\n\n .gantt-container[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 400px;\n height: 100%;\n position: relative;\n }\n\n .gantt-resizer[_ngcontent-%COMP%] {\n width: 4px;\n background: var(--mj-border-default);\n cursor: col-resize;\n flex-shrink: 0;\n transition: background 0.2s;\n }\n\n .gantt-resizer[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n }\n\n .task-detail-panel[_ngcontent-%COMP%] {\n min-width: 300px;\n max-width: 600px;\n height: 100%;\n border-left: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n \n\n [_nghost-%COMP%] .gantt_container {\n font-family: inherit;\n font-size: 13px;\n }\n\n [_nghost-%COMP%] .gantt_grid_scale, \n [_nghost-%COMP%] .gantt_task_scale {\n background: var(--mj-bg-surface-sunken);\n border-bottom: 2px solid var(--mj-border-default);\n }\n\n [_nghost-%COMP%] .gantt_task .gantt_task_content {\n font-weight: 500;\n }\n\n .loading-container[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 80px 20px;\n }\n\n .no-tasks[_ngcontent-%COMP%] {\n text-align: center;\n padding: 80px 20px;\n color: var(--mj-text-disabled);\n }\n\n .no-tasks[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 64px;\n opacity: 0.3;\n margin-bottom: 16px;\n }\n\n .no-tasks[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n }"] });
|
|
414
446
|
}
|
|
415
447
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(GanttTaskViewerComponent, [{
|
|
416
448
|
type: Component,
|
|
417
|
-
args: [{ selector: 'mj-gantt-task-viewer', standalone: true, imports: [TaskDetailPanelComponent], template: `
|
|
449
|
+
args: [{ selector: 'mj-gantt-task-viewer', standalone: true, imports: [TaskDetailPanelComponent, SharedGenericModule], template: `
|
|
418
450
|
<div class="gantt-task-viewer">
|
|
419
|
-
@if (
|
|
420
|
-
<div class="
|
|
421
|
-
<
|
|
422
|
-
<p>No tasks to display in Gantt view</p>
|
|
451
|
+
@if (IsGanttLoading) {
|
|
452
|
+
<div class="loading-container">
|
|
453
|
+
<mj-loading text="Loading Gantt chart..."></mj-loading>
|
|
423
454
|
</div>
|
|
424
455
|
}
|
|
425
|
-
|
|
426
|
-
@if (
|
|
427
|
-
|
|
428
|
-
<div
|
|
429
|
-
|
|
430
|
-
<
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
456
|
+
|
|
457
|
+
@if (!IsGanttLoading) {
|
|
458
|
+
@if (!tasks || tasks.length === 0) {
|
|
459
|
+
<div class="no-tasks">
|
|
460
|
+
<i class="fas fa-chart-gantt"></i>
|
|
461
|
+
<p>No tasks to display in Gantt view</p>
|
|
462
|
+
</div>
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
@if (tasks && tasks.length > 0) {
|
|
466
|
+
<div class="gantt-layout">
|
|
467
|
+
<div #ganttContainer class="gantt-container"></div>
|
|
468
|
+
@if (selectedTask) {
|
|
469
|
+
<div class="gantt-resizer"
|
|
470
|
+
(mousedown)="startResize($event)"></div>
|
|
471
|
+
}
|
|
472
|
+
@if (selectedTask) {
|
|
473
|
+
<div class="task-detail-panel" [style.width.px]="detailPanelWidth">
|
|
474
|
+
<mj-task-detail-panel
|
|
475
|
+
[task]="selectedTask"
|
|
476
|
+
[agentRunId]="getAgentRunId(selectedTask)"
|
|
477
|
+
(closePanel)="closeDetailPanel()"
|
|
478
|
+
(openEntityRecord)="onOpenEntityRecord($event)">
|
|
479
|
+
</mj-task-detail-panel>
|
|
480
|
+
</div>
|
|
481
|
+
}
|
|
482
|
+
</div>
|
|
483
|
+
}
|
|
444
484
|
}
|
|
445
485
|
</div>
|
|
446
|
-
`, styles: ["\n .gantt-task-viewer {\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n\n .gantt-layout {\n display: flex;\n height: 600px;\n position: relative;\n }\n\n .gantt-container {\n flex: 1;\n min-width: 400px;\n height: 100%;\n position: relative;\n }\n\n .gantt-resizer {\n width: 4px;\n background: var(--mj-border-default);\n cursor: col-resize;\n flex-shrink: 0;\n transition: background 0.2s;\n }\n\n .gantt-resizer:hover {\n background: var(--mj-brand-primary);\n }\n\n .task-detail-panel {\n min-width: 300px;\n max-width: 600px;\n height: 100%;\n border-left: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n /* Override DHTMLX Gantt default styles */\n :host ::ng-deep .gantt_container {\n font-family: inherit;\n font-size: 13px;\n }\n\n :host ::ng-deep .gantt_grid_scale,\n :host ::ng-deep .gantt_task_scale {\n background: var(--mj-bg-surface-sunken);\n border-bottom: 2px solid var(--mj-border-default);\n }\n\n :host ::ng-deep .gantt_task .gantt_task_content {\n font-weight: 500;\n }\n\n .no-tasks {\n text-align: center;\n padding: 80px 20px;\n color: var(--mj-text-disabled);\n }\n\n .no-tasks i {\n font-size: 64px;\n opacity: 0.3;\n margin-bottom: 16px;\n }\n\n .no-tasks p {\n margin: 0;\n font-size: 14px;\n }\n "] }]
|
|
486
|
+
`, styles: ["\n .gantt-task-viewer {\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n\n .gantt-layout {\n display: flex;\n height: 600px;\n position: relative;\n }\n\n .gantt-container {\n flex: 1;\n min-width: 400px;\n height: 100%;\n position: relative;\n }\n\n .gantt-resizer {\n width: 4px;\n background: var(--mj-border-default);\n cursor: col-resize;\n flex-shrink: 0;\n transition: background 0.2s;\n }\n\n .gantt-resizer:hover {\n background: var(--mj-brand-primary);\n }\n\n .task-detail-panel {\n min-width: 300px;\n max-width: 600px;\n height: 100%;\n border-left: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n /* Override DHTMLX Gantt default styles */\n :host ::ng-deep .gantt_container {\n font-family: inherit;\n font-size: 13px;\n }\n\n :host ::ng-deep .gantt_grid_scale,\n :host ::ng-deep .gantt_task_scale {\n background: var(--mj-bg-surface-sunken);\n border-bottom: 2px solid var(--mj-border-default);\n }\n\n :host ::ng-deep .gantt_task .gantt_task_content {\n font-weight: 500;\n }\n\n .loading-container {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 80px 20px;\n }\n\n .no-tasks {\n text-align: center;\n padding: 80px 20px;\n color: var(--mj-text-disabled);\n }\n\n .no-tasks i {\n font-size: 64px;\n opacity: 0.3;\n margin-bottom: 16px;\n }\n\n .no-tasks p {\n margin: 0;\n font-size: 14px;\n }\n "] }]
|
|
447
487
|
}], null, { tasks: [{
|
|
448
488
|
type: Input
|
|
449
489
|
}], taskDependencies: [{
|
|
@@ -464,5 +504,5 @@ export class GanttTaskViewerComponent {
|
|
|
464
504
|
type: HostListener,
|
|
465
505
|
args: ['document:mouseup']
|
|
466
506
|
}] }); })();
|
|
467
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(GanttTaskViewerComponent, { className: "GanttTaskViewerComponent", filePath: "src/lib/components/gantt-task-viewer.component.ts", lineNumber:
|
|
507
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(GanttTaskViewerComponent, { className: "GanttTaskViewerComponent", filePath: "src/lib/components/gantt-task-viewer.component.ts", lineNumber: 137 }); })();
|
|
468
508
|
//# sourceMappingURL=gantt-task-viewer.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gantt-task-viewer.component.js","sourceRoot":"","sources":["../../../src/lib/components/gantt-task-viewer.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, OnChanges, AfterViewInit, ElementRef, ViewChild, OnDestroy, HostListener } from '@angular/core';\n\nimport { MJTaskEntity, MJTaskDependencyEntity } from '@memberjunction/core-entities';\nimport { gantt } from 'dhtmlx-gantt';\nimport { TaskDetailPanelComponent } from './task-detail-panel.component';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n/**\n * Gantt chart view for tasks using DHTMLX Gantt\n */\n@Component({\n selector: 'mj-gantt-task-viewer',\n standalone: true,\n imports: [TaskDetailPanelComponent],\n template: `\n <div class=\"gantt-task-viewer\">\n @if (!tasks || tasks.length === 0) {\n <div class=\"no-tasks\">\n <i class=\"fas fa-chart-gantt\"></i>\n <p>No tasks to display in Gantt view</p>\n </div>\n }\n \n @if (tasks && tasks.length > 0) {\n <div class=\"gantt-layout\">\n <div #ganttContainer class=\"gantt-container\"></div>\n @if (selectedTask) {\n <div class=\"gantt-resizer\"\n (mousedown)=\"startResize($event)\"></div>\n }\n @if (selectedTask) {\n <div class=\"task-detail-panel\" [style.width.px]=\"detailPanelWidth\">\n <mj-task-detail-panel\n [task]=\"selectedTask\"\n [agentRunId]=\"getAgentRunId(selectedTask)\"\n (closePanel)=\"closeDetailPanel()\"\n (openEntityRecord)=\"onOpenEntityRecord($event)\">\n </mj-task-detail-panel>\n </div>\n }\n </div>\n }\n </div>\n `,\n styles: [`\n .gantt-task-viewer {\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n\n .gantt-layout {\n display: flex;\n height: 600px;\n position: relative;\n }\n\n .gantt-container {\n flex: 1;\n min-width: 400px;\n height: 100%;\n position: relative;\n }\n\n .gantt-resizer {\n width: 4px;\n background: var(--mj-border-default);\n cursor: col-resize;\n flex-shrink: 0;\n transition: background 0.2s;\n }\n\n .gantt-resizer:hover {\n background: var(--mj-brand-primary);\n }\n\n .task-detail-panel {\n min-width: 300px;\n max-width: 600px;\n height: 100%;\n border-left: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n /* Override DHTMLX Gantt default styles */\n :host ::ng-deep .gantt_container {\n font-family: inherit;\n font-size: 13px;\n }\n\n :host ::ng-deep .gantt_grid_scale,\n :host ::ng-deep .gantt_task_scale {\n background: var(--mj-bg-surface-sunken);\n border-bottom: 2px solid var(--mj-border-default);\n }\n\n :host ::ng-deep .gantt_task .gantt_task_content {\n font-weight: 500;\n }\n\n .no-tasks {\n text-align: center;\n padding: 80px 20px;\n color: var(--mj-text-disabled);\n }\n\n .no-tasks i {\n font-size: 64px;\n opacity: 0.3;\n margin-bottom: 16px;\n }\n\n .no-tasks p {\n margin: 0;\n font-size: 14px;\n }\n `]\n})\nexport class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDestroy {\n @Input() tasks: MJTaskEntity[] = [];\n @Input() taskDependencies: MJTaskDependencyEntity[] = [];\n @Input() agentRunMap?: Map<string, string>; // Maps TaskID -> AgentRunID\n @Output() taskClicked = new EventEmitter<MJTaskEntity>();\n @Output() openEntityRecord = new EventEmitter<{ entityName: string; recordId: string }>();\n\n @ViewChild('ganttContainer', { static: false }) ganttContainer!: ElementRef<HTMLDivElement>;\n\n public selectedTask: MJTaskEntity | null = null;\n public detailPanelWidth: number = 400;\n\n private ganttInitialized = false;\n private isResizing = false;\n private resizeStartX = 0;\n private resizeStartWidth = 0;\n\n ngAfterViewInit() {\n console.log('🔧 ngAfterViewInit called', {\n taskCount: this.tasks?.length || 0,\n hasContainer: !!this.ganttContainer\n });\n\n if (this.tasks && this.tasks.length > 0 && this.ganttContainer) {\n this.initGantt();\n }\n }\n\n ngOnChanges() {\n console.log('🔄 ngOnChanges called', {\n initialized: this.ganttInitialized,\n hasContainer: !!this.ganttContainer,\n taskCount: this.tasks?.length || 0\n });\n\n if (this.ganttInitialized && this.ganttContainer) {\n this.updateGanttData();\n } else if (!this.ganttInitialized && this.ganttContainer && this.tasks && this.tasks.length > 0) {\n // Initialize if we have container and tasks but haven't initialized yet\n console.log('🎨 Late initialization - gantt not initialized but container and tasks available');\n this.initGantt();\n }\n }\n\n ngOnDestroy() {\n if (this.ganttInitialized) {\n gantt.clearAll();\n this.ganttInitialized = false;\n }\n }\n\n private initGantt(): void {\n try {\n console.log('🎨 Initializing DHTMLX Gantt');\n\n // IMPORTANT: Clear any previous configuration\n gantt.clearAll();\n\n // Configure Gantt layout and appearance\n gantt.config.date_format = '%Y-%m-%d %H:%i';\n gantt.config.scale_unit = 'day';\n gantt.config.date_scale = '%d %M';\n gantt.config.subscales = [];\n gantt.config.show_progress = true;\n gantt.config.show_links = true;\n gantt.config.auto_types = true;\n gantt.config.readonly = true; // Read-only for now\n gantt.config.fit_tasks = true; // Auto-fit timeline to tasks\n\n // Disable auto-scheduling - we calculate dates ourselves based on dependencies\n gantt.config.auto_scheduling = false;\n\n // Grid configuration\n gantt.config.grid_width = 350;\n gantt.config.row_height = 36;\n gantt.config.scale_height = 0; // Hide date scale - dates are just for positioning\n\n // Layout configuration - ensure timeline is visible\n gantt.config.layout = {\n css: \"gantt_container\",\n rows: [\n {\n cols: [\n { view: \"grid\", group: \"grids\", scrollY: \"scrollVer\" },\n { resizer: true, width: 1 },\n { view: \"timeline\", scrollX: \"scrollHor\", scrollY: \"scrollVer\" },\n { view: \"scrollbar\", id: \"scrollVer\", group: \"vertical\" }\n ]\n },\n { view: \"scrollbar\", id: \"scrollHor\", group: \"horizontal\" }\n ]\n };\n\n // Column configuration - only show task names, hide dates\n gantt.config.columns = [\n { name: 'text', label: 'Task name', tree: true, width: '*' }\n ];\n\n // Initialize Gantt in the container\n gantt.init(this.ganttContainer.nativeElement);\n this.ganttInitialized = true;\n\n // Attach click event\n gantt.attachEvent('onTaskClick', (id: string) => {\n const originalTask = this.tasks.find(t => UUIDsEqual(t.ID, id));\n if (originalTask) {\n this.selectedTask = originalTask;\n this.taskClicked.emit(originalTask);\n }\n return true;\n });\n\n // Load data\n this.updateGanttData();\n\n // Force resize after data load to ensure proper rendering\n setTimeout(() => {\n gantt.setSizes();\n }, 0);\n\n // Expand and select after render completes\n setTimeout(() => {\n this.expandAllAndSelectRoot();\n }, 100);\n\n console.log('✅ DHTMLX Gantt initialized successfully');\n } catch (error) {\n console.error('❌ Error initializing Gantt:', error);\n }\n }\n\n private updateGanttData(): void {\n if (!this.ganttInitialized) return;\n\n try {\n console.log('📊 Updating Gantt data with', this.tasks.length, 'tasks');\n\n const ganttData = this.convertToGanttFormat(this.tasks);\n gantt.clearAll();\n gantt.parse(ganttData);\n\n // Log final parsed data for debugging\n console.log('📋 Gantt data after parse:', {\n tasks: gantt.getTaskByTime(),\n links: gantt.getLinks()\n });\n\n // Force resize after data update\n setTimeout(() => {\n gantt.setSizes();\n }, 0);\n\n // Expand and select after render completes\n setTimeout(() => {\n this.expandAllAndSelectRoot();\n }, 100);\n\n console.log('✅ Gantt data updated');\n } catch (error) {\n console.error('❌ Error updating Gantt data:', error);\n }\n }\n\n private convertToGanttFormat(tasks: MJTaskEntity[]): { data: any[], links: any[] } {\n const data: any[] = [];\n const links: any[] = [];\n\n console.log('🔍 Converting tasks:', tasks);\n console.log('🔗 Task dependencies:', this.taskDependencies);\n\n // Build a map of task ID to task for quick lookup\n const taskMap = new Map<string, MJTaskEntity>();\n tasks.forEach(t => taskMap.set(t.ID, t));\n\n // Build dependency map: taskId -> array of tasks it depends on\n const dependencyMap = new Map<string, string[]>();\n this.taskDependencies.forEach(dep => {\n if (!dependencyMap.has(dep.TaskID)) {\n dependencyMap.set(dep.TaskID, []);\n }\n dependencyMap.get(dep.TaskID)!.push(dep.DependsOnTaskID);\n });\n\n // Calculate start dates based on dependencies\n const taskStartDates = new Map<string, Date>();\n const baseDate = new Date();\n baseDate.setHours(0, 0, 0, 0);\n\n // Recursive function to calculate start date for a task\n const calculateStartDate = (taskId: string, visited = new Set<string>()): Date => {\n // Prevent circular dependencies\n if (visited.has(taskId)) {\n return new Date(baseDate);\n }\n visited.add(taskId);\n\n // If already calculated, return it\n if (taskStartDates.has(taskId)) {\n return taskStartDates.get(taskId)!;\n }\n\n const task = taskMap.get(taskId);\n if (!task) return new Date(baseDate);\n\n const dependencies = dependencyMap.get(taskId) || [];\n\n if (dependencies.length === 0) {\n // No dependencies - use base date or task's actual start date\n const startDate = task.StartedAt ? new Date(task.StartedAt) : new Date(baseDate);\n taskStartDates.set(taskId, startDate);\n return startDate;\n }\n\n // Has dependencies - start after the latest dependency ends\n let latestEnd = new Date(baseDate);\n for (const depId of dependencies) {\n const depTask = taskMap.get(depId);\n if (depTask) {\n const depStart = calculateStartDate(depId, new Set(visited));\n const depDuration = this.calculateDuration(depTask);\n const depEnd = new Date(depStart);\n depEnd.setDate(depEnd.getDate() + depDuration);\n\n if (depEnd > latestEnd) {\n latestEnd = depEnd;\n }\n }\n }\n\n taskStartDates.set(taskId, latestEnd);\n return latestEnd;\n };\n\n // Calculate start dates for all tasks\n tasks.forEach(task => calculateStartDate(task.ID));\n\n // Calculate min/max dates for timeline display range\n let minDate: Date | null = null;\n let maxDate: Date | null = null;\n\n tasks.forEach(task => {\n const startDate = taskStartDates.get(task.ID);\n if (startDate) {\n const duration = this.calculateDuration(task);\n const endDate = new Date(startDate);\n endDate.setDate(endDate.getDate() + duration);\n\n if (!minDate || startDate < minDate) {\n minDate = startDate;\n }\n if (!maxDate || endDate > maxDate) {\n maxDate = endDate;\n }\n }\n });\n\n // Add 1 day padding before and after\n if (minDate) {\n const paddedStart = new Date(minDate);\n paddedStart.setDate(paddedStart.getDate() - 1);\n gantt.config.start_date = paddedStart;\n }\n if (maxDate) {\n const paddedEnd = new Date(maxDate);\n paddedEnd.setDate(paddedEnd.getDate() + 1);\n gantt.config.end_date = paddedEnd;\n }\n\n // Now create Gantt tasks with calculated dates\n tasks.forEach((task) => {\n console.log('📝 Processing task:', {\n ID: task.ID,\n Name: task.Name,\n ParentID: task.ParentID\n });\n\n // Calculate progress (0-1 scale for DHTMLX)\n let progress = (task.PercentComplete || 0) / 100;\n if (task.Status === 'Complete') {\n progress = 1;\n }\n\n const duration = this.calculateDuration(task);\n const startDate = taskStartDates.get(task.ID) || new Date(baseDate);\n\n const ganttTask: any = {\n id: task.ID,\n text: task.Name || 'Untitled Task',\n start_date: this.formatDateForDHTMLX(startDate),\n duration: duration,\n progress: progress\n };\n\n // Add parent relationship for tree structure (not dependency)\n if (task.ParentID) {\n ganttTask.parent = task.ParentID;\n } else {\n // Root tasks need parent: 0\n ganttTask.parent = 0;\n }\n\n console.log('✅ Created Gantt task:', ganttTask);\n data.push(ganttTask);\n });\n\n // Create links from MJTaskDependencyEntity records\n this.taskDependencies.forEach((dep, index) => {\n links.push({\n id: dep.ID || `link_${index}`,\n source: dep.DependsOnTaskID, // The task being depended on\n target: dep.TaskID, // The task that depends on it\n type: '0' // finish-to-start (DHTMLX type 0)\n });\n console.log(`🔗 Created link: ${dep.DependsOnTaskID} -> ${dep.TaskID}`);\n });\n\n console.log('📊 Final DHTMLX format:', { data, links });\n return { data, links };\n }\n\n private calculateDuration(task: MJTaskEntity): number {\n if (task.StartedAt && task.DueAt) {\n const startDate = new Date(task.StartedAt);\n const endDate = new Date(task.DueAt);\n return Math.max(1, Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)));\n }\n return 1; // Default to 1 day\n }\n\n private formatDateForDHTMLX(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}-${month}-${day} 00:00`;\n }\n\n public getAgentRunId(task: MJTaskEntity): string | null {\n return this.agentRunMap?.get(task.ID) || null;\n }\n\n public closeDetailPanel(): void {\n this.selectedTask = null;\n }\n\n public onOpenEntityRecord(event: { entityName: string; recordId: string }): void {\n this.openEntityRecord.emit(event);\n }\n\n public startResize(event: MouseEvent): void {\n this.isResizing = true;\n this.resizeStartX = event.clientX;\n this.resizeStartWidth = this.detailPanelWidth;\n event.preventDefault();\n }\n\n @HostListener('document:mousemove', ['$event'])\n handleResize(event: MouseEvent): void {\n if (!this.isResizing) return;\n\n const delta = this.resizeStartX - event.clientX;\n const newWidth = this.resizeStartWidth + delta;\n\n // Constrain width between min and max\n this.detailPanelWidth = Math.max(300, Math.min(600, newWidth));\n }\n\n @HostListener('document:mouseup')\n stopResize(): void {\n if (this.isResizing) {\n this.isResizing = false;\n // Resize gantt chart after panel resize completes\n setTimeout(() => {\n if (this.ganttInitialized) {\n gantt.setSizes();\n }\n }, 0);\n }\n }\n\n private expandAllAndSelectRoot(): void {\n try {\n // Expand all tasks\n gantt.eachTask((task: any) => {\n gantt.open(task.id);\n });\n\n // Find and select the root task (task with parent = 0)\n let rootTask: any = null;\n gantt.eachTask((task: any) => {\n if (task.parent === 0 || task.parent === '0') {\n rootTask = task;\n return false; // Stop iteration\n }\n });\n\n if (rootTask) {\n gantt.selectTask(rootTask.id);\n // Trigger task click event to open detail panel\n const originalTask = this.tasks.find(t => UUIDsEqual(t.ID, rootTask.id));\n if (originalTask) {\n this.selectedTask = originalTask;\n this.taskClicked.emit(originalTask);\n }\n }\n\n console.log('✅ Expanded all tasks and selected root:', rootTask?.id);\n } catch (error) {\n console.error('❌ Error expanding/selecting tasks:', error);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"gantt-task-viewer.component.js","sourceRoot":"","sources":["../../../src/lib/components/gantt-task-viewer.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, OnChanges, AfterViewInit, ElementRef, ViewChild, OnDestroy, HostListener } from '@angular/core';\n\nimport { MJTaskEntity, MJTaskDependencyEntity } from '@memberjunction/core-entities';\nimport type { GanttStatic, Task as GanttTask, Link as GanttLink } from 'dhtmlx-gantt';\nimport { TaskDetailPanelComponent } from './task-detail-panel.component';\nimport { SharedGenericModule } from '@memberjunction/ng-shared-generic';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n/**\n * Gantt chart view for tasks using DHTMLX Gantt\n */\n@Component({\n selector: 'mj-gantt-task-viewer',\n standalone: true,\n imports: [TaskDetailPanelComponent, SharedGenericModule],\n template: `\n <div class=\"gantt-task-viewer\">\n @if (IsGanttLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading Gantt chart...\"></mj-loading>\n </div>\n }\n\n @if (!IsGanttLoading) {\n @if (!tasks || tasks.length === 0) {\n <div class=\"no-tasks\">\n <i class=\"fas fa-chart-gantt\"></i>\n <p>No tasks to display in Gantt view</p>\n </div>\n }\n\n @if (tasks && tasks.length > 0) {\n <div class=\"gantt-layout\">\n <div #ganttContainer class=\"gantt-container\"></div>\n @if (selectedTask) {\n <div class=\"gantt-resizer\"\n (mousedown)=\"startResize($event)\"></div>\n }\n @if (selectedTask) {\n <div class=\"task-detail-panel\" [style.width.px]=\"detailPanelWidth\">\n <mj-task-detail-panel\n [task]=\"selectedTask\"\n [agentRunId]=\"getAgentRunId(selectedTask)\"\n (closePanel)=\"closeDetailPanel()\"\n (openEntityRecord)=\"onOpenEntityRecord($event)\">\n </mj-task-detail-panel>\n </div>\n }\n </div>\n }\n }\n </div>\n `,\n styles: [`\n .gantt-task-viewer {\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n\n .gantt-layout {\n display: flex;\n height: 600px;\n position: relative;\n }\n\n .gantt-container {\n flex: 1;\n min-width: 400px;\n height: 100%;\n position: relative;\n }\n\n .gantt-resizer {\n width: 4px;\n background: var(--mj-border-default);\n cursor: col-resize;\n flex-shrink: 0;\n transition: background 0.2s;\n }\n\n .gantt-resizer:hover {\n background: var(--mj-brand-primary);\n }\n\n .task-detail-panel {\n min-width: 300px;\n max-width: 600px;\n height: 100%;\n border-left: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n }\n\n /* Override DHTMLX Gantt default styles */\n :host ::ng-deep .gantt_container {\n font-family: inherit;\n font-size: 13px;\n }\n\n :host ::ng-deep .gantt_grid_scale,\n :host ::ng-deep .gantt_task_scale {\n background: var(--mj-bg-surface-sunken);\n border-bottom: 2px solid var(--mj-border-default);\n }\n\n :host ::ng-deep .gantt_task .gantt_task_content {\n font-weight: 500;\n }\n\n .loading-container {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 80px 20px;\n }\n\n .no-tasks {\n text-align: center;\n padding: 80px 20px;\n color: var(--mj-text-disabled);\n }\n\n .no-tasks i {\n font-size: 64px;\n opacity: 0.3;\n margin-bottom: 16px;\n }\n\n .no-tasks p {\n margin: 0;\n font-size: 14px;\n }\n `]\n})\nexport class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDestroy {\n @Input() tasks: MJTaskEntity[] = [];\n @Input() taskDependencies: MJTaskDependencyEntity[] = [];\n @Input() agentRunMap?: Map<string, string>; // Maps TaskID -> AgentRunID\n @Output() taskClicked = new EventEmitter<MJTaskEntity>();\n @Output() openEntityRecord = new EventEmitter<{ entityName: string; recordId: string }>();\n\n @ViewChild('ganttContainer', { static: false }) ganttContainer!: ElementRef<HTMLDivElement>;\n\n public selectedTask: MJTaskEntity | null = null;\n public detailPanelWidth: number = 400;\n public IsGanttLoading = true;\n\n private ganttLib: GanttStatic | null = null;\n private ganttInitialized = false;\n private isResizing = false;\n private resizeStartX = 0;\n private resizeStartWidth = 0;\n\n async ngAfterViewInit() {\n console.log('🔧 ngAfterViewInit called', {\n taskCount: this.tasks?.length || 0,\n hasContainer: !!this.ganttContainer\n });\n\n try {\n const module = await import('dhtmlx-gantt');\n this.ganttLib = module.gantt;\n this.IsGanttLoading = false;\n\n if (this.tasks && this.tasks.length > 0 && this.ganttContainer) {\n this.initGantt();\n }\n } catch (error) {\n console.error('Failed to load dhtmlx-gantt:', error);\n this.IsGanttLoading = false;\n }\n }\n\n ngOnChanges() {\n console.log('🔄 ngOnChanges called', {\n initialized: this.ganttInitialized,\n hasContainer: !!this.ganttContainer,\n taskCount: this.tasks?.length || 0\n });\n\n if (!this.ganttLib) return; // Library not yet loaded\n\n if (this.ganttInitialized && this.ganttContainer) {\n this.updateGanttData();\n } else if (!this.ganttInitialized && this.ganttContainer && this.tasks && this.tasks.length > 0) {\n // Initialize if we have container and tasks but haven't initialized yet\n console.log('🎨 Late initialization - gantt not initialized but container and tasks available');\n this.initGantt();\n }\n }\n\n ngOnDestroy() {\n if (this.ganttInitialized && this.ganttLib) {\n this.ganttLib.clearAll();\n this.ganttInitialized = false;\n }\n }\n\n private initGantt(): void {\n try {\n console.log('🎨 Initializing DHTMLX Gantt');\n const g = this.ganttLib!;\n\n // IMPORTANT: Clear any previous configuration\n g.clearAll();\n\n // Configure Gantt layout and appearance\n g.config.date_format = '%Y-%m-%d %H:%i';\n g.config.scale_unit = 'day';\n g.config.date_scale = '%d %M';\n g.config.subscales = [];\n g.config.show_progress = true;\n g.config.show_links = true;\n g.config.auto_types = true;\n g.config.readonly = true; // Read-only for now\n g.config.fit_tasks = true; // Auto-fit timeline to tasks\n\n // Disable auto-scheduling - we calculate dates ourselves based on dependencies\n g.config.auto_scheduling = false;\n\n // Grid configuration\n g.config.grid_width = 350;\n g.config.row_height = 36;\n g.config.scale_height = 0; // Hide date scale - dates are just for positioning\n\n // Layout configuration - ensure timeline is visible\n g.config.layout = {\n css: \"gantt_container\",\n rows: [\n {\n cols: [\n { view: \"grid\", group: \"grids\", scrollY: \"scrollVer\" },\n { resizer: true, width: 1 },\n { view: \"timeline\", scrollX: \"scrollHor\", scrollY: \"scrollVer\" },\n { view: \"scrollbar\", id: \"scrollVer\", group: \"vertical\" }\n ]\n },\n { view: \"scrollbar\", id: \"scrollHor\", group: \"horizontal\" }\n ]\n };\n\n // Column configuration - only show task names, hide dates\n g.config.columns = [\n { name: 'text', label: 'Task name', tree: true, width: '*' }\n ];\n\n // Initialize Gantt in the container\n g.init(this.ganttContainer.nativeElement);\n this.ganttInitialized = true;\n\n // Attach click event\n g.attachEvent('onTaskClick', (id: string) => {\n const originalTask = this.tasks.find(t => UUIDsEqual(t.ID, id));\n if (originalTask) {\n this.selectedTask = originalTask;\n this.taskClicked.emit(originalTask);\n }\n return true;\n });\n\n // Load data\n this.updateGanttData();\n\n // Force resize after data load to ensure proper rendering\n setTimeout(() => {\n g.setSizes();\n }, 0);\n\n // Expand and select after render completes\n setTimeout(() => {\n this.expandAllAndSelectRoot();\n }, 100);\n\n console.log('✅ DHTMLX Gantt initialized successfully');\n } catch (error) {\n console.error('❌ Error initializing Gantt:', error);\n }\n }\n\n private updateGanttData(): void {\n if (!this.ganttInitialized) return;\n\n try {\n console.log('📊 Updating Gantt data with', this.tasks.length, 'tasks');\n const g = this.ganttLib!;\n\n const ganttData = this.convertToGanttFormat(this.tasks);\n g.clearAll();\n g.parse(ganttData);\n\n // Log final parsed data for debugging\n console.log('📋 Gantt data after parse:', {\n tasks: g.getTaskByTime(),\n links: g.getLinks()\n });\n\n // Force resize after data update\n setTimeout(() => {\n g.setSizes();\n }, 0);\n\n // Expand and select after render completes\n setTimeout(() => {\n this.expandAllAndSelectRoot();\n }, 100);\n\n console.log('✅ Gantt data updated');\n } catch (error) {\n console.error('❌ Error updating Gantt data:', error);\n }\n }\n\n private convertToGanttFormat(tasks: MJTaskEntity[]): { data: GanttTask[], links: GanttLink[] } {\n const data: GanttTask[] = [];\n const links: GanttLink[] = [];\n\n console.log('🔍 Converting tasks:', tasks);\n console.log('🔗 Task dependencies:', this.taskDependencies);\n\n // Build a map of task ID to task for quick lookup\n const taskMap = new Map<string, MJTaskEntity>();\n tasks.forEach(t => taskMap.set(t.ID, t));\n\n // Build dependency map: taskId -> array of tasks it depends on\n const dependencyMap = new Map<string, string[]>();\n this.taskDependencies.forEach(dep => {\n if (!dependencyMap.has(dep.TaskID)) {\n dependencyMap.set(dep.TaskID, []);\n }\n dependencyMap.get(dep.TaskID)!.push(dep.DependsOnTaskID);\n });\n\n // Calculate start dates based on dependencies\n const taskStartDates = new Map<string, Date>();\n const baseDate = new Date();\n baseDate.setHours(0, 0, 0, 0);\n\n // Recursive function to calculate start date for a task\n const calculateStartDate = (taskId: string, visited = new Set<string>()): Date => {\n // Prevent circular dependencies\n if (visited.has(taskId)) {\n return new Date(baseDate);\n }\n visited.add(taskId);\n\n // If already calculated, return it\n if (taskStartDates.has(taskId)) {\n return taskStartDates.get(taskId)!;\n }\n\n const task = taskMap.get(taskId);\n if (!task) return new Date(baseDate);\n\n const dependencies = dependencyMap.get(taskId) || [];\n\n if (dependencies.length === 0) {\n // No dependencies - use base date or task's actual start date\n const startDate = task.StartedAt ? new Date(task.StartedAt) : new Date(baseDate);\n taskStartDates.set(taskId, startDate);\n return startDate;\n }\n\n // Has dependencies - start after the latest dependency ends\n let latestEnd = new Date(baseDate);\n for (const depId of dependencies) {\n const depTask = taskMap.get(depId);\n if (depTask) {\n const depStart = calculateStartDate(depId, new Set(visited));\n const depDuration = this.calculateDuration(depTask);\n const depEnd = new Date(depStart);\n depEnd.setDate(depEnd.getDate() + depDuration);\n\n if (depEnd > latestEnd) {\n latestEnd = depEnd;\n }\n }\n }\n\n taskStartDates.set(taskId, latestEnd);\n return latestEnd;\n };\n\n // Calculate start dates for all tasks\n tasks.forEach(task => calculateStartDate(task.ID));\n\n // Calculate min/max dates for timeline display range\n let minDate: Date | null = null;\n let maxDate: Date | null = null;\n\n tasks.forEach(task => {\n const startDate = taskStartDates.get(task.ID);\n if (startDate) {\n const duration = this.calculateDuration(task);\n const endDate = new Date(startDate);\n endDate.setDate(endDate.getDate() + duration);\n\n if (!minDate || startDate < minDate) {\n minDate = startDate;\n }\n if (!maxDate || endDate > maxDate) {\n maxDate = endDate;\n }\n }\n });\n\n // Add 1 day padding before and after\n const g = this.ganttLib!;\n if (minDate) {\n const paddedStart = new Date(minDate);\n paddedStart.setDate(paddedStart.getDate() - 1);\n g.config.start_date = paddedStart;\n }\n if (maxDate) {\n const paddedEnd = new Date(maxDate);\n paddedEnd.setDate(paddedEnd.getDate() + 1);\n g.config.end_date = paddedEnd;\n }\n\n // Now create Gantt tasks with calculated dates\n tasks.forEach((task) => {\n console.log('📝 Processing task:', {\n ID: task.ID,\n Name: task.Name,\n ParentID: task.ParentID\n });\n\n // Calculate progress (0-1 scale for DHTMLX)\n let progress = (task.PercentComplete || 0) / 100;\n if (task.Status === 'Complete') {\n progress = 1;\n }\n\n const duration = this.calculateDuration(task);\n const startDate = taskStartDates.get(task.ID) || new Date(baseDate);\n\n const ganttTask: Partial<GanttTask> & Pick<GanttTask, 'id'> = {\n id: task.ID,\n text: task.Name || 'Untitled Task',\n start_date: this.formatDateForDHTMLX(startDate) as unknown as Date,\n duration: duration,\n progress: progress\n };\n\n // Add parent relationship for tree structure (not dependency)\n if (task.ParentID) {\n ganttTask.parent = task.ParentID;\n } else {\n // Root tasks need parent: 0\n ganttTask.parent = 0;\n }\n\n console.log('✅ Created Gantt task:', ganttTask);\n data.push(ganttTask as GanttTask);\n });\n\n // Create links from MJTaskDependencyEntity records\n this.taskDependencies.forEach((dep, index) => {\n links.push({\n id: dep.ID || `link_${index}`,\n source: dep.DependsOnTaskID, // The task being depended on\n target: dep.TaskID, // The task that depends on it\n type: '0' // finish-to-start (DHTMLX type 0)\n });\n console.log(`🔗 Created link: ${dep.DependsOnTaskID} -> ${dep.TaskID}`);\n });\n\n console.log('📊 Final DHTMLX format:', { data, links });\n return { data, links };\n }\n\n private calculateDuration(task: MJTaskEntity): number {\n if (task.StartedAt && task.DueAt) {\n const startDate = new Date(task.StartedAt);\n const endDate = new Date(task.DueAt);\n return Math.max(1, Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)));\n }\n return 1; // Default to 1 day\n }\n\n private formatDateForDHTMLX(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}-${month}-${day} 00:00`;\n }\n\n public getAgentRunId(task: MJTaskEntity): string | null {\n return this.agentRunMap?.get(task.ID) || null;\n }\n\n public closeDetailPanel(): void {\n this.selectedTask = null;\n }\n\n public onOpenEntityRecord(event: { entityName: string; recordId: string }): void {\n this.openEntityRecord.emit(event);\n }\n\n public startResize(event: MouseEvent): void {\n this.isResizing = true;\n this.resizeStartX = event.clientX;\n this.resizeStartWidth = this.detailPanelWidth;\n event.preventDefault();\n }\n\n @HostListener('document:mousemove', ['$event'])\n handleResize(event: MouseEvent): void {\n if (!this.isResizing) return;\n\n const delta = this.resizeStartX - event.clientX;\n const newWidth = this.resizeStartWidth + delta;\n\n // Constrain width between min and max\n this.detailPanelWidth = Math.max(300, Math.min(600, newWidth));\n }\n\n @HostListener('document:mouseup')\n stopResize(): void {\n if (this.isResizing) {\n this.isResizing = false;\n // Resize gantt chart after panel resize completes\n setTimeout(() => {\n if (this.ganttInitialized && this.ganttLib) {\n this.ganttLib.setSizes();\n }\n }, 0);\n }\n }\n\n private expandAllAndSelectRoot(): void {\n try {\n const g = this.ganttLib!;\n\n // Expand all tasks\n g.eachTask((task: GanttTask) => {\n g.open(task.id);\n });\n\n // Find and select the root task (task with parent = 0)\n let rootTaskId: string | number | null = null;\n g.eachTask((task: GanttTask) => {\n if (task.parent === 0 || task.parent === '0') {\n rootTaskId = task.id;\n return false; // Stop iteration\n }\n });\n\n if (rootTaskId != null) {\n g.selectTask(rootTaskId);\n // Trigger task click event to open detail panel\n const originalTask = this.tasks.find(t => UUIDsEqual(t.ID, String(rootTaskId)));\n if (originalTask) {\n this.selectedTask = originalTask;\n this.taskClicked.emit(originalTask);\n }\n }\n\n console.log('✅ Expanded all tasks and selected root:', rootTaskId);\n } catch (error) {\n console.error('❌ Error expanding/selecting tasks:', error);\n }\n }\n}\n"]}
|
|
@@ -11,7 +11,8 @@ export class NgTasksModule {
|
|
|
11
11
|
static ɵfac = function NgTasksModule_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || NgTasksModule)(); };
|
|
12
12
|
static ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: NgTasksModule });
|
|
13
13
|
static ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [TaskComponent,
|
|
14
|
-
SimpleTaskViewerComponent
|
|
14
|
+
SimpleTaskViewerComponent,
|
|
15
|
+
GanttTaskViewerComponent] });
|
|
15
16
|
}
|
|
16
17
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(NgTasksModule, [{
|
|
17
18
|
type: NgModule,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ng-tasks.module.js","sourceRoot":"","sources":["../../src/lib/ng-tasks.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;;AAEpF;;;GAGG;AAaH,MAAM,OAAO,aAAa;uGAAb,aAAa;4DAAb,aAAa;gEAVtB,aAAa;YACb,yBAAyB;;
|
|
1
|
+
{"version":3,"file":"ng-tasks.module.js","sourceRoot":"","sources":["../../src/lib/ng-tasks.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;;AAEpF;;;GAGG;AAaH,MAAM,OAAO,aAAa;uGAAb,aAAa;4DAAb,aAAa;gEAVtB,aAAa;YACb,yBAAyB;YACzB,wBAAwB;;iFAQf,aAAa;cAZzB,QAAQ;eAAC;gBACR,OAAO,EAAE;oBACP,aAAa;oBACb,yBAAyB;oBACzB,wBAAwB;iBACzB;gBACD,OAAO,EAAE;oBACP,aAAa;oBACb,yBAAyB;oBACzB,wBAAwB;iBACzB;aACF;;wFACY,aAAa,cAVtB,aAAa;QACb,yBAAyB;QACzB,wBAAwB,aAGxB,aAAa;QACb,yBAAyB;QACzB,wBAAwB","sourcesContent":["import { NgModule } from '@angular/core';\nimport { TaskComponent } from './components/task.component';\nimport { SimpleTaskViewerComponent } from './components/simple-task-viewer.component';\nimport { GanttTaskViewerComponent } from './components/gantt-task-viewer.component';\n\n/**\n * MemberJunction Tasks Module\n * Provides task visualization components with Gantt chart support\n */\n@NgModule({\n imports: [\n TaskComponent,\n SimpleTaskViewerComponent,\n GanttTaskViewerComponent\n ],\n exports: [\n TaskComponent,\n SimpleTaskViewerComponent,\n GanttTaskViewerComponent\n ]\n})\nexport class NgTasksModule { }\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-tasks",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.16.0",
|
|
4
4
|
"description": "MemberJunction: Angular components for task visualization and management with Gantt chart support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
"@angular/common": "21.1.3",
|
|
16
16
|
"@angular/core": "21.1.3",
|
|
17
17
|
"@angular/platform-browser": "21.1.3",
|
|
18
|
-
"@memberjunction/core": "5.
|
|
19
|
-
"@memberjunction/core-entities": "5.
|
|
20
|
-
"@memberjunction/ai-core-plus": "5.
|
|
21
|
-
"@memberjunction/ai-engine-base": "5.
|
|
18
|
+
"@memberjunction/core": "5.16.0",
|
|
19
|
+
"@memberjunction/core-entities": "5.16.0",
|
|
20
|
+
"@memberjunction/ai-core-plus": "5.16.0",
|
|
21
|
+
"@memberjunction/ai-engine-base": "5.16.0",
|
|
22
|
+
"@memberjunction/ng-shared-generic": "5.16.0",
|
|
22
23
|
"dhtmlx-gantt": "^9.1.1",
|
|
23
24
|
"rxjs": "^7.8.2"
|
|
24
25
|
},
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, OnChanges, AfterViewInit, ElementRef, ViewChild, OnDestroy, HostListener } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
import { MJTaskEntity, MJTaskDependencyEntity } from '@memberjunction/core-entities';
|
|
4
|
-
import {
|
|
4
|
+
import type { GanttStatic, Task as GanttTask, Link as GanttLink } from 'dhtmlx-gantt';
|
|
5
5
|
import { TaskDetailPanelComponent } from './task-detail-panel.component';
|
|
6
|
+
import { SharedGenericModule } from '@memberjunction/ng-shared-generic';
|
|
6
7
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -11,34 +12,42 @@ import { UUIDsEqual } from '@memberjunction/global';
|
|
|
11
12
|
@Component({
|
|
12
13
|
selector: 'mj-gantt-task-viewer',
|
|
13
14
|
standalone: true,
|
|
14
|
-
imports: [TaskDetailPanelComponent],
|
|
15
|
+
imports: [TaskDetailPanelComponent, SharedGenericModule],
|
|
15
16
|
template: `
|
|
16
17
|
<div class="gantt-task-viewer">
|
|
17
|
-
@if (
|
|
18
|
-
<div class="
|
|
19
|
-
<
|
|
20
|
-
<p>No tasks to display in Gantt view</p>
|
|
18
|
+
@if (IsGanttLoading) {
|
|
19
|
+
<div class="loading-container">
|
|
20
|
+
<mj-loading text="Loading Gantt chart..."></mj-loading>
|
|
21
21
|
</div>
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
@if (
|
|
25
|
-
|
|
26
|
-
<div
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
23
|
+
|
|
24
|
+
@if (!IsGanttLoading) {
|
|
25
|
+
@if (!tasks || tasks.length === 0) {
|
|
26
|
+
<div class="no-tasks">
|
|
27
|
+
<i class="fas fa-chart-gantt"></i>
|
|
28
|
+
<p>No tasks to display in Gantt view</p>
|
|
29
|
+
</div>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@if (tasks && tasks.length > 0) {
|
|
33
|
+
<div class="gantt-layout">
|
|
34
|
+
<div #ganttContainer class="gantt-container"></div>
|
|
35
|
+
@if (selectedTask) {
|
|
36
|
+
<div class="gantt-resizer"
|
|
37
|
+
(mousedown)="startResize($event)"></div>
|
|
38
|
+
}
|
|
39
|
+
@if (selectedTask) {
|
|
40
|
+
<div class="task-detail-panel" [style.width.px]="detailPanelWidth">
|
|
41
|
+
<mj-task-detail-panel
|
|
42
|
+
[task]="selectedTask"
|
|
43
|
+
[agentRunId]="getAgentRunId(selectedTask)"
|
|
44
|
+
(closePanel)="closeDetailPanel()"
|
|
45
|
+
(openEntityRecord)="onOpenEntityRecord($event)">
|
|
46
|
+
</mj-task-detail-panel>
|
|
47
|
+
</div>
|
|
48
|
+
}
|
|
49
|
+
</div>
|
|
50
|
+
}
|
|
42
51
|
}
|
|
43
52
|
</div>
|
|
44
53
|
`,
|
|
@@ -100,6 +109,13 @@ import { UUIDsEqual } from '@memberjunction/global';
|
|
|
100
109
|
font-weight: 500;
|
|
101
110
|
}
|
|
102
111
|
|
|
112
|
+
.loading-container {
|
|
113
|
+
display: flex;
|
|
114
|
+
justify-content: center;
|
|
115
|
+
align-items: center;
|
|
116
|
+
padding: 80px 20px;
|
|
117
|
+
}
|
|
118
|
+
|
|
103
119
|
.no-tasks {
|
|
104
120
|
text-align: center;
|
|
105
121
|
padding: 80px 20px;
|
|
@@ -129,20 +145,31 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
129
145
|
|
|
130
146
|
public selectedTask: MJTaskEntity | null = null;
|
|
131
147
|
public detailPanelWidth: number = 400;
|
|
148
|
+
public IsGanttLoading = true;
|
|
132
149
|
|
|
150
|
+
private ganttLib: GanttStatic | null = null;
|
|
133
151
|
private ganttInitialized = false;
|
|
134
152
|
private isResizing = false;
|
|
135
153
|
private resizeStartX = 0;
|
|
136
154
|
private resizeStartWidth = 0;
|
|
137
155
|
|
|
138
|
-
ngAfterViewInit() {
|
|
156
|
+
async ngAfterViewInit() {
|
|
139
157
|
console.log('🔧 ngAfterViewInit called', {
|
|
140
158
|
taskCount: this.tasks?.length || 0,
|
|
141
159
|
hasContainer: !!this.ganttContainer
|
|
142
160
|
});
|
|
143
161
|
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
try {
|
|
163
|
+
const module = await import('dhtmlx-gantt');
|
|
164
|
+
this.ganttLib = module.gantt;
|
|
165
|
+
this.IsGanttLoading = false;
|
|
166
|
+
|
|
167
|
+
if (this.tasks && this.tasks.length > 0 && this.ganttContainer) {
|
|
168
|
+
this.initGantt();
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error('Failed to load dhtmlx-gantt:', error);
|
|
172
|
+
this.IsGanttLoading = false;
|
|
146
173
|
}
|
|
147
174
|
}
|
|
148
175
|
|
|
@@ -153,6 +180,8 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
153
180
|
taskCount: this.tasks?.length || 0
|
|
154
181
|
});
|
|
155
182
|
|
|
183
|
+
if (!this.ganttLib) return; // Library not yet loaded
|
|
184
|
+
|
|
156
185
|
if (this.ganttInitialized && this.ganttContainer) {
|
|
157
186
|
this.updateGanttData();
|
|
158
187
|
} else if (!this.ganttInitialized && this.ganttContainer && this.tasks && this.tasks.length > 0) {
|
|
@@ -163,8 +192,8 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
163
192
|
}
|
|
164
193
|
|
|
165
194
|
ngOnDestroy() {
|
|
166
|
-
if (this.ganttInitialized) {
|
|
167
|
-
|
|
195
|
+
if (this.ganttInitialized && this.ganttLib) {
|
|
196
|
+
this.ganttLib.clearAll();
|
|
168
197
|
this.ganttInitialized = false;
|
|
169
198
|
}
|
|
170
199
|
}
|
|
@@ -172,31 +201,32 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
172
201
|
private initGantt(): void {
|
|
173
202
|
try {
|
|
174
203
|
console.log('🎨 Initializing DHTMLX Gantt');
|
|
204
|
+
const g = this.ganttLib!;
|
|
175
205
|
|
|
176
206
|
// IMPORTANT: Clear any previous configuration
|
|
177
|
-
|
|
207
|
+
g.clearAll();
|
|
178
208
|
|
|
179
209
|
// Configure Gantt layout and appearance
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
210
|
+
g.config.date_format = '%Y-%m-%d %H:%i';
|
|
211
|
+
g.config.scale_unit = 'day';
|
|
212
|
+
g.config.date_scale = '%d %M';
|
|
213
|
+
g.config.subscales = [];
|
|
214
|
+
g.config.show_progress = true;
|
|
215
|
+
g.config.show_links = true;
|
|
216
|
+
g.config.auto_types = true;
|
|
217
|
+
g.config.readonly = true; // Read-only for now
|
|
218
|
+
g.config.fit_tasks = true; // Auto-fit timeline to tasks
|
|
189
219
|
|
|
190
220
|
// Disable auto-scheduling - we calculate dates ourselves based on dependencies
|
|
191
|
-
|
|
221
|
+
g.config.auto_scheduling = false;
|
|
192
222
|
|
|
193
223
|
// Grid configuration
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
224
|
+
g.config.grid_width = 350;
|
|
225
|
+
g.config.row_height = 36;
|
|
226
|
+
g.config.scale_height = 0; // Hide date scale - dates are just for positioning
|
|
197
227
|
|
|
198
228
|
// Layout configuration - ensure timeline is visible
|
|
199
|
-
|
|
229
|
+
g.config.layout = {
|
|
200
230
|
css: "gantt_container",
|
|
201
231
|
rows: [
|
|
202
232
|
{
|
|
@@ -212,16 +242,16 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
212
242
|
};
|
|
213
243
|
|
|
214
244
|
// Column configuration - only show task names, hide dates
|
|
215
|
-
|
|
245
|
+
g.config.columns = [
|
|
216
246
|
{ name: 'text', label: 'Task name', tree: true, width: '*' }
|
|
217
247
|
];
|
|
218
248
|
|
|
219
249
|
// Initialize Gantt in the container
|
|
220
|
-
|
|
250
|
+
g.init(this.ganttContainer.nativeElement);
|
|
221
251
|
this.ganttInitialized = true;
|
|
222
252
|
|
|
223
253
|
// Attach click event
|
|
224
|
-
|
|
254
|
+
g.attachEvent('onTaskClick', (id: string) => {
|
|
225
255
|
const originalTask = this.tasks.find(t => UUIDsEqual(t.ID, id));
|
|
226
256
|
if (originalTask) {
|
|
227
257
|
this.selectedTask = originalTask;
|
|
@@ -235,7 +265,7 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
235
265
|
|
|
236
266
|
// Force resize after data load to ensure proper rendering
|
|
237
267
|
setTimeout(() => {
|
|
238
|
-
|
|
268
|
+
g.setSizes();
|
|
239
269
|
}, 0);
|
|
240
270
|
|
|
241
271
|
// Expand and select after render completes
|
|
@@ -254,20 +284,21 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
254
284
|
|
|
255
285
|
try {
|
|
256
286
|
console.log('📊 Updating Gantt data with', this.tasks.length, 'tasks');
|
|
287
|
+
const g = this.ganttLib!;
|
|
257
288
|
|
|
258
289
|
const ganttData = this.convertToGanttFormat(this.tasks);
|
|
259
|
-
|
|
260
|
-
|
|
290
|
+
g.clearAll();
|
|
291
|
+
g.parse(ganttData);
|
|
261
292
|
|
|
262
293
|
// Log final parsed data for debugging
|
|
263
294
|
console.log('📋 Gantt data after parse:', {
|
|
264
|
-
tasks:
|
|
265
|
-
links:
|
|
295
|
+
tasks: g.getTaskByTime(),
|
|
296
|
+
links: g.getLinks()
|
|
266
297
|
});
|
|
267
298
|
|
|
268
299
|
// Force resize after data update
|
|
269
300
|
setTimeout(() => {
|
|
270
|
-
|
|
301
|
+
g.setSizes();
|
|
271
302
|
}, 0);
|
|
272
303
|
|
|
273
304
|
// Expand and select after render completes
|
|
@@ -281,9 +312,9 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
281
312
|
}
|
|
282
313
|
}
|
|
283
314
|
|
|
284
|
-
private convertToGanttFormat(tasks: MJTaskEntity[]): { data:
|
|
285
|
-
const data:
|
|
286
|
-
const links:
|
|
315
|
+
private convertToGanttFormat(tasks: MJTaskEntity[]): { data: GanttTask[], links: GanttLink[] } {
|
|
316
|
+
const data: GanttTask[] = [];
|
|
317
|
+
const links: GanttLink[] = [];
|
|
287
318
|
|
|
288
319
|
console.log('🔍 Converting tasks:', tasks);
|
|
289
320
|
console.log('🔗 Task dependencies:', this.taskDependencies);
|
|
@@ -375,15 +406,16 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
375
406
|
});
|
|
376
407
|
|
|
377
408
|
// Add 1 day padding before and after
|
|
409
|
+
const g = this.ganttLib!;
|
|
378
410
|
if (minDate) {
|
|
379
411
|
const paddedStart = new Date(minDate);
|
|
380
412
|
paddedStart.setDate(paddedStart.getDate() - 1);
|
|
381
|
-
|
|
413
|
+
g.config.start_date = paddedStart;
|
|
382
414
|
}
|
|
383
415
|
if (maxDate) {
|
|
384
416
|
const paddedEnd = new Date(maxDate);
|
|
385
417
|
paddedEnd.setDate(paddedEnd.getDate() + 1);
|
|
386
|
-
|
|
418
|
+
g.config.end_date = paddedEnd;
|
|
387
419
|
}
|
|
388
420
|
|
|
389
421
|
// Now create Gantt tasks with calculated dates
|
|
@@ -403,10 +435,10 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
403
435
|
const duration = this.calculateDuration(task);
|
|
404
436
|
const startDate = taskStartDates.get(task.ID) || new Date(baseDate);
|
|
405
437
|
|
|
406
|
-
const ganttTask:
|
|
438
|
+
const ganttTask: Partial<GanttTask> & Pick<GanttTask, 'id'> = {
|
|
407
439
|
id: task.ID,
|
|
408
440
|
text: task.Name || 'Untitled Task',
|
|
409
|
-
start_date: this.formatDateForDHTMLX(startDate),
|
|
441
|
+
start_date: this.formatDateForDHTMLX(startDate) as unknown as Date,
|
|
410
442
|
duration: duration,
|
|
411
443
|
progress: progress
|
|
412
444
|
};
|
|
@@ -420,7 +452,7 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
420
452
|
}
|
|
421
453
|
|
|
422
454
|
console.log('✅ Created Gantt task:', ganttTask);
|
|
423
|
-
data.push(ganttTask);
|
|
455
|
+
data.push(ganttTask as GanttTask);
|
|
424
456
|
});
|
|
425
457
|
|
|
426
458
|
// Create links from MJTaskDependencyEntity records
|
|
@@ -490,8 +522,8 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
490
522
|
this.isResizing = false;
|
|
491
523
|
// Resize gantt chart after panel resize completes
|
|
492
524
|
setTimeout(() => {
|
|
493
|
-
if (this.ganttInitialized) {
|
|
494
|
-
|
|
525
|
+
if (this.ganttInitialized && this.ganttLib) {
|
|
526
|
+
this.ganttLib.setSizes();
|
|
495
527
|
}
|
|
496
528
|
}, 0);
|
|
497
529
|
}
|
|
@@ -499,31 +531,33 @@ export class GanttTaskViewerComponent implements OnChanges, AfterViewInit, OnDes
|
|
|
499
531
|
|
|
500
532
|
private expandAllAndSelectRoot(): void {
|
|
501
533
|
try {
|
|
534
|
+
const g = this.ganttLib!;
|
|
535
|
+
|
|
502
536
|
// Expand all tasks
|
|
503
|
-
|
|
504
|
-
|
|
537
|
+
g.eachTask((task: GanttTask) => {
|
|
538
|
+
g.open(task.id);
|
|
505
539
|
});
|
|
506
540
|
|
|
507
541
|
// Find and select the root task (task with parent = 0)
|
|
508
|
-
let
|
|
509
|
-
|
|
542
|
+
let rootTaskId: string | number | null = null;
|
|
543
|
+
g.eachTask((task: GanttTask) => {
|
|
510
544
|
if (task.parent === 0 || task.parent === '0') {
|
|
511
|
-
|
|
545
|
+
rootTaskId = task.id;
|
|
512
546
|
return false; // Stop iteration
|
|
513
547
|
}
|
|
514
548
|
});
|
|
515
549
|
|
|
516
|
-
if (
|
|
517
|
-
|
|
550
|
+
if (rootTaskId != null) {
|
|
551
|
+
g.selectTask(rootTaskId);
|
|
518
552
|
// Trigger task click event to open detail panel
|
|
519
|
-
const originalTask = this.tasks.find(t => UUIDsEqual(t.ID,
|
|
553
|
+
const originalTask = this.tasks.find(t => UUIDsEqual(t.ID, String(rootTaskId)));
|
|
520
554
|
if (originalTask) {
|
|
521
555
|
this.selectedTask = originalTask;
|
|
522
556
|
this.taskClicked.emit(originalTask);
|
|
523
557
|
}
|
|
524
558
|
}
|
|
525
559
|
|
|
526
|
-
console.log('✅ Expanded all tasks and selected root:',
|
|
560
|
+
console.log('✅ Expanded all tasks and selected root:', rootTaskId);
|
|
527
561
|
} catch (error) {
|
|
528
562
|
console.error('❌ Error expanding/selecting tasks:', error);
|
|
529
563
|
}
|