construction-gantt 0.1.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +5 -0
  3. package/dist/export/index.cjs +1 -0
  4. package/dist/export/index.d.cts +112 -0
  5. package/dist/export/index.d.cts.map +1 -0
  6. package/dist/export/index.d.ts +112 -0
  7. package/dist/export/index.d.ts.map +1 -0
  8. package/dist/export/index.js +1 -0
  9. package/dist/index.cjs +3492 -0
  10. package/dist/index.d.cts +628 -0
  11. package/dist/index.d.cts.map +1 -0
  12. package/dist/index.d.ts +628 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +3461 -0
  15. package/dist/pdf-CAQDrX0w.cjs +120 -0
  16. package/dist/pdf-CBaoJRTI.js +120 -0
  17. package/dist/png-C8t74695.cjs +88 -0
  18. package/dist/png-DKZeKnRh.js +88 -0
  19. package/dist/xlsx-5FRPFck7.js +89 -0
  20. package/dist/xlsx-Gh5L_NL3.cjs +111 -0
  21. package/package.json +86 -0
  22. package/src/Gantt.css +23 -0
  23. package/src/Gantt.tsx +636 -0
  24. package/src/SpikeGantt.tsx +114 -0
  25. package/src/analysis.ts +83 -0
  26. package/src/baseline.ts +119 -0
  27. package/src/calendars/canterbury-table.ts +44 -0
  28. package/src/calendars/internal/computus.ts +25 -0
  29. package/src/calendars/internal/date-utils.ts +13 -0
  30. package/src/calendars/internal/mondayisation.ts +46 -0
  31. package/src/calendars/internal/month-rules.ts +65 -0
  32. package/src/calendars/matariki-table.ts +63 -0
  33. package/src/calendars/nz-holidays.ts +214 -0
  34. package/src/editing/command-history.ts +78 -0
  35. package/src/editing/commands.ts +327 -0
  36. package/src/editing/composite-command.ts +64 -0
  37. package/src/editing/draft-project.ts +59 -0
  38. package/src/editing/errors.ts +14 -0
  39. package/src/editing/factories.ts +92 -0
  40. package/src/editing/use-editable-project.ts +122 -0
  41. package/src/export/index.ts +12 -0
  42. package/src/export/offscreen.tsx +89 -0
  43. package/src/export/pdf-dimensions.ts +64 -0
  44. package/src/export/pdf.ts +68 -0
  45. package/src/export/png.ts +48 -0
  46. package/src/export/types.ts +42 -0
  47. package/src/export/xlsx.ts +70 -0
  48. package/src/index.ts +89 -0
  49. package/src/mspdi/parse.ts +820 -0
  50. package/src/mspdi/serialize.ts +352 -0
  51. package/src/mspdi/types.ts +53 -0
  52. package/src/schedule.ts +470 -0
  53. package/src/topological-sort.ts +51 -0
  54. package/src/types.ts +254 -0
  55. package/src/visibility.ts +35 -0
  56. package/src/working-time.ts +235 -0
