@lateralus-ai/shipping-ui 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/DocumentEditor/DocumentEditor.d.ts +4 -0
- package/dist/components/DocumentEditor/index.d.ts +1 -0
- package/dist/components/PdfViewer/PdfViewer.d.ts +3 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/index.cjs +272 -21
- package/dist/index.esm.js +28668 -10625
- package/dist/sample-document.docx +0 -0
- package/dist/types/documentEditor.d.ts +47 -0
- package/dist/utils/checkboxModule.d.ts +54 -0
- package/package.json +4 -1
- package/src/components/DocumentEditor/DocumentEditor.tsx +872 -0
- package/src/components/DocumentEditor/index.ts +1 -0
- package/src/components/PdfViewer/PdfViewer.tsx +20 -2
- package/src/components/index.ts +1 -0
- package/src/stories/DocumentEditor.stories.tsx +287 -0
- package/src/types/documentEditor.ts +56 -0
- package/src/utils/checkboxModule.ts +242 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as DocumentEditor } from "./DocumentEditor";
|
|
@@ -6,7 +6,7 @@ import { Icon } from "@iconify/react";
|
|
|
6
6
|
import { cn } from "../../utils/cn";
|
|
7
7
|
import "react-pdf/dist/Page/AnnotationLayer.css";
|
|
8
8
|
import "react-pdf/dist/Page/TextLayer.css";
|
|
9
|
-
import { useState, ChangeEvent, useMemo } from "react";
|
|
9
|
+
import { useState, ChangeEvent, useMemo, MouseEvent as ReactMouseEvent } from "react";
|
|
10
10
|
import { useZoom } from "./useZoom";
|
|
11
11
|
import { useRotation } from "./useRotation";
|
|
12
12
|
import { usePageManagement } from "./usePageManagement";
|
|
@@ -18,6 +18,7 @@ interface PdfViewerProps extends React.HTMLProps<HTMLDivElement> {
|
|
|
18
18
|
onClose: () => void;
|
|
19
19
|
src: string;
|
|
20
20
|
title?: string;
|
|
21
|
+
onOpen?: (event: ReactMouseEvent<HTMLAnchorElement>) => void;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export const PdfViewer = ({
|
|
@@ -25,16 +26,33 @@ export const PdfViewer = ({
|
|
|
25
26
|
src,
|
|
26
27
|
title = "PDF Viewer",
|
|
27
28
|
className,
|
|
29
|
+
onOpen,
|
|
28
30
|
}: PdfViewerProps) => {
|
|
29
31
|
const [zoom, zoomActions] = useZoom();
|
|
30
32
|
const [rotation, rotationActions] = useRotation();
|
|
31
33
|
const [{ currentPage, totalPages }, pageActions] = usePageManagement();
|
|
32
34
|
const [{ pan, isDragging }, panActions] = usePanning();
|
|
33
35
|
|
|
36
|
+
const handleOpen = (event: ReactMouseEvent<HTMLAnchorElement>) => {
|
|
37
|
+
onOpen?.(event);
|
|
38
|
+
if (event.defaultPrevented) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Allow default anchor behavior when event isn't prevented
|
|
43
|
+
if (!src) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
34
47
|
|
|
35
48
|
const rightButtons = (
|
|
36
49
|
<IconButton variant="text" color="gray">
|
|
37
|
-
<a
|
|
50
|
+
<a
|
|
51
|
+
href={src}
|
|
52
|
+
target="_blank"
|
|
53
|
+
rel="noopener noreferrer"
|
|
54
|
+
onClick={handleOpen}
|
|
55
|
+
>
|
|
38
56
|
<ExpandIcon className="size-4" />
|
|
39
57
|
</a>
|
|
40
58
|
</IconButton>
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { DocumentEditor } from "../components/DocumentEditor";
|
|
3
|
+
import type { Schema } from "../types/documentEditor";
|
|
4
|
+
|
|
5
|
+
// Sample schema based on the defect report form
|
|
6
|
+
const sampleSchema: Schema = {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
shipName: {
|
|
10
|
+
type: "string",
|
|
11
|
+
description: "Name of the ship",
|
|
12
|
+
},
|
|
13
|
+
reportDate: {
|
|
14
|
+
type: "string",
|
|
15
|
+
format: "date",
|
|
16
|
+
description: "Date of the report",
|
|
17
|
+
},
|
|
18
|
+
reportNo: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Report number",
|
|
21
|
+
},
|
|
22
|
+
malfunctionDateTime: {
|
|
23
|
+
type: "string",
|
|
24
|
+
format: "date",
|
|
25
|
+
description: "Date the malfunction occurred",
|
|
26
|
+
},
|
|
27
|
+
malfunctionDescription: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Description of the malfunction or defect",
|
|
30
|
+
},
|
|
31
|
+
isCriticalEquipment: {
|
|
32
|
+
type: "boolean",
|
|
33
|
+
description: "Whether the equipment referenced is critical",
|
|
34
|
+
docx_mapping: {
|
|
35
|
+
type: "checkbox",
|
|
36
|
+
true_name: "Check1",
|
|
37
|
+
false_name: "Check2",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
equipmentManufacturer: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "The malfunctioning equipment manufacturer",
|
|
43
|
+
},
|
|
44
|
+
equipmentModel: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "The malfunctioning equipment model",
|
|
47
|
+
},
|
|
48
|
+
equipmentSerial: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "The malfunctioning equipment serial number",
|
|
51
|
+
},
|
|
52
|
+
equipmentManufacturerModelSerial: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Manufacturer, model, type, and serial number",
|
|
55
|
+
},
|
|
56
|
+
consequences: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "Consequences of the malfunction",
|
|
59
|
+
},
|
|
60
|
+
rootCause: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "Root cause of the defect (if known)",
|
|
63
|
+
},
|
|
64
|
+
actionsTaken: {
|
|
65
|
+
type: "string",
|
|
66
|
+
description: "Actions taken or suggestions for corrective actions",
|
|
67
|
+
},
|
|
68
|
+
isShoreAssistanceRequired: {
|
|
69
|
+
type: "boolean",
|
|
70
|
+
description: "Whether shore assistance is required",
|
|
71
|
+
docx_mapping: {
|
|
72
|
+
type: "checkbox",
|
|
73
|
+
true_name: "Check3",
|
|
74
|
+
false_name: "Check4",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
shoreAssistanceRequisition: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Requisition details for shore assistance",
|
|
80
|
+
},
|
|
81
|
+
isEquipmentLandedAshore: {
|
|
82
|
+
type: "boolean",
|
|
83
|
+
description: "Whether equipment/machinery was landed ashore",
|
|
84
|
+
docx_mapping: {
|
|
85
|
+
type: "checkbox",
|
|
86
|
+
true_name: "Check5",
|
|
87
|
+
false_name: "Check6",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
equipmentLandedRequisition: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Requisition details for equipment landed ashore",
|
|
93
|
+
},
|
|
94
|
+
areSparesAvailableOnboard: {
|
|
95
|
+
type: "boolean",
|
|
96
|
+
description: "Whether required spare parts are available onboard",
|
|
97
|
+
docx_mapping: {
|
|
98
|
+
type: "checkbox",
|
|
99
|
+
true_name: "Check7",
|
|
100
|
+
false_name: "Check8",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
sparesAvailableRequisition: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "Requisition details if spares are available onboard",
|
|
106
|
+
},
|
|
107
|
+
isNextDryDock: {
|
|
108
|
+
type: "boolean",
|
|
109
|
+
description: "If it's going to close out in next dry dock",
|
|
110
|
+
docx_mapping: {
|
|
111
|
+
type: "checkbox",
|
|
112
|
+
true_name: "Check9",
|
|
113
|
+
false_name: "Check10",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
proposedCompletionDate: {
|
|
117
|
+
type: "string",
|
|
118
|
+
format: "date",
|
|
119
|
+
description: "Proposed date for completion",
|
|
120
|
+
},
|
|
121
|
+
chiefEngineerName: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "Name of the Chief Engineer",
|
|
124
|
+
},
|
|
125
|
+
chiefEngineerSignature: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "Signature of the Chief Engineer (surname only)",
|
|
128
|
+
},
|
|
129
|
+
chiefEngineerDate: {
|
|
130
|
+
type: "string",
|
|
131
|
+
format: "date",
|
|
132
|
+
description: "Date signed by the Chief Engineer",
|
|
133
|
+
},
|
|
134
|
+
masterName: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Name of the Master",
|
|
137
|
+
},
|
|
138
|
+
masterSignature: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Signature of the Master (surname only)",
|
|
141
|
+
},
|
|
142
|
+
masterDate: {
|
|
143
|
+
type: "string",
|
|
144
|
+
format: "date",
|
|
145
|
+
description: "Date signed by the Master",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
required: [],
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Sample initial data for demonstration
|
|
152
|
+
const sampleInitialData = {
|
|
153
|
+
shipName: "MV Excellence",
|
|
154
|
+
reportNo: "DR-2024-001",
|
|
155
|
+
reportDate: "2024-03-15",
|
|
156
|
+
malfunctionDateTime: "2024-03-14",
|
|
157
|
+
malfunctionDescription:
|
|
158
|
+
"Main engine turbocharger failure during normal operation",
|
|
159
|
+
isCriticalEquipment: true,
|
|
160
|
+
equipmentManufacturer: "ABB",
|
|
161
|
+
equipmentModel: "TPL 85-B",
|
|
162
|
+
equipmentSerial: "TC-12345-2020",
|
|
163
|
+
consequences: "Reduced engine power, speed limited to 10 knots",
|
|
164
|
+
rootCause: "Bearing failure due to inadequate lubrication",
|
|
165
|
+
actionsTaken:
|
|
166
|
+
"Turbocharger isolated, vessel proceeding at reduced speed to nearest port",
|
|
167
|
+
isShoreAssistanceRequired: true,
|
|
168
|
+
shoreAssistanceRequisition:
|
|
169
|
+
"Service engineer required for turbocharger overhaul",
|
|
170
|
+
isEquipmentLandedAshore: false,
|
|
171
|
+
areSparesAvailableOnboard: false,
|
|
172
|
+
isNextDryDock: false,
|
|
173
|
+
proposedCompletionDate: "2024-03-20",
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const DocumentEditorWrapper = () => {
|
|
177
|
+
const handleDataChange = (data: Record<string, any>) => {
|
|
178
|
+
console.log("Data changed:", data);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className="w-full max-w-6xl mx-auto p-4">
|
|
183
|
+
<DocumentEditor
|
|
184
|
+
docxUrl="/sample-document.docx"
|
|
185
|
+
schema={sampleSchema}
|
|
186
|
+
onDataChange={handleDataChange}
|
|
187
|
+
initialData={sampleInitialData}
|
|
188
|
+
readonly={false}
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const meta = {
|
|
195
|
+
title: "Design System/DocumentEditor",
|
|
196
|
+
component: DocumentEditorWrapper,
|
|
197
|
+
parameters: {
|
|
198
|
+
layout: "fullscreen",
|
|
199
|
+
docs: {
|
|
200
|
+
description: {
|
|
201
|
+
component: `
|
|
202
|
+
The DocumentEditor component provides an interactive Word document editor with inline field editing capabilities.
|
|
203
|
+
|
|
204
|
+
## Features
|
|
205
|
+
- Renders Word documents using docx-preview
|
|
206
|
+
- Supports inline editing with various field types (text, boolean, date, dropdown)
|
|
207
|
+
- Tracks field changes with visual color coding
|
|
208
|
+
- Handles checkbox patterns (single, dual Yes/No, radio buttons)
|
|
209
|
+
- Preserves document formatting and layout
|
|
210
|
+
|
|
211
|
+
## Usage
|
|
212
|
+
\`\`\`tsx
|
|
213
|
+
import { DocumentEditor } from '@lateralus-ai/shipping-ui';
|
|
214
|
+
|
|
215
|
+
<DocumentEditor
|
|
216
|
+
docxUrl="/path/to/document.docx"
|
|
217
|
+
schema={documentSchema}
|
|
218
|
+
onDataChange={(data) => console.log(data)}
|
|
219
|
+
initialData={initialValues}
|
|
220
|
+
readonly={false}
|
|
221
|
+
/>
|
|
222
|
+
\`\`\`
|
|
223
|
+
`,
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
tags: ["autodocs"],
|
|
228
|
+
argTypes: {
|
|
229
|
+
docxUrl: {
|
|
230
|
+
control: "text",
|
|
231
|
+
description: "URL to the DOCX document template",
|
|
232
|
+
},
|
|
233
|
+
readonly: {
|
|
234
|
+
control: "boolean",
|
|
235
|
+
description: "Whether the document is in read-only mode",
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
} satisfies Meta<typeof DocumentEditorWrapper>;
|
|
239
|
+
|
|
240
|
+
export default meta;
|
|
241
|
+
type Story = StoryObj<typeof meta>;
|
|
242
|
+
|
|
243
|
+
export const Default: Story = {
|
|
244
|
+
render: () => {
|
|
245
|
+
return (
|
|
246
|
+
<div className="w-full max-w-6xl mx-auto p-4">
|
|
247
|
+
<DocumentEditor
|
|
248
|
+
docxUrl="/sample-document.docx"
|
|
249
|
+
schema={sampleSchema}
|
|
250
|
+
initialData={sampleInitialData}
|
|
251
|
+
onDataChange={(data) => console.log(data)}
|
|
252
|
+
/>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export const ReadOnly: Story = {
|
|
259
|
+
render: () => {
|
|
260
|
+
return (
|
|
261
|
+
<div className="w-full max-w-6xl mx-auto p-4">
|
|
262
|
+
<DocumentEditor
|
|
263
|
+
docxUrl="/sample-document.docx"
|
|
264
|
+
schema={sampleSchema}
|
|
265
|
+
initialData={sampleInitialData}
|
|
266
|
+
readonly={true}
|
|
267
|
+
/>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export const Empty: Story = {
|
|
274
|
+
render: () => {
|
|
275
|
+
return (
|
|
276
|
+
<div className="w-full max-w-6xl mx-auto p-4">
|
|
277
|
+
<DocumentEditor
|
|
278
|
+
docxUrl="/sample-document.docx"
|
|
279
|
+
schema={sampleSchema}
|
|
280
|
+
initialData={{}}
|
|
281
|
+
readonly={false}
|
|
282
|
+
onDataChange={(data) => console.log(data)}
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
},
|
|
287
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Type definitions for Document Editor components
|
|
2
|
+
|
|
3
|
+
export interface SchemaProperty {
|
|
4
|
+
type: "string" | "boolean" | "number" | "integer";
|
|
5
|
+
enum?: string[];
|
|
6
|
+
format?: "date" | "date-time" | string;
|
|
7
|
+
description?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
"ui:widget"?: string;
|
|
10
|
+
"ui:rows"?: number;
|
|
11
|
+
docx_mapping?: DocxMapping;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DocxMapping {
|
|
15
|
+
type: "checkbox" | "radio" | "text";
|
|
16
|
+
name?: string;
|
|
17
|
+
true_name?: string;
|
|
18
|
+
false_name?: string;
|
|
19
|
+
mapping?: RadioMapping[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RadioMapping {
|
|
23
|
+
name: string;
|
|
24
|
+
value: string | boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Schema {
|
|
28
|
+
type: string;
|
|
29
|
+
properties: Record<string, SchemaProperty>;
|
|
30
|
+
required?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CheckboxModuleOptions {
|
|
34
|
+
checkboxData?: Record<string, boolean>;
|
|
35
|
+
schema?: Schema;
|
|
36
|
+
finalGeneration?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DocumentEditorProps {
|
|
40
|
+
docxUrl: string;
|
|
41
|
+
schema: Schema;
|
|
42
|
+
onDataChange?: (data: Record<string, any>) => void;
|
|
43
|
+
initialData?: Record<string, any>;
|
|
44
|
+
onSaveRef?: React.MutableRefObject<(() => void) | null>;
|
|
45
|
+
readonly?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DocxtemplaterPart {
|
|
49
|
+
value?: string;
|
|
50
|
+
[key: string]: any;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DocxtemplaterOptions {
|
|
54
|
+
contentType?: string;
|
|
55
|
+
[key: string]: any;
|
|
56
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CheckboxModuleOptions,
|
|
3
|
+
Schema,
|
|
4
|
+
DocxtemplaterPart,
|
|
5
|
+
DocxtemplaterOptions,
|
|
6
|
+
} from "../types/documentEditor";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* CheckboxModule for docxtemplater
|
|
10
|
+
*
|
|
11
|
+
* This module handles three distinct checkbox patterns in DOCX documents:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Single Checkbox Pattern**: A field maps to a single checkbox
|
|
14
|
+
* - Schema: `docx_mapping: { type: "checkbox", name: "Check1" }`
|
|
15
|
+
* - Maps boolean field value directly to checkbox state
|
|
16
|
+
*
|
|
17
|
+
* 2. **Dual Checkbox Pattern**: Yes/No checkboxes for a single field
|
|
18
|
+
* - Schema: `docx_mapping: { type: "checkbox", true_name: "Check1", false_name: "Check2" }`
|
|
19
|
+
* - true_name checkbox checked when field is true
|
|
20
|
+
* - false_name checkbox checked when field is false
|
|
21
|
+
* - Used for Yes/No questions where both options have checkboxes
|
|
22
|
+
*
|
|
23
|
+
* 3. **Radio Button Pattern**: Multiple checkboxes acting as radio buttons
|
|
24
|
+
* - Schema: `docx_mapping: { type: "radio", mapping: [{ name: "Check1", value: "option1" }, ...] }`
|
|
25
|
+
* - Only the checkbox corresponding to the field's value is checked
|
|
26
|
+
* - Used for multiple choice questions
|
|
27
|
+
*
|
|
28
|
+
* The module operates in two modes:
|
|
29
|
+
* - **Preview Mode** (finalGeneration: false): Creates visual markers for inline editing
|
|
30
|
+
* - **Final Generation Mode** (finalGeneration: true): Updates actual Word form fields
|
|
31
|
+
*/
|
|
32
|
+
class CheckboxModule {
|
|
33
|
+
private checkboxData: Record<string, boolean>;
|
|
34
|
+
private schema?: Schema;
|
|
35
|
+
private finalGeneration: boolean;
|
|
36
|
+
|
|
37
|
+
constructor(options: CheckboxModuleOptions = {}) {
|
|
38
|
+
this.checkboxData = options.checkboxData || {};
|
|
39
|
+
this.schema = options.schema;
|
|
40
|
+
this.finalGeneration = options.finalGeneration || false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Required docxtemplater module interface
|
|
44
|
+
name = "CheckboxModule";
|
|
45
|
+
|
|
46
|
+
parse(placeHolderContent: string) {
|
|
47
|
+
// Return null to indicate this module doesn't handle this placeholder
|
|
48
|
+
// This allows other modules/default processing to handle it
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
postparse(parsed: any) {
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
render(part: any, options: any) {
|
|
57
|
+
return part;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
postrender(parts: DocxtemplaterPart[], options: DocxtemplaterOptions) {
|
|
61
|
+
const hasValidOptions = !!(
|
|
62
|
+
options &&
|
|
63
|
+
options.contentType &&
|
|
64
|
+
options.contentType ===
|
|
65
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return !hasValidOptions ? parts : (() => {
|
|
69
|
+
try {
|
|
70
|
+
// Convert parts to string for processing
|
|
71
|
+
const xmlContent = parts
|
|
72
|
+
.map((part) => {
|
|
73
|
+
const isString = typeof part === "string";
|
|
74
|
+
const hasValue = part && typeof part === "object" && part.value;
|
|
75
|
+
|
|
76
|
+
const getString = () => isString && part;
|
|
77
|
+
const getValue = () => !isString && hasValue && part.value;
|
|
78
|
+
const getStringified = () => !isString && !hasValue && String(part);
|
|
79
|
+
|
|
80
|
+
return getString() || getValue() || getStringified();
|
|
81
|
+
})
|
|
82
|
+
.join("");
|
|
83
|
+
|
|
84
|
+
// Process checkbox patterns
|
|
85
|
+
const processedContent = this.processCheckboxPatterns(xmlContent);
|
|
86
|
+
|
|
87
|
+
// Return the processed content maintaining the same structure as input
|
|
88
|
+
return [processedContent];
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// Return original parts if processing fails
|
|
91
|
+
return parts;
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Process checkbox patterns in the document XML
|
|
98
|
+
* Identifies Word form field checkboxes by their bookmark names (Check1, Check2, etc.)
|
|
99
|
+
* and either updates their values or replaces them with visual markers
|
|
100
|
+
*/
|
|
101
|
+
private processCheckboxPatterns(xmlContent: string): string {
|
|
102
|
+
try {
|
|
103
|
+
// Pattern 1: Simple checkbox bookmarks (most conservative approach)
|
|
104
|
+
// Matches: <w:bookmarkStart w:name="Check1" w:id="0"/>
|
|
105
|
+
const simpleBookmarkRegex =
|
|
106
|
+
/<w:bookmarkStart[^>]*w:name="(Check\d+)"[^>]*\/>/gi;
|
|
107
|
+
|
|
108
|
+
// Reset regex
|
|
109
|
+
simpleBookmarkRegex.lastIndex = 0;
|
|
110
|
+
|
|
111
|
+
const checkboxMatches: string[] = [];
|
|
112
|
+
let match;
|
|
113
|
+
while ((match = simpleBookmarkRegex.exec(xmlContent)) !== null) {
|
|
114
|
+
checkboxMatches.push(match[1]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Only process if we found actual checkbox bookmarks
|
|
118
|
+
const hasCheckboxMatches = checkboxMatches.length > 0;
|
|
119
|
+
|
|
120
|
+
return !hasCheckboxMatches ? xmlContent : (() => {
|
|
121
|
+
const processForFinalGeneration = () => {
|
|
122
|
+
// For final generation, update the checkbox default values directly in the XML
|
|
123
|
+
checkboxMatches.forEach((checkboxName) => {
|
|
124
|
+
const isChecked = this.getCheckboxState(checkboxName);
|
|
125
|
+
const checkedValue = isChecked ? "1" : "0";
|
|
126
|
+
|
|
127
|
+
// Update the default value in the checkbox XML
|
|
128
|
+
const checkboxRegex = new RegExp(
|
|
129
|
+
`(<w:fldChar w:fldCharType="begin">.*?<w:name w:val="${checkboxName}".*?<w:default w:val=")([01])(".*?</w:fldChar>)`,
|
|
130
|
+
"gs",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
xmlContent = xmlContent.replace(checkboxRegex, `$1${checkedValue}$3`);
|
|
134
|
+
});
|
|
135
|
+
return xmlContent;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const processForPreview = () => {
|
|
139
|
+
// For preview mode, create visual markers
|
|
140
|
+
// Reset regex for replacement
|
|
141
|
+
simpleBookmarkRegex.lastIndex = 0;
|
|
142
|
+
|
|
143
|
+
return xmlContent.replace(
|
|
144
|
+
simpleBookmarkRegex,
|
|
145
|
+
(match, checkboxName) => {
|
|
146
|
+
try {
|
|
147
|
+
const isChecked = this.getCheckboxState(checkboxName);
|
|
148
|
+
const symbol = isChecked ? "☑" : "☐";
|
|
149
|
+
const marker = `|||${symbol}|||checkbox:${checkboxName}|||`;
|
|
150
|
+
|
|
151
|
+
// Replace just the bookmark start with our marker in a text run
|
|
152
|
+
return `<w:r><w:t>${marker}</w:t></w:r><w:bookmarkStart w:name="${checkboxName}" w:id="0"/>`;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// Return original if processing fails
|
|
155
|
+
return match;
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const renderFinalGeneration = () => this.finalGeneration && processForFinalGeneration();
|
|
162
|
+
const renderPreview = () => !this.finalGeneration && processForPreview();
|
|
163
|
+
|
|
164
|
+
return renderFinalGeneration() || renderPreview();
|
|
165
|
+
})();
|
|
166
|
+
} catch (error) {
|
|
167
|
+
// Return original content if processing fails
|
|
168
|
+
return xmlContent;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Determine the state of a checkbox based on field mappings and data
|
|
174
|
+
* @param checkboxName - The name of the checkbox (e.g., "Check1")
|
|
175
|
+
* @returns true if checkbox should be checked, false otherwise
|
|
176
|
+
*/
|
|
177
|
+
private getCheckboxState(checkboxName: string): boolean {
|
|
178
|
+
const hasSchema = !!this.schema;
|
|
179
|
+
|
|
180
|
+
const getSimpleLookup = () => !hasSchema && (this.checkboxData[checkboxName] || false);
|
|
181
|
+
const getSchemaBasedLookup = () => hasSchema && (() => {
|
|
182
|
+
// Check all fields in the schema for checkbox mappings
|
|
183
|
+
const matchedField = Object.entries(
|
|
184
|
+
this.schema!.properties || {},
|
|
185
|
+
).find(([fieldName, fieldConfig]) => {
|
|
186
|
+
const config = fieldConfig as any;
|
|
187
|
+
const docxMapping = config.docx_mapping;
|
|
188
|
+
const hasCheckboxMapping = docxMapping && docxMapping.type === "checkbox";
|
|
189
|
+
const hasRadioMapping = docxMapping && docxMapping.type === "radio" && docxMapping.mapping;
|
|
190
|
+
|
|
191
|
+
// Pattern 1: Single checkbox with "name" property
|
|
192
|
+
const isSingleCheckbox = hasCheckboxMapping && docxMapping.name === checkboxName;
|
|
193
|
+
|
|
194
|
+
// Pattern 2: Dual checkbox with "true_name" and "false_name" properties
|
|
195
|
+
const isTrueCheckbox = hasCheckboxMapping && docxMapping.true_name === checkboxName;
|
|
196
|
+
const isFalseCheckbox = hasCheckboxMapping && docxMapping.false_name === checkboxName;
|
|
197
|
+
|
|
198
|
+
// Pattern 3: Radio button mapping
|
|
199
|
+
const radioMapping = hasRadioMapping && docxMapping.mapping.find(
|
|
200
|
+
(m: any) => m.name === checkboxName,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
return isSingleCheckbox || isTrueCheckbox || isFalseCheckbox || !!radioMapping;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return matchedField ? (() => {
|
|
207
|
+
const [fieldName, fieldConfig] = matchedField;
|
|
208
|
+
const config = fieldConfig as any;
|
|
209
|
+
const docxMapping = config.docx_mapping;
|
|
210
|
+
|
|
211
|
+
// Pattern 1: Single checkbox with "name" property
|
|
212
|
+
const isSingleCheckbox = docxMapping && docxMapping.type === "checkbox" && docxMapping.name === checkboxName;
|
|
213
|
+
|
|
214
|
+
// Pattern 2: Dual checkbox with "true_name" and "false_name" properties
|
|
215
|
+
const isTrueCheckbox = docxMapping && docxMapping.type === "checkbox" && docxMapping.true_name === checkboxName;
|
|
216
|
+
const isFalseCheckbox = docxMapping && docxMapping.type === "checkbox" && docxMapping.false_name === checkboxName;
|
|
217
|
+
|
|
218
|
+
// Pattern 3: Radio button mapping
|
|
219
|
+
const radioMapping = docxMapping && docxMapping.type === "radio" && docxMapping.mapping &&
|
|
220
|
+
docxMapping.mapping.find((m: any) => m.name === checkboxName);
|
|
221
|
+
|
|
222
|
+
const checkSingleOrTrue = () => (isSingleCheckbox || isTrueCheckbox) && this.checkboxData[fieldName] === true;
|
|
223
|
+
const checkFalse = () => isFalseCheckbox && this.checkboxData[fieldName] === false;
|
|
224
|
+
const checkRadio = () => radioMapping && this.checkboxData[fieldName] === radioMapping.value;
|
|
225
|
+
|
|
226
|
+
return checkSingleOrTrue() || checkFalse() || checkRadio() || false;
|
|
227
|
+
})() : (this.checkboxData[checkboxName] || false);
|
|
228
|
+
})();
|
|
229
|
+
|
|
230
|
+
return getSimpleLookup() || getSchemaBasedLookup() || false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Update checkbox data (can be called to change checkbox states)
|
|
235
|
+
* @param checkboxData - New checkbox data to merge with existing data
|
|
236
|
+
*/
|
|
237
|
+
updateCheckboxData(checkboxData: Record<string, boolean>) {
|
|
238
|
+
this.checkboxData = { ...this.checkboxData, ...checkboxData };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export default CheckboxModule;
|