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.
- package/CHANGELOG.md +57 -0
- package/README.md +5 -0
- package/dist/export/index.cjs +1 -0
- package/dist/export/index.d.cts +112 -0
- package/dist/export/index.d.cts.map +1 -0
- package/dist/export/index.d.ts +112 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +1 -0
- package/dist/index.cjs +3492 -0
- package/dist/index.d.cts +628 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +628 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3461 -0
- package/dist/pdf-CAQDrX0w.cjs +120 -0
- package/dist/pdf-CBaoJRTI.js +120 -0
- package/dist/png-C8t74695.cjs +88 -0
- package/dist/png-DKZeKnRh.js +88 -0
- package/dist/xlsx-5FRPFck7.js +89 -0
- package/dist/xlsx-Gh5L_NL3.cjs +111 -0
- package/package.json +86 -0
- package/src/Gantt.css +23 -0
- package/src/Gantt.tsx +636 -0
- package/src/SpikeGantt.tsx +114 -0
- package/src/analysis.ts +83 -0
- package/src/baseline.ts +119 -0
- package/src/calendars/canterbury-table.ts +44 -0
- package/src/calendars/internal/computus.ts +25 -0
- package/src/calendars/internal/date-utils.ts +13 -0
- package/src/calendars/internal/mondayisation.ts +46 -0
- package/src/calendars/internal/month-rules.ts +65 -0
- package/src/calendars/matariki-table.ts +63 -0
- package/src/calendars/nz-holidays.ts +214 -0
- package/src/editing/command-history.ts +78 -0
- package/src/editing/commands.ts +327 -0
- package/src/editing/composite-command.ts +64 -0
- package/src/editing/draft-project.ts +59 -0
- package/src/editing/errors.ts +14 -0
- package/src/editing/factories.ts +92 -0
- package/src/editing/use-editable-project.ts +122 -0
- package/src/export/index.ts +12 -0
- package/src/export/offscreen.tsx +89 -0
- package/src/export/pdf-dimensions.ts +64 -0
- package/src/export/pdf.ts +68 -0
- package/src/export/png.ts +48 -0
- package/src/export/types.ts +42 -0
- package/src/export/xlsx.ts +70 -0
- package/src/index.ts +89 -0
- package/src/mspdi/parse.ts +820 -0
- package/src/mspdi/serialize.ts +352 -0
- package/src/mspdi/types.ts +53 -0
- package/src/schedule.ts +470 -0
- package/src/topological-sort.ts +51 -0
- package/src/types.ts +254 -0
- package/src/visibility.ts +35 -0
- 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
|
+
}
|