@@ -0,0 +1,120 @@
1
+ function __insertCSS(code) {
2
+ if (!code || typeof document == 'undefined') return
3
+ let head = document.head || document.getElementsByTagName('head')[0]
4
+ let style = document.createElement('style')
5
+ style.type = 'text/css'
6
+ head.appendChild(style)
7
+ ;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
8
+ }
9
+
10
+ var jspdf = require('jspdf');
11
+ var png = require('./png-C8t74695.cjs');
12
+
13
+ // Pure-function image-fit math. Used by the PDF exporter to scale the
14
+ // captured PNG onto a chosen page format with a uniform margin while
15
+ // preserving aspect ratio. Kept separate from pdf.ts so it can be
16
+ // Vitest-covered without instantiating jsPDF.
17
+ // Standard print dimensions (ISO 216 + ANSI Letter), in millimetres.
18
+ const PAGE_DIMENSIONS_MM = {
19
+ a4: {
20
+ portrait: {
21
+ width: 210,
22
+ height: 297
23
+ },
24
+ landscape: {
25
+ width: 297,
26
+ height: 210
27
+ }
28
+ },
29
+ a3: {
30
+ portrait: {
31
+ width: 297,
32
+ height: 420
33
+ },
34
+ landscape: {
35
+ width: 420,
36
+ height: 297
37
+ }
38
+ },
39
+ letter: {
40
+ portrait: {
41
+ width: 215.9,
42
+ height: 279.4
43
+ },
44
+ landscape: {
45
+ width: 279.4,
46
+ height: 215.9
47
+ }
48
+ }
49
+ };
50
+ function computeImageFit(args) {
51
+ const { pageWidth, pageHeight, margin, pngPxWidth, pngPxHeight } = args;
52
+ const availableWidth = pageWidth - 2 * margin;
53
+ const availableHeight = pageHeight - 2 * margin;
54
+ const widthScale = availableWidth / pngPxWidth;
55
+ const heightScale = availableHeight / pngPxHeight;
56
+ const scale = Math.min(widthScale, heightScale);
57
+ const width = pngPxWidth * scale;
58
+ const height = pngPxHeight * scale;
59
+ const x = (pageWidth - width) / 2;
60
+ const y = (pageHeight - height) / 2;
61
+ return {
62
+ width,
63
+ height,
64
+ x,
65
+ y
66
+ };
67
+ }
68
+
69
+ async function exportPDF(args) {
70
+ const { scheduled, ganttProps, options } = args;
71
+ const orientation = options.orientation ?? 'landscape';
72
+ const format = options.format ?? 'a3';
73
+ const margin = options.margin ?? 10;
74
+ const backgroundColor = options.backgroundColor ?? '#ffffff';
75
+ const pngBlob = await png.exportPNG({
76
+ scheduled,
77
+ ganttProps,
78
+ options: {
79
+ backgroundColor
80
+ }
81
+ });
82
+ const dataUrl = await blobToDataUrl(pngBlob);
83
+ const { width: pngPxWidth, height: pngPxHeight } = await probeImageDimensions(dataUrl);
84
+ const page = PAGE_DIMENSIONS_MM[format][orientation];
85
+ const fit = computeImageFit({
86
+ pageWidth: page.width,
87
+ pageHeight: page.height,
88
+ margin,
89
+ pngPxWidth,
90
+ pngPxHeight
91
+ });
92
+ const pdf = new jspdf.jsPDF({
93
+ orientation,
94
+ unit: 'mm',
95
+ format
96
+ });
97
+ pdf.addImage(dataUrl, 'PNG', fit.x, fit.y, fit.width, fit.height);
98
+ return pdf.output('blob');
99
+ }
100
+ function blobToDataUrl(blob) {
101
+ return new Promise((resolve, reject)=>{
102
+ const reader = new FileReader();
103
+ reader.onload = ()=>resolve(String(reader.result));
104
+ reader.onerror = ()=>reject(reader.error ?? new Error('FileReader failed'));
105
+ reader.readAsDataURL(blob);
106
+ });
107
+ }
108
+ function probeImageDimensions(dataUrl) {
109
+ return new Promise((resolve, reject)=>{
110
+ const img = new Image();
111
+ img.onload = ()=>resolve({
112
+ width: img.width,
113
+ height: img.height
114
+ });
115
+ img.onerror = ()=>reject(new Error('Failed to probe PNG dimensions'));
116
+ img.src = dataUrl;
117
+ });
118
+ }
119
+
120
+ exports.exportPDF = exportPDF;
@@ -0,0 +1,120 @@
1
+ function __insertCSS(code) {
2
+ if (!code || typeof document == 'undefined') return
3
+ let head = document.head || document.getElementsByTagName('head')[0]
4
+ let style = document.createElement('style')
5
+ style.type = 'text/css'
6
+ head.appendChild(style)
7
+ ;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
8
+ }
9
+
10
+ import { jsPDF } from 'jspdf';
11
+ import { exportPNG } from './png-DKZeKnRh.js';
12
+
13
+ // Pure-function image-fit math. Used by the PDF exporter to scale the
14
+ // captured PNG onto a chosen page format with a uniform margin while
15
+ // preserving aspect ratio. Kept separate from pdf.ts so it can be
16
+ // Vitest-covered without instantiating jsPDF.
17
+ // Standard print dimensions (ISO 216 + ANSI Letter), in millimetres.
18
+ const PAGE_DIMENSIONS_MM = {
19
+ a4: {
20
+ portrait: {
21
+ width: 210,
22
+ height: 297
23
+ },
24
+ landscape: {
25
+ width: 297,
26
+ height: 210
27
+ }
28
+ },
29
+ a3: {
30
+ portrait: {
31
+ width: 297,
32
+ height: 420
33
+ },
34
+ landscape: {
35
+ width: 420,
36
+ height: 297
37
+ }
38
+ },
39
+ letter: {
40
+ portrait: {
41
+ width: 215.9,
42
+ height: 279.4
43
+ },
44
+ landscape: {
45
+ width: 279.4,
46
+ height: 215.9
47
+ }
48
+ }
49
+ };
50
+ function computeImageFit(args) {
51
+ const { pageWidth, pageHeight, margin, pngPxWidth, pngPxHeight } = args;
52
+ const availableWidth = pageWidth - 2 * margin;
53
+ const availableHeight = pageHeight - 2 * margin;
54
+ const widthScale = availableWidth / pngPxWidth;
55
+ const heightScale = availableHeight / pngPxHeight;
56
+ const scale = Math.min(widthScale, heightScale);
57
+ const width = pngPxWidth * scale;
58
+ const height = pngPxHeight * scale;
59
+ const x = (pageWidth - width) / 2;
60
+ const y = (pageHeight - height) / 2;
61
+ return {
62
+ width,
63
+ height,
64
+ x,
65
+ y
66
+ };
67
+ }
68
+
69
+ async function exportPDF(args) {
70
+ const { scheduled, ganttProps, options } = args;
71
+ const orientation = options.orientation ?? 'landscape';
72
+ const format = options.format ?? 'a3';
73
+ const margin = options.margin ?? 10;
74
+ const backgroundColor = options.backgroundColor ?? '#ffffff';
75
+ const pngBlob = await exportPNG({
76
+ scheduled,
77
+ ganttProps,
78
+ options: {
79
+ backgroundColor
80
+ }
81
+ });
82
+ const dataUrl = await blobToDataUrl(pngBlob);
83
+ const { width: pngPxWidth, height: pngPxHeight } = await probeImageDimensions(dataUrl);
84
+ const page = PAGE_DIMENSIONS_MM[format][orientation];
85
+ const fit = computeImageFit({
86
+ pageWidth: page.width,
87
+ pageHeight: page.height,
88
+ margin,
89
+ pngPxWidth,
90
+ pngPxHeight
91
+ });
92
+ const pdf = new jsPDF({
93
+ orientation,
94
+ unit: 'mm',
95
+ format
96
+ });
97
+ pdf.addImage(dataUrl, 'PNG', fit.x, fit.y, fit.width, fit.height);
98
+ return pdf.output('blob');
99
+ }
100
+ function blobToDataUrl(blob) {
101
+ return new Promise((resolve, reject)=>{
102
+ const reader = new FileReader();
103
+ reader.onload = ()=>resolve(String(reader.result));
104
+ reader.onerror = ()=>reject(reader.error ?? new Error('FileReader failed'));
105
+ reader.readAsDataURL(blob);
106
+ });
107
+ }
108
+ function probeImageDimensions(dataUrl) {
109
+ return new Promise((resolve, reject)=>{
110
+ const img = new Image();
111
+ img.onload = ()=>resolve({
112
+ width: img.width,
113
+ height: img.height
114
+ });
115
+ img.onerror = ()=>reject(new Error('Failed to probe PNG dimensions'));
116
+ img.src = dataUrl;
117
+ });
118
+ }
119
+
120
+ export { exportPDF };
@@ -0,0 +1,88 @@
1
+ function __insertCSS(code) {
2
+ if (!code || typeof document == 'undefined') return
3
+ let head = document.head || document.getElementsByTagName('head')[0]
4
+ let style = document.createElement('style')
5
+ style.type = 'text/css'
6
+ head.appendChild(style)
7
+ ;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
8
+ }
9
+
10
+ var htmlToImage = require('html-to-image');
11
+ var jsxRuntime = require('react/jsx-runtime');
12
+ var client = require('react-dom/client');
13
+ var index = require('./index.cjs');
14
+
15
+ async function renderOffscreen(args) {
16
+ const { scheduled, ganttProps } = args;
17
+ const container = document.createElement('div');
18
+ container.style.position = 'absolute';
19
+ container.style.left = '-99999px';
20
+ container.style.top = '0';
21
+ container.style.width = 'max-content';
22
+ container.style.background = '#ffffff';
23
+ document.body.appendChild(container);
24
+ const root = client.createRoot(container);
25
+ root.render(/*#__PURE__*/ jsxRuntime.jsx(index.Gantt, {
26
+ project: scheduled,
27
+ preScheduled: true,
28
+ cellWidth: ganttProps.cellWidth,
29
+ cellHeight: ganttProps.cellHeight,
30
+ markers: ganttProps.markers,
31
+ baselineIndex: ganttProps.baselineIndex,
32
+ showBaselineBars: ganttProps.showBaselineBars,
33
+ columns: ganttProps.columns,
34
+ visibleTaskIds: ganttProps.visibleTaskIds,
35
+ height: computeFullHeight(scheduled, ganttProps.cellHeight ?? 42)
36
+ }));
37
+ // Two animation frames + font load to give SVAR time to lay out and
38
+ // any custom fonts time to be ready before snapshot.
39
+ await waitForRender();
40
+ return {
41
+ container,
42
+ async dispose () {
43
+ root.unmount();
44
+ if (container.parentNode) {
45
+ container.parentNode.removeChild(container);
46
+ }
47
+ }
48
+ };
49
+ }
50
+ function computeFullHeight(project, cellHeight) {
51
+ // Header band + one row per task. Heuristic; the off-screen container's
52
+ // width:max-content + the height being a lower bound mean an underestimate
53
+ // is harmless — content expands rather than clipping.
54
+ return 80 + project.tasks.length * cellHeight;
55
+ }
56
+ async function waitForRender() {
57
+ await new Promise((resolve)=>requestAnimationFrame(()=>resolve()));
58
+ await new Promise((resolve)=>requestAnimationFrame(()=>resolve()));
59
+ const fonts = document.fonts;
60
+ if (fonts?.ready) {
61
+ await fonts.ready;
62
+ }
63
+ }
64
+
65
+ async function exportPNG(args) {
66
+ const { scheduled, ganttProps, options } = args;
67
+ const pixelRatio = options.pixelRatio ?? (typeof window !== 'undefined' && window.devicePixelRatio ? window.devicePixelRatio : 2);
68
+ const backgroundColor = options.backgroundColor ?? '#ffffff';
69
+ const offscreen = await renderOffscreen({
70
+ scheduled,
71
+ ganttProps: ganttProps
72
+ });
73
+ try {
74
+ const blob = await htmlToImage.toBlob(offscreen.container, {
75
+ backgroundColor,
76
+ pixelRatio,
77
+ cacheBust: true
78
+ });
79
+ if (!blob) {
80
+ throw new Error('exportPNG: html-to-image returned null');
81
+ }
82
+ return blob;
83
+ } finally{
84
+ await offscreen.dispose();
85
+ }
86
+ }
87
+
88
+ exports.exportPNG = exportPNG;
@@ -0,0 +1,88 @@
1
+ function __insertCSS(code) {
2
+ if (!code || typeof document == 'undefined') return
3
+ let head = document.head || document.getElementsByTagName('head')[0]
4
+ let style = document.createElement('style')
5
+ style.type = 'text/css'
6
+ head.appendChild(style)
7
+ ;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
8
+ }
9
+
10
+ import { toBlob } from 'html-to-image';
11
+ import { jsx } from 'react/jsx-runtime';
12
+ import { createRoot } from 'react-dom/client';
13
+ import { Gantt } from './index.js';
14
+
15
+ async function renderOffscreen(args) {
16
+ const { scheduled, ganttProps } = args;
17
+ const container = document.createElement('div');
18
+ container.style.position = 'absolute';
19
+ container.style.left = '-99999px';
20
+ container.style.top = '0';
21
+ container.style.width = 'max-content';
22
+ container.style.background = '#ffffff';
23
+ document.body.appendChild(container);
24
+ const root = createRoot(container);
25
+ root.render(/*#__PURE__*/ jsx(Gantt, {
26
+ project: scheduled,
27
+ preScheduled: true,
28
+ cellWidth: ganttProps.cellWidth,
29
+ cellHeight: ganttProps.cellHeight,
30
+ markers: ganttProps.markers,
31
+ baselineIndex: ganttProps.baselineIndex,
32
+ showBaselineBars: ganttProps.showBaselineBars,
33
+ columns: ganttProps.columns,
34
+ visibleTaskIds: ganttProps.visibleTaskIds,
35
+ height: computeFullHeight(scheduled, ganttProps.cellHeight ?? 42)
36
+ }));
37
+ // Two animation frames + font load to give SVAR time to lay out and
38
+ // any custom fonts time to be ready before snapshot.
39
+ await waitForRender();
40
+ return {
41
+ container,
42
+ async dispose () {
43
+ root.unmount();
44
+ if (container.parentNode) {
45
+ container.parentNode.removeChild(container);
46
+ }
47
+ }
48
+ };
49
+ }
50
+ function computeFullHeight(project, cellHeight) {
51
+ // Header band + one row per task. Heuristic; the off-screen container's
52
+ // width:max-content + the height being a lower bound mean an underestimate
53
+ // is harmless — content expands rather than clipping.
54
+ return 80 + project.tasks.length * cellHeight;
55
+ }
56
+ async function waitForRender() {
57
+ await new Promise((resolve)=>requestAnimationFrame(()=>resolve()));
58
+ await new Promise((resolve)=>requestAnimationFrame(()=>resolve()));
59
+ const fonts = document.fonts;
60
+ if (fonts?.ready) {
61
+ await fonts.ready;
62
+ }
63
+ }
64
+
65
+ async function exportPNG(args) {
66
+ const { scheduled, ganttProps, options } = args;
67
+ const pixelRatio = options.pixelRatio ?? (typeof window !== 'undefined' && window.devicePixelRatio ? window.devicePixelRatio : 2);
68
+ const backgroundColor = options.backgroundColor ?? '#ffffff';
69
+ const offscreen = await renderOffscreen({
70
+ scheduled,
71
+ ganttProps: ganttProps
72
+ });
73
+ try {
74
+ const blob = await toBlob(offscreen.container, {
75
+ backgroundColor,
76
+ pixelRatio,
77
+ cacheBust: true
78
+ });
79
+ if (!blob) {
80
+ throw new Error('exportPNG: html-to-image returned null');
81
+ }
82
+ return blob;
83
+ } finally{
84
+ await offscreen.dispose();
85
+ }
86
+ }
87
+
88
+ export { exportPNG };
@@ -0,0 +1,89 @@
1
+ function __insertCSS(code) {
2
+ if (!code || typeof document == 'undefined') return
3
+ let head = document.head || document.getElementsByTagName('head')[0]
4
+ let style = document.createElement('style')
5
+ style.type = 'text/css'
6
+ head.appendChild(style)
7
+ ;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
8
+ }
9
+
10
+ import * as XLSX from 'xlsx';
11
+
12
+ const DEFAULT_XLSX_COLUMNS = [
13
+ {
14
+ header: 'ID',
15
+ value: 'id'
16
+ },
17
+ {
18
+ header: 'Name',
19
+ value: 'text'
20
+ },
21
+ {
22
+ header: 'Start',
23
+ value: 'start'
24
+ },
25
+ {
26
+ header: 'End',
27
+ value: 'end'
28
+ },
29
+ {
30
+ header: 'Duration (working minutes)',
31
+ value: 'duration'
32
+ },
33
+ {
34
+ header: 'Critical',
35
+ value: (t)=>t.computed?.isCritical ? 'Y' : 'N'
36
+ },
37
+ {
38
+ header: 'Total slack (working minutes)',
39
+ value: (t)=>t.computed?.totalSlack ?? 0
40
+ },
41
+ {
42
+ header: 'Progress (%)',
43
+ value: 'progress'
44
+ },
45
+ {
46
+ header: 'Parent',
47
+ value: (t)=>t.parent ?? ''
48
+ }
49
+ ];
50
+ function buildSheetRows(project, columns) {
51
+ const header = columns.map((c)=>c.header);
52
+ const dataRows = project.tasks.map((task)=>columns.map((c)=>readCell(task, c)));
53
+ return [
54
+ header,
55
+ ...dataRows
56
+ ];
57
+ }
58
+ function readCell(task, column) {
59
+ if (typeof column.value === 'function') {
60
+ const raw = column.value(task);
61
+ return raw ?? '';
62
+ }
63
+ const raw = task[column.value];
64
+ if (raw === undefined || raw === null) return '';
65
+ if (raw instanceof Date) return raw;
66
+ if (typeof raw === 'number' || typeof raw === 'string') return raw;
67
+ return String(raw);
68
+ }
69
+ async function exportXLSX({ scheduled, options }) {
70
+ const columns = options.columns ?? DEFAULT_XLSX_COLUMNS;
71
+ const sheetName = options.sheetName ?? 'Programme';
72
+ const rows = buildSheetRows(scheduled, columns);
73
+ const sheet = XLSX.utils.aoa_to_sheet(rows, {
74
+ cellDates: true
75
+ });
76
+ const workbook = XLSX.utils.book_new();
77
+ XLSX.utils.book_append_sheet(workbook, sheet, sheetName);
78
+ const arrayBuffer = XLSX.write(workbook, {
79
+ type: 'array',
80
+ bookType: 'xlsx'
81
+ });
82
+ return new Blob([
83
+ arrayBuffer
84
+ ], {
85
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
86
+ });
87
+ }
88
+
89
+ export { DEFAULT_XLSX_COLUMNS, buildSheetRows, exportXLSX };
@@ -0,0 +1,111 @@
1
+ function __insertCSS(code) {
2
+ if (!code || typeof document == 'undefined') return
3
+ let head = document.head || document.getElementsByTagName('head')[0]
4
+ let style = document.createElement('style')
5
+ style.type = 'text/css'
6
+ head.appendChild(style)
7
+ ;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
8
+ }
9
+
10
+ var XLSX = require('xlsx');
11
+
12
+ function _interopNamespace(e) {
13
+ if (e && e.__esModule) return e;
14
+ var n = Object.create(null);
15
+ if (e) {
16
+ Object.keys(e).forEach(function (k) {
17
+ if (k !== 'default') {
18
+ var d = Object.getOwnPropertyDescriptor(e, k);
19
+ Object.defineProperty(n, k, d.get ? d : {
20
+ enumerable: true,
21
+ get: function () { return e[k]; }
22
+ });
23
+ }
24
+ });
25
+ }
26
+ n.default = e;
27
+ return n;
28
+ }
29
+
30
+ var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
31
+
32
+ const DEFAULT_XLSX_COLUMNS = [
33
+ {
34
+ header: 'ID',
35
+ value: 'id'
36
+ },
37
+ {
38
+ header: 'Name',
39
+ value: 'text'
40
+ },
41
+ {
42
+ header: 'Start',
43
+ value: 'start'
44
+ },
45
+ {
46
+ header: 'End',
47
+ value: 'end'
48
+ },
49
+ {
50
+ header: 'Duration (working minutes)',
51
+ value: 'duration'
52
+ },
53
+ {
54
+ header: 'Critical',
55
+ value: (t)=>t.computed?.isCritical ? 'Y' : 'N'
56
+ },
57
+ {
58
+ header: 'Total slack (working minutes)',
59
+ value: (t)=>t.computed?.totalSlack ?? 0
60
+ },
61
+ {
62
+ header: 'Progress (%)',
63
+ value: 'progress'
64
+ },
65
+ {
66
+ header: 'Parent',
67
+ value: (t)=>t.parent ?? ''
68
+ }
69
+ ];
70
+ function buildSheetRows(project, columns) {
71
+ const header = columns.map((c)=>c.header);
72
+ const dataRows = project.tasks.map((task)=>columns.map((c)=>readCell(task, c)));
73
+ return [
74
+ header,
75
+ ...dataRows
76
+ ];
77
+ }
78
+ function readCell(task, column) {
79
+ if (typeof column.value === 'function') {
80
+ const raw = column.value(task);
81
+ return raw ?? '';
82
+ }
83
+ const raw = task[column.value];
84
+ if (raw === undefined || raw === null) return '';
85
+ if (raw instanceof Date) return raw;
86
+ if (typeof raw === 'number' || typeof raw === 'string') return raw;
87
+ return String(raw);
88
+ }
89
+ async function exportXLSX({ scheduled, options }) {
90
+ const columns = options.columns ?? DEFAULT_XLSX_COLUMNS;
91
+ const sheetName = options.sheetName ?? 'Programme';
92
+ const rows = buildSheetRows(scheduled, columns);
93
+ const sheet = XLSX__namespace.utils.aoa_to_sheet(rows, {
94
+ cellDates: true
95
+ });
96
+ const workbook = XLSX__namespace.utils.book_new();
97
+ XLSX__namespace.utils.book_append_sheet(workbook, sheet, sheetName);
98
+ const arrayBuffer = XLSX__namespace.write(workbook, {
99
+ type: 'array',
100
+ bookType: 'xlsx'
101
+ });
102
+ return new Blob([
103
+ arrayBuffer
104
+ ], {
105
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
106
+ });
107
+ }
108
+
109
+ exports.DEFAULT_XLSX_COLUMNS = DEFAULT_XLSX_COLUMNS;
110
+ exports.buildSheetRows = buildSheetRows;
111
+ exports.exportXLSX = exportXLSX;
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "construction-gantt",
3
+ "version": "0.1.0",
4
+ "description": "MIT React Gantt for construction project management — PRO-equivalent scheduling engine + construction-vertical extensions on free SVAR React Gantt",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Rips Cassels (DipSprayArc)",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/DipSprayArc/construction-gantt.git"
11
+ },
12
+ "keywords": [
13
+ "react",
14
+ "gantt",
15
+ "construction",
16
+ "scheduling",
17
+ "msproject",
18
+ "primavera",
19
+ "svar"
20
+ ],
21
+ "main": "./dist/index.cjs",
22
+ "module": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "import": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "require": {
31
+ "types": "./dist/index.d.cts",
32
+ "default": "./dist/index.cjs"
33
+ }
34
+ },
35
+ "./export": {
36
+ "import": {
37
+ "types": "./dist/export/index.d.ts",
38
+ "default": "./dist/export/index.js"
39
+ },
40
+ "require": {
41
+ "types": "./dist/export/index.d.cts",
42
+ "default": "./dist/export/index.cjs"
43
+ }
44
+ }
45
+ },
46
+ "sideEffects": [
47
+ "**/*.css"
48
+ ],
49
+ "files": [
50
+ "dist",
51
+ "src",
52
+ "!src/**/*.test.ts",
53
+ "!src/**/*.test.tsx",
54
+ "!src/**/__fixtures__/**",
55
+ "README.md",
56
+ "LICENSE.md",
57
+ "CHANGELOG.md"
58
+ ],
59
+ "peerDependencies": {
60
+ "react": "^18.0.0 || ^19.0.0",
61
+ "react-dom": "^18.0.0 || ^19.0.0"
62
+ },
63
+ "dependencies": {
64
+ "@svar-ui/react-gantt": "^2.6.1",
65
+ "fast-xml-parser": "^4.5.6",
66
+ "html-to-image": "^1.11.13",
67
+ "jspdf": "^2.5.2",
68
+ "xlsx": "^0.18.5"
69
+ },
70
+ "devDependencies": {
71
+ "@testing-library/react": "^16.3.2",
72
+ "@types/react": "^19.2.14",
73
+ "@types/react-dom": "^19.2.3",
74
+ "bunchee": "^6.10.0",
75
+ "react": "^19.2.6",
76
+ "react-dom": "^19.2.6",
77
+ "typescript": "^5.9.2"
78
+ },
79
+ "scripts": {
80
+ "build": "bunchee",
81
+ "dev": "bunchee --watch",
82
+ "clean": "rm -rf dist",
83
+ "typecheck": "tsc --noEmit",
84
+ "test": "vitest run --project=unit"
85
+ }
86
+ }