ct-gantt-core 1.0.8 → 1.0.10

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/dist/index.cjs CHANGED
@@ -30,12 +30,15 @@ __export(index_exports, {
30
30
  RESOURCE_COLOR_PALETTE: () => RESOURCE_COLOR_PALETTE,
31
31
  addDays: () => addDays,
32
32
  addHours: () => addHours,
33
+ addPlannedDuration: () => addPlannedDuration,
34
+ alignPlannedDate: () => alignPlannedDate,
33
35
  checkCyclicDependency: () => checkCyclicDependency,
34
36
  computeHourScale: () => computeHourScale,
35
37
  computeImpact: () => computeImpact,
36
38
  computeLayout: () => computeLayout,
37
39
  computeResourceLayout: () => computeResourceLayout,
38
40
  computeTimeScale: () => computeTimeScale,
41
+ computeWorkload: () => computeWorkload,
39
42
  createGanttEngine: () => createGanttEngine,
40
43
  defaultConfig: () => defaultConfig,
41
44
  diffDays: () => diffDays,
@@ -48,6 +51,7 @@ __export(index_exports, {
48
51
  isValidDate: () => isValidDate,
49
52
  mergeTaskPatch: () => mergeTaskPatch,
50
53
  normalizeLinks: () => normalizeLinks,
54
+ plannedDurationBetween: () => plannedDurationBetween,
51
55
  resolveResourceColor: () => resolveResourceColor,
52
56
  resolveUsageColor: () => resolveUsageColor,
53
57
  resolveUsageTimes: () => resolveUsageTimes,
@@ -55,6 +59,7 @@ __export(index_exports, {
55
59
  scheduleByDependencies: () => scheduleByDependencies,
56
60
  shiftTask: () => shiftTask,
57
61
  startOfDay: () => startOfDay,
62
+ subtractPlannedDuration: () => subtractPlannedDuration,
58
63
  toDate: () => toDate,
59
64
  toDateTime: () => toDateTime
60
65
  });
@@ -107,7 +112,7 @@ var defaultConfig = {
107
112
  showPlanBar: true,
108
113
  showActualBar: true,
109
114
  showTimelineWhenEmpty: false,
110
- builtInTaskEditor: true,
115
+ builtInTaskEditor: false,
111
116
  builtInMarkerEditor: true,
112
117
  editablePlan: false,
113
118
  editableActual: true,
@@ -303,6 +308,52 @@ function diffHours(start, end) {
303
308
  function inclusiveDays(start, end) {
304
309
  return Math.max(0, diffDays(start, end) + 1);
305
310
  }
311
+ function addPlannedDuration(start, duration, calendarId = "standard") {
312
+ const days = Math.max(0, Math.trunc(Number.isFinite(duration) ? duration : 0));
313
+ if (days === 0) return toDate(start);
314
+ if (calendarId === "delivery") return addDays(start, days - 1);
315
+ let current = toDate(start);
316
+ let remaining = days;
317
+ while (remaining > 0) {
318
+ const day = current.getDay();
319
+ if (day !== 0 && day !== 6) remaining -= 1;
320
+ if (remaining > 0) current = addDays(current, 1);
321
+ }
322
+ return current;
323
+ }
324
+ function subtractPlannedDuration(end, duration, calendarId = "standard") {
325
+ const days = Math.max(0, Math.trunc(Number.isFinite(duration) ? duration : 0));
326
+ if (days === 0) return toDate(end);
327
+ if (calendarId === "delivery") return addDays(end, -(days - 1));
328
+ let current = toDate(end);
329
+ let remaining = days;
330
+ while (remaining > 0) {
331
+ const day = current.getDay();
332
+ if (day !== 0 && day !== 6) remaining -= 1;
333
+ if (remaining > 0) current = addDays(current, -1);
334
+ }
335
+ return current;
336
+ }
337
+ function alignPlannedDate(date, calendarId = "standard", direction = 1) {
338
+ let current = toDate(date);
339
+ if (calendarId === "delivery") return current;
340
+ while (current.getDay() === 0 || current.getDay() === 6) {
341
+ current = addDays(current, direction);
342
+ }
343
+ return current;
344
+ }
345
+ function plannedDurationBetween(start, end, calendarId = "standard") {
346
+ const first = toDate(start);
347
+ const last = toDate(end);
348
+ if (last.getTime() < first.getTime()) return 0;
349
+ if (calendarId === "delivery") return inclusiveDays(first, last);
350
+ let duration = 0;
351
+ for (let current = first; current <= last; current = addDays(current, 1)) {
352
+ const day = current.getDay();
353
+ if (day !== 0 && day !== 6) duration += 1;
354
+ }
355
+ return duration;
356
+ }
306
357
  function isValidDate(value) {
307
358
  const date = value instanceof Date ? value : new Date(value);
308
359
  return !Number.isNaN(date.getTime());
@@ -798,15 +849,84 @@ function minDate2(dates) {
798
849
  return new Date(Math.min(...dates.map((date) => date.getTime())));
799
850
  }
800
851
 
852
+ // src/engines/computeWorkload.ts
853
+ function computeWorkload(tasks, people, departments = [], range) {
854
+ const dated = range ?? inferRange(tasks);
855
+ if (!dated) return { dates: [], departments: [], ungrouped: [] };
856
+ const dates = datesBetween(dated.start, dated.end);
857
+ const dateSet = new Set(dates);
858
+ const rows = new Map(people.map((person) => [person.id, createPerson(person, dates)]));
859
+ for (const task of tasks) {
860
+ if (task.type === "summary" || task.type === "milestone") continue;
861
+ const ids = task.resources ?? [];
862
+ if (!ids.length) continue;
863
+ const values = task.dailyWorkloads ?? averageTaskWorkload(task);
864
+ for (const personId of ids) {
865
+ const row = rows.get(personId);
866
+ if (!row) continue;
867
+ for (const [date, hours] of Object.entries(values)) if (dateSet.has(date)) row.days.find((day) => day.date === date).hours += hours;
868
+ }
869
+ }
870
+ for (const row of rows.values()) finalize(row);
871
+ const grouped = /* @__PURE__ */ new Map();
872
+ const ungrouped = [];
873
+ for (const row of rows.values()) (row.person.departmentId ? grouped.get(row.person.departmentId) ?? (grouped.set(row.person.departmentId, []), grouped.get(row.person.departmentId)) : ungrouped).push(row);
874
+ const departmentMap = new Map(departments.map((item) => [item.id, item]));
875
+ const result = [...grouped].map(([id, members]) => aggregateDepartment(departmentMap.get(id) ?? { id, name: id }, members, dates));
876
+ return { dates, departments: result, ungrouped };
877
+ }
878
+ function inferRange(tasks) {
879
+ if (!tasks.length) return void 0;
880
+ const dates = tasks.flatMap((t) => [toDate(t.plan.start), toDate(t.plan.end)]);
881
+ return { start: new Date(Math.min(...dates.map((d) => d.getTime()))), end: new Date(Math.max(...dates.map((d) => d.getTime()))) };
882
+ }
883
+ function datesBetween(start, end) {
884
+ const values = [];
885
+ for (let date = toDate(start); date <= toDate(end); date = addDays(date, 1)) values.push(formatDate(date));
886
+ return values;
887
+ }
888
+ function averageTaskWorkload(task) {
889
+ const total = task.workload ?? (task.duration ?? inclusiveDays(task.plan.start, task.plan.end)) * 8;
890
+ const allDates = datesBetween(task.plan.start, task.plan.end);
891
+ const dates = task.calendarId === "delivery" ? allDates : allDates.filter((date) => {
892
+ const day = toDate(date).getDay();
893
+ return day !== 0 && day !== 6;
894
+ });
895
+ const hours = dates.length ? total / dates.length : 0;
896
+ return Object.fromEntries(dates.map((date) => [date, hours]));
897
+ }
898
+ function createPerson(person, dates) {
899
+ return { person, days: dates.map((date) => ({ date, hours: 0, capacity: person.capacityPerDay ?? 8 })), workload: 0, capacity: 0, utilization: 0, progress: 0 };
900
+ }
901
+ function finalize(row) {
902
+ row.workload = row.days.reduce((sum, day) => sum + day.hours, 0);
903
+ row.capacity = row.days.reduce((sum, day) => sum + day.capacity, 0);
904
+ row.utilization = row.capacity ? Math.round(row.workload / row.capacity * 100) : 0;
905
+ row.progress = row.utilization;
906
+ }
907
+ function aggregateDepartment(department, people, dates) {
908
+ const days = dates.map((date, index) => ({ date, hours: people.reduce((sum, person) => sum + person.days[index].hours, 0), capacity: people.reduce((sum, person) => sum + person.days[index].capacity, 0) }));
909
+ const workload = days.reduce((sum, day) => sum + day.hours, 0);
910
+ const capacity = days.reduce((sum, day) => sum + day.capacity, 0);
911
+ const utilization = capacity ? Math.round(workload / capacity * 100) : 0;
912
+ return { department, people, days, workload, capacity, utilization, progress: utilization };
913
+ }
914
+
801
915
  // src/engine/GanttEngine.ts
802
916
  function mergeTaskPatch(task, patch) {
917
+ const type = patch.type ?? task.type;
918
+ const calendarId = patch.calendarId ?? task.calendarId ?? "standard";
919
+ const planStart = patch.planStart ?? task.plan.start;
920
+ const planEnd = type === "milestone" ? planStart : patch.planEnd ?? task.plan.end;
921
+ const duration = type === "milestone" ? 1 : plannedDurationBetween(planStart, planEnd, calendarId);
803
922
  return {
804
923
  ...task,
805
924
  ...patch,
925
+ duration,
806
926
  plan: {
807
927
  ...task.plan,
808
- start: patch.planStart ?? task.plan.start,
809
- end: patch.planEnd ?? task.plan.end,
928
+ start: planStart,
929
+ end: planEnd,
810
930
  progress: patch.progress ?? task.plan.progress
811
931
  },
812
932
  actual: {
@@ -1176,12 +1296,15 @@ function createGanttEngine(options = {}) {
1176
1296
  RESOURCE_COLOR_PALETTE,
1177
1297
  addDays,
1178
1298
  addHours,
1299
+ addPlannedDuration,
1300
+ alignPlannedDate,
1179
1301
  checkCyclicDependency,
1180
1302
  computeHourScale,
1181
1303
  computeImpact,
1182
1304
  computeLayout,
1183
1305
  computeResourceLayout,
1184
1306
  computeTimeScale,
1307
+ computeWorkload,
1185
1308
  createGanttEngine,
1186
1309
  defaultConfig,
1187
1310
  diffDays,
@@ -1194,6 +1317,7 @@ function createGanttEngine(options = {}) {
1194
1317
  isValidDate,
1195
1318
  mergeTaskPatch,
1196
1319
  normalizeLinks,
1320
+ plannedDurationBetween,
1197
1321
  resolveResourceColor,
1198
1322
  resolveUsageColor,
1199
1323
  resolveUsageTimes,
@@ -1201,6 +1325,7 @@ function createGanttEngine(options = {}) {
1201
1325
  scheduleByDependencies,
1202
1326
  shiftTask,
1203
1327
  startOfDay,
1328
+ subtractPlannedDuration,
1204
1329
  toDate,
1205
1330
  toDateTime
1206
1331
  });