@visns-studio/visns-components 5.13.13 → 5.13.15
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/package.json
CHANGED
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
89
89
|
},
|
|
90
90
|
"name": "@visns-studio/visns-components",
|
|
91
|
-
"version": "5.13.
|
|
91
|
+
"version": "5.13.15",
|
|
92
92
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
93
93
|
"main": "src/index.js",
|
|
94
94
|
"files": [
|
|
@@ -1249,85 +1249,77 @@ const GenericEditableTable = ({
|
|
|
1249
1249
|
if (!hasMarginRow) return null;
|
|
1250
1250
|
|
|
1251
1251
|
const totalProjectCost = calculateTotalProjectCost(localData);
|
|
1252
|
+
const gst = totalProjectCost * 0.1;
|
|
1253
|
+
const totalWithGst = totalProjectCost + gst;
|
|
1252
1254
|
const marginAmount = calculateMarginAmount(
|
|
1253
1255
|
totalProjectCost,
|
|
1254
1256
|
marginPercentage
|
|
1255
1257
|
);
|
|
1256
|
-
const
|
|
1258
|
+
const totalLkdProjectCost = totalProjectCost + marginAmount;
|
|
1259
|
+
const totalLkdGst = totalLkdProjectCost * 0.1;
|
|
1260
|
+
const totalLkdWithGst = totalLkdProjectCost + totalLkdGst;
|
|
1257
1261
|
|
|
1258
1262
|
return (
|
|
1259
1263
|
<>
|
|
1260
|
-
{/*
|
|
1261
|
-
<tr
|
|
1264
|
+
{/* Total Project Cost */}
|
|
1265
|
+
<tr style={{ backgroundColor: '#e9ecef', fontWeight: 'bold' }}>
|
|
1266
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1267
|
+
Total Project Cost
|
|
1268
|
+
</td>
|
|
1262
1269
|
<td
|
|
1263
|
-
colSpan={
|
|
1264
|
-
style={{
|
|
1265
|
-
backgroundColor: '#e9ecef',
|
|
1266
|
-
fontWeight: 'bold',
|
|
1267
|
-
padding: '8px 12px',
|
|
1268
|
-
}}
|
|
1270
|
+
colSpan={4}
|
|
1271
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1269
1272
|
>
|
|
1270
|
-
{
|
|
1273
|
+
{totalProjectCost.toLocaleString('en-AU', {
|
|
1274
|
+
style: 'currency',
|
|
1275
|
+
currency: 'AUD',
|
|
1276
|
+
})}
|
|
1271
1277
|
</td>
|
|
1278
|
+
<td></td>
|
|
1272
1279
|
</tr>
|
|
1273
1280
|
|
|
1274
|
-
{/*
|
|
1275
|
-
<tr>
|
|
1276
|
-
{
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
textAlign: 'left',
|
|
1283
|
-
width: '60%',
|
|
1284
|
-
}}
|
|
1285
|
-
colSpan={6}
|
|
1286
|
-
>
|
|
1287
|
-
Description
|
|
1288
|
-
</th>
|
|
1289
|
-
{/* Margin % column - smaller width */}
|
|
1290
|
-
<th
|
|
1291
|
-
style={{
|
|
1292
|
-
color: 'white',
|
|
1293
|
-
fontWeight: 'bold',
|
|
1294
|
-
padding: '8px',
|
|
1295
|
-
textAlign: 'left',
|
|
1296
|
-
width: '20%',
|
|
1297
|
-
}}
|
|
1298
|
-
colSpan={2}
|
|
1299
|
-
>
|
|
1300
|
-
Margin %
|
|
1301
|
-
</th>
|
|
1302
|
-
{/* Total column */}
|
|
1303
|
-
<th
|
|
1304
|
-
style={{
|
|
1305
|
-
color: 'white',
|
|
1306
|
-
fontWeight: 'bold',
|
|
1307
|
-
padding: '8px',
|
|
1308
|
-
textAlign: 'right',
|
|
1309
|
-
width: '20%',
|
|
1310
|
-
}}
|
|
1311
|
-
colSpan={2}
|
|
1281
|
+
{/* GST */}
|
|
1282
|
+
<tr style={{ backgroundColor: '#e9ecef' }}>
|
|
1283
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1284
|
+
GST
|
|
1285
|
+
</td>
|
|
1286
|
+
<td
|
|
1287
|
+
colSpan={4}
|
|
1288
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1312
1289
|
>
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1290
|
+
{gst.toLocaleString('en-AU', {
|
|
1291
|
+
style: 'currency',
|
|
1292
|
+
currency: 'AUD',
|
|
1293
|
+
})}
|
|
1294
|
+
</td>
|
|
1295
|
+
<td></td>
|
|
1316
1296
|
</tr>
|
|
1317
1297
|
|
|
1318
|
-
{/*
|
|
1319
|
-
<tr style={{ backgroundColor: '#
|
|
1320
|
-
{
|
|
1298
|
+
{/* Total Project Cost (Including GST) */}
|
|
1299
|
+
<tr style={{ backgroundColor: '#e9ecef', fontWeight: 'bold' }}>
|
|
1300
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1301
|
+
Total Project Cost (Including GST)
|
|
1302
|
+
</td>
|
|
1321
1303
|
<td
|
|
1322
|
-
|
|
1323
|
-
|
|
1304
|
+
colSpan={4}
|
|
1305
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1324
1306
|
>
|
|
1325
|
-
{
|
|
1307
|
+
{totalWithGst.toLocaleString('en-AU', {
|
|
1308
|
+
style: 'currency',
|
|
1309
|
+
currency: 'AUD',
|
|
1310
|
+
})}
|
|
1311
|
+
</td>
|
|
1312
|
+
<td></td>
|
|
1313
|
+
</tr>
|
|
1314
|
+
|
|
1315
|
+
{/* LKD Margin */}
|
|
1316
|
+
<tr style={{ backgroundColor: '#f8f9fa' }}>
|
|
1317
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1318
|
+
LKD Margin (in %)
|
|
1326
1319
|
</td>
|
|
1327
|
-
{/* Margin % input */}
|
|
1328
1320
|
<td
|
|
1329
|
-
style={{ textAlign: 'center', padding: '8px' }}
|
|
1330
1321
|
colSpan={2}
|
|
1322
|
+
style={{ padding: '8px', textAlign: 'center' }}
|
|
1331
1323
|
>
|
|
1332
1324
|
<input
|
|
1333
1325
|
type="number"
|
|
@@ -1337,25 +1329,72 @@ const GenericEditableTable = ({
|
|
|
1337
1329
|
parseFloat(e.target.value) || 0
|
|
1338
1330
|
)
|
|
1339
1331
|
}
|
|
1340
|
-
placeholder="
|
|
1332
|
+
placeholder="0"
|
|
1341
1333
|
style={{
|
|
1342
|
-
|
|
1343
|
-
padding: '6px',
|
|
1334
|
+
padding: '4px',
|
|
1344
1335
|
border: '1px solid #ccc',
|
|
1345
1336
|
borderRadius: '3px',
|
|
1337
|
+
textAlign: 'center',
|
|
1338
|
+
marginRight: '4px',
|
|
1346
1339
|
}}
|
|
1347
1340
|
/>
|
|
1348
1341
|
</td>
|
|
1349
|
-
{/* Total (subtotal of all categories + margin) */}
|
|
1350
1342
|
<td
|
|
1351
|
-
style={{
|
|
1352
|
-
textAlign: 'right',
|
|
1353
|
-
fontWeight: 'bold',
|
|
1354
|
-
padding: '8px',
|
|
1355
|
-
}}
|
|
1356
1343
|
colSpan={2}
|
|
1344
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1345
|
+
>
|
|
1346
|
+
{marginAmount.toLocaleString('en-AU', {
|
|
1347
|
+
style: 'currency',
|
|
1348
|
+
currency: 'AUD',
|
|
1349
|
+
})}
|
|
1350
|
+
</td>
|
|
1351
|
+
<td></td>
|
|
1352
|
+
</tr>
|
|
1353
|
+
|
|
1354
|
+
{/* Total LKD Project Cost */}
|
|
1355
|
+
<tr style={{ backgroundColor: '#e9ecef', fontWeight: 'bold' }}>
|
|
1356
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1357
|
+
Total LKD Project Cost
|
|
1358
|
+
</td>
|
|
1359
|
+
<td
|
|
1360
|
+
colSpan={4}
|
|
1361
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1362
|
+
>
|
|
1363
|
+
{totalLkdProjectCost.toLocaleString('en-AU', {
|
|
1364
|
+
style: 'currency',
|
|
1365
|
+
currency: 'AUD',
|
|
1366
|
+
})}
|
|
1367
|
+
</td>
|
|
1368
|
+
<td></td>
|
|
1369
|
+
</tr>
|
|
1370
|
+
|
|
1371
|
+
{/* Total GST */}
|
|
1372
|
+
<tr style={{ backgroundColor: '#e9ecef' }}>
|
|
1373
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1374
|
+
Total GST
|
|
1375
|
+
</td>
|
|
1376
|
+
<td
|
|
1377
|
+
colSpan={4}
|
|
1378
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1379
|
+
>
|
|
1380
|
+
{totalLkdGst.toLocaleString('en-AU', {
|
|
1381
|
+
style: 'currency',
|
|
1382
|
+
currency: 'AUD',
|
|
1383
|
+
})}
|
|
1384
|
+
</td>
|
|
1385
|
+
<td></td>
|
|
1386
|
+
</tr>
|
|
1387
|
+
|
|
1388
|
+
{/* Total LKD Project Cost (Including GST) */}
|
|
1389
|
+
<tr style={{ backgroundColor: '#e9ecef', fontWeight: 'bold' }}>
|
|
1390
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1391
|
+
Total LKD Project Cost (Including GST)
|
|
1392
|
+
</td>
|
|
1393
|
+
<td
|
|
1394
|
+
colSpan={4}
|
|
1395
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1357
1396
|
>
|
|
1358
|
-
{
|
|
1397
|
+
{totalLkdWithGst.toLocaleString('en-AU', {
|
|
1359
1398
|
style: 'currency',
|
|
1360
1399
|
currency: 'AUD',
|
|
1361
1400
|
})}
|
|
@@ -291,7 +291,6 @@
|
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
.signature-editor-preview-content {
|
|
294
|
-
border: 1px solid var(--paragraph-color, #e2e8f0);
|
|
295
294
|
border-radius: 6px;
|
|
296
295
|
overflow: hidden;
|
|
297
296
|
}
|
|
@@ -328,8 +327,12 @@
|
|
|
328
327
|
}
|
|
329
328
|
|
|
330
329
|
@keyframes spin {
|
|
331
|
-
0% {
|
|
332
|
-
|
|
330
|
+
0% {
|
|
331
|
+
transform: rotate(0deg);
|
|
332
|
+
}
|
|
333
|
+
100% {
|
|
334
|
+
transform: rotate(360deg);
|
|
335
|
+
}
|
|
333
336
|
}
|
|
334
337
|
|
|
335
338
|
/* Field Editor Modal */
|
|
@@ -448,7 +451,7 @@
|
|
|
448
451
|
cursor: pointer;
|
|
449
452
|
}
|
|
450
453
|
|
|
451
|
-
.signature-field-editor-checkbox input[type=
|
|
454
|
+
.signature-field-editor-checkbox input[type='checkbox'] {
|
|
452
455
|
accent-color: var(--primary-color, #3cbf7d);
|
|
453
456
|
}
|
|
454
457
|
|
|
@@ -465,34 +468,34 @@
|
|
|
465
468
|
.signature-editor-container {
|
|
466
469
|
padding: 1rem;
|
|
467
470
|
}
|
|
468
|
-
|
|
471
|
+
|
|
469
472
|
.signature-editor-fields-header {
|
|
470
473
|
flex-direction: column;
|
|
471
474
|
align-items: flex-start;
|
|
472
475
|
gap: 1rem;
|
|
473
476
|
}
|
|
474
|
-
|
|
477
|
+
|
|
475
478
|
.signature-field-header {
|
|
476
479
|
flex-direction: column;
|
|
477
480
|
align-items: flex-start;
|
|
478
481
|
gap: 0.5rem;
|
|
479
482
|
}
|
|
480
|
-
|
|
483
|
+
|
|
481
484
|
.signature-field-actions {
|
|
482
485
|
width: 100%;
|
|
483
486
|
justify-content: flex-end;
|
|
484
487
|
}
|
|
485
|
-
|
|
488
|
+
|
|
486
489
|
.signature-editor-actions {
|
|
487
490
|
flex-direction: column;
|
|
488
491
|
}
|
|
489
|
-
|
|
492
|
+
|
|
490
493
|
.signature-field-editor-grid {
|
|
491
494
|
grid-template-columns: 1fr;
|
|
492
495
|
}
|
|
493
|
-
|
|
496
|
+
|
|
494
497
|
.signature-editor-modal {
|
|
495
498
|
width: 95%;
|
|
496
499
|
margin: 1rem;
|
|
497
500
|
}
|
|
498
|
-
}
|
|
501
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { Plus, Trash2, Move, Type, Edit3, Save, X, Tag } from 'lucide-react';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
SortableContainer,
|
|
5
|
+
SortableElement,
|
|
6
|
+
SortableHandle,
|
|
7
|
+
} from 'react-sortable-hoc';
|
|
4
8
|
import { arrayMoveImmutable } from 'array-move';
|
|
5
9
|
import { toast } from 'react-toastify';
|
|
6
10
|
import { buildUrl, defaultProposalApiEndpoints } from '../../utils/urlBuilder';
|
|
@@ -66,7 +70,7 @@ const FieldEditor = ({ field, onSave, onCancel }) => {
|
|
|
66
70
|
type: field?.type || 'text',
|
|
67
71
|
placeholder: field?.placeholder || '',
|
|
68
72
|
required: field?.required || false,
|
|
69
|
-
width: field?.width || 'full'
|
|
73
|
+
width: field?.width || 'full',
|
|
70
74
|
});
|
|
71
75
|
|
|
72
76
|
const fieldTypes = [
|
|
@@ -74,13 +78,13 @@ const FieldEditor = ({ field, onSave, onCancel }) => {
|
|
|
74
78
|
{ value: 'signature', label: 'Signature Block' },
|
|
75
79
|
{ value: 'date', label: 'Date Field' },
|
|
76
80
|
{ value: 'select', label: 'Dropdown' },
|
|
77
|
-
{ value: 'textarea', label: 'Text Area' }
|
|
81
|
+
{ value: 'textarea', label: 'Text Area' },
|
|
78
82
|
];
|
|
79
83
|
|
|
80
84
|
const fieldWidths = [
|
|
81
85
|
{ value: 'full', label: 'Full Width' },
|
|
82
86
|
{ value: 'half', label: 'Half Width' },
|
|
83
|
-
{ value: 'third', label: 'Third Width' }
|
|
87
|
+
{ value: 'third', label: 'Third Width' },
|
|
84
88
|
];
|
|
85
89
|
|
|
86
90
|
const handleSubmit = (e) => {
|
|
@@ -96,55 +100,81 @@ const FieldEditor = ({ field, onSave, onCancel }) => {
|
|
|
96
100
|
<div className="signature-field-editor">
|
|
97
101
|
<div className="signature-field-editor-header">
|
|
98
102
|
<h4>{field ? 'Edit Field' : 'Add New Field'}</h4>
|
|
99
|
-
<button
|
|
103
|
+
<button
|
|
104
|
+
onClick={onCancel}
|
|
105
|
+
className="signature-field-editor-close"
|
|
106
|
+
>
|
|
100
107
|
<X size={16} />
|
|
101
108
|
</button>
|
|
102
109
|
</div>
|
|
103
|
-
|
|
104
|
-
<form
|
|
110
|
+
|
|
111
|
+
<form
|
|
112
|
+
onSubmit={handleSubmit}
|
|
113
|
+
className="signature-field-editor-form"
|
|
114
|
+
>
|
|
105
115
|
<div className="signature-field-editor-grid">
|
|
106
116
|
<div className="signature-field-editor-group">
|
|
107
117
|
<label>Field Label *</label>
|
|
108
118
|
<input
|
|
109
119
|
type="text"
|
|
110
120
|
value={formData.label}
|
|
111
|
-
onChange={(e) =>
|
|
121
|
+
onChange={(e) =>
|
|
122
|
+
setFormData({
|
|
123
|
+
...formData,
|
|
124
|
+
label: e.target.value,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
112
127
|
placeholder="e.g., Name, Company Name, ABN"
|
|
113
128
|
required
|
|
114
129
|
/>
|
|
115
130
|
</div>
|
|
116
|
-
|
|
131
|
+
|
|
117
132
|
<div className="signature-field-editor-group">
|
|
118
133
|
<label>Field Type</label>
|
|
119
134
|
<select
|
|
120
135
|
value={formData.type}
|
|
121
|
-
onChange={(e) =>
|
|
136
|
+
onChange={(e) =>
|
|
137
|
+
setFormData({
|
|
138
|
+
...formData,
|
|
139
|
+
type: e.target.value,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
122
142
|
>
|
|
123
|
-
{fieldTypes.map(type => (
|
|
143
|
+
{fieldTypes.map((type) => (
|
|
124
144
|
<option key={type.value} value={type.value}>
|
|
125
145
|
{type.label}
|
|
126
146
|
</option>
|
|
127
147
|
))}
|
|
128
148
|
</select>
|
|
129
149
|
</div>
|
|
130
|
-
|
|
150
|
+
|
|
131
151
|
<div className="signature-field-editor-group">
|
|
132
152
|
<label>Placeholder Text</label>
|
|
133
153
|
<input
|
|
134
154
|
type="text"
|
|
135
155
|
value={formData.placeholder}
|
|
136
|
-
onChange={(e) =>
|
|
156
|
+
onChange={(e) =>
|
|
157
|
+
setFormData({
|
|
158
|
+
...formData,
|
|
159
|
+
placeholder: e.target.value,
|
|
160
|
+
})
|
|
161
|
+
}
|
|
137
162
|
placeholder="Optional placeholder text"
|
|
138
163
|
/>
|
|
139
164
|
</div>
|
|
140
|
-
|
|
165
|
+
|
|
141
166
|
<div className="signature-field-editor-group">
|
|
142
167
|
<label>Field Width</label>
|
|
143
168
|
<select
|
|
144
169
|
value={formData.width}
|
|
145
|
-
onChange={(e) =>
|
|
170
|
+
onChange={(e) =>
|
|
171
|
+
setFormData({
|
|
172
|
+
...formData,
|
|
173
|
+
width: e.target.value,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
146
176
|
>
|
|
147
|
-
{fieldWidths.map(width => (
|
|
177
|
+
{fieldWidths.map((width) => (
|
|
148
178
|
<option key={width.value} value={width.value}>
|
|
149
179
|
{width.label}
|
|
150
180
|
</option>
|
|
@@ -152,20 +182,29 @@ const FieldEditor = ({ field, onSave, onCancel }) => {
|
|
|
152
182
|
</select>
|
|
153
183
|
</div>
|
|
154
184
|
</div>
|
|
155
|
-
|
|
185
|
+
|
|
156
186
|
<div className="signature-field-editor-checkbox">
|
|
157
187
|
<label>
|
|
158
188
|
<input
|
|
159
189
|
type="checkbox"
|
|
160
190
|
checked={formData.required}
|
|
161
|
-
onChange={(e) =>
|
|
191
|
+
onChange={(e) =>
|
|
192
|
+
setFormData({
|
|
193
|
+
...formData,
|
|
194
|
+
required: e.target.checked,
|
|
195
|
+
})
|
|
196
|
+
}
|
|
162
197
|
/>
|
|
163
198
|
Required field
|
|
164
199
|
</label>
|
|
165
200
|
</div>
|
|
166
|
-
|
|
201
|
+
|
|
167
202
|
<div className="signature-field-editor-actions">
|
|
168
|
-
<button
|
|
203
|
+
<button
|
|
204
|
+
type="button"
|
|
205
|
+
onClick={onCancel}
|
|
206
|
+
className="signature-btn secondary"
|
|
207
|
+
>
|
|
169
208
|
Cancel
|
|
170
209
|
</button>
|
|
171
210
|
<button type="submit" className="signature-btn primary">
|
|
@@ -178,21 +217,25 @@ const FieldEditor = ({ field, onSave, onCancel }) => {
|
|
|
178
217
|
);
|
|
179
218
|
};
|
|
180
219
|
|
|
181
|
-
const AgreementSignatureEditor = ({
|
|
182
|
-
section,
|
|
220
|
+
const AgreementSignatureEditor = ({
|
|
221
|
+
section,
|
|
183
222
|
templateId,
|
|
184
|
-
onSave,
|
|
223
|
+
onSave,
|
|
185
224
|
onCancel,
|
|
186
225
|
apiEndpoints = defaultProposalApiEndpoints,
|
|
187
226
|
hideFooter = false,
|
|
188
|
-
onTemplateChange = null
|
|
227
|
+
onTemplateChange = null,
|
|
189
228
|
}) => {
|
|
190
229
|
const [template, setTemplate] = useState({
|
|
191
|
-
headerText: section?.template
|
|
192
|
-
|
|
193
|
-
|
|
230
|
+
headerText: section?.template
|
|
231
|
+
? section.template.headerText || ''
|
|
232
|
+
: 'APPROVAL TO PROCEED',
|
|
233
|
+
bodyText:
|
|
234
|
+
section?.template?.bodyText ||
|
|
235
|
+
'By signing the Approval to Proceed, you agree to engage {{company_name}} to complete the Scope of Works outlined within this Fee Proposal in accordance with our Terms of Engagement.',
|
|
236
|
+
fields: section?.template?.fields || [],
|
|
194
237
|
});
|
|
195
|
-
|
|
238
|
+
|
|
196
239
|
const [editingField, setEditingField] = useState(null);
|
|
197
240
|
const [showFieldEditor, setShowFieldEditor] = useState(false);
|
|
198
241
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -208,28 +251,72 @@ const AgreementSignatureEditor = ({
|
|
|
208
251
|
// Parse HTML content to extract template data
|
|
209
252
|
const parser = new DOMParser();
|
|
210
253
|
const doc = parser.parseFromString(content, 'text/html');
|
|
211
|
-
|
|
254
|
+
|
|
212
255
|
// Extract header text from h3 tag
|
|
213
256
|
const headerElement = doc.querySelector('h3');
|
|
214
|
-
const headerText = headerElement
|
|
215
|
-
|
|
257
|
+
const headerText = headerElement
|
|
258
|
+
? headerElement.textContent.trim()
|
|
259
|
+
: '';
|
|
260
|
+
|
|
216
261
|
// Extract body text from p tag
|
|
217
262
|
const bodyElement = doc.querySelector('p');
|
|
218
|
-
const bodyText = bodyElement
|
|
219
|
-
|
|
263
|
+
const bodyText = bodyElement
|
|
264
|
+
? bodyElement.textContent.trim()
|
|
265
|
+
: 'By signing the Approval to Proceed, you agree to engage {{company_name}} to complete the Scope of Works outlined within this Fee Proposal in accordance with our Terms of Engagement.';
|
|
266
|
+
|
|
220
267
|
// Extract fields from the content (basic parsing)
|
|
221
268
|
const defaultFields = [
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
269
|
+
{
|
|
270
|
+
id: 1,
|
|
271
|
+
label: 'Name',
|
|
272
|
+
type: 'text',
|
|
273
|
+
placeholder: '',
|
|
274
|
+
required: true,
|
|
275
|
+
width: 'full',
|
|
276
|
+
order: 1,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: 2,
|
|
280
|
+
label: 'Company Name',
|
|
281
|
+
type: 'text',
|
|
282
|
+
placeholder: '',
|
|
283
|
+
required: true,
|
|
284
|
+
width: 'full',
|
|
285
|
+
order: 2,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: 3,
|
|
289
|
+
label: 'ABN',
|
|
290
|
+
type: 'text',
|
|
291
|
+
placeholder: '',
|
|
292
|
+
required: false,
|
|
293
|
+
width: 'full',
|
|
294
|
+
order: 3,
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: 4,
|
|
298
|
+
label: 'Signature',
|
|
299
|
+
type: 'signature',
|
|
300
|
+
placeholder: '',
|
|
301
|
+
required: true,
|
|
302
|
+
width: 'full',
|
|
303
|
+
order: 4,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: 5,
|
|
307
|
+
label: 'Date',
|
|
308
|
+
type: 'date',
|
|
309
|
+
placeholder: '',
|
|
310
|
+
required: true,
|
|
311
|
+
width: 'full',
|
|
312
|
+
order: 5,
|
|
313
|
+
},
|
|
227
314
|
];
|
|
228
|
-
|
|
315
|
+
|
|
229
316
|
return {
|
|
230
317
|
headerText: headerText,
|
|
231
318
|
bodyText: bodyText,
|
|
232
|
-
fields: defaultFields
|
|
319
|
+
fields: defaultFields,
|
|
233
320
|
};
|
|
234
321
|
};
|
|
235
322
|
|
|
@@ -251,7 +338,7 @@ const AgreementSignatureEditor = ({
|
|
|
251
338
|
const updatedSection = {
|
|
252
339
|
...section,
|
|
253
340
|
template: template,
|
|
254
|
-
content: generatePreviewContent(template)
|
|
341
|
+
content: generatePreviewContent(template),
|
|
255
342
|
};
|
|
256
343
|
onTemplateChange(updatedSection);
|
|
257
344
|
}
|
|
@@ -262,76 +349,250 @@ const AgreementSignatureEditor = ({
|
|
|
262
349
|
// Load default template when no templateId is provided
|
|
263
350
|
setTemplate({
|
|
264
351
|
headerText: 'APPROVAL TO PROCEED',
|
|
265
|
-
bodyText:
|
|
352
|
+
bodyText:
|
|
353
|
+
'By signing the Approval to Proceed, you agree to engage {{company_name}} to complete the Scope of Works outlined within this Fee Proposal in accordance with our Terms of Engagement.',
|
|
266
354
|
fields: [
|
|
267
|
-
{
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
355
|
+
{
|
|
356
|
+
id: 1,
|
|
357
|
+
label: 'Name',
|
|
358
|
+
type: 'text',
|
|
359
|
+
placeholder: '',
|
|
360
|
+
required: true,
|
|
361
|
+
width: 'full',
|
|
362
|
+
order: 1,
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: 2,
|
|
366
|
+
label: 'Company Name',
|
|
367
|
+
type: 'text',
|
|
368
|
+
placeholder: '',
|
|
369
|
+
required: true,
|
|
370
|
+
width: 'full',
|
|
371
|
+
order: 2,
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: 3,
|
|
375
|
+
label: 'ABN',
|
|
376
|
+
type: 'text',
|
|
377
|
+
placeholder: '',
|
|
378
|
+
required: false,
|
|
379
|
+
width: 'full',
|
|
380
|
+
order: 3,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: 4,
|
|
384
|
+
label: 'Signature',
|
|
385
|
+
type: 'signature',
|
|
386
|
+
placeholder: '',
|
|
387
|
+
required: true,
|
|
388
|
+
width: 'full',
|
|
389
|
+
order: 4,
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
id: 5,
|
|
393
|
+
label: 'Date',
|
|
394
|
+
type: 'date',
|
|
395
|
+
placeholder: '',
|
|
396
|
+
required: true,
|
|
397
|
+
width: 'full',
|
|
398
|
+
order: 5,
|
|
399
|
+
},
|
|
400
|
+
],
|
|
273
401
|
});
|
|
274
402
|
return;
|
|
275
403
|
}
|
|
276
|
-
|
|
404
|
+
|
|
277
405
|
try {
|
|
278
406
|
setIsLoading(true);
|
|
279
|
-
const response = await fetch(
|
|
280
|
-
|
|
407
|
+
const response = await fetch(
|
|
408
|
+
buildUrlWithTemplate(
|
|
409
|
+
apiEndpoints.agreementSignatureTemplate ||
|
|
410
|
+
'/ajax/agreement-signature-template/{templateId}'
|
|
411
|
+
)
|
|
412
|
+
);
|
|
413
|
+
|
|
281
414
|
// Check if the response is ok and contains JSON
|
|
282
415
|
if (!response.ok) {
|
|
283
416
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
284
417
|
}
|
|
285
|
-
|
|
418
|
+
|
|
286
419
|
const contentType = response.headers.get('content-type');
|
|
287
420
|
if (!contentType || !contentType.includes('application/json')) {
|
|
288
421
|
// If the API doesn't exist or returns HTML, use default template
|
|
289
|
-
console.warn(
|
|
422
|
+
console.warn(
|
|
423
|
+
'Agreement signature template API not available, using default template'
|
|
424
|
+
);
|
|
290
425
|
setTemplate({
|
|
291
426
|
headerText: 'APPROVAL TO PROCEED',
|
|
292
|
-
bodyText:
|
|
427
|
+
bodyText:
|
|
428
|
+
'By signing the Approval to Proceed, you agree to engage {{company_name}} to complete the Scope of Works outlined within this Fee Proposal in accordance with our Terms of Engagement.',
|
|
293
429
|
fields: [
|
|
294
|
-
{
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
430
|
+
{
|
|
431
|
+
id: 1,
|
|
432
|
+
label: 'Name',
|
|
433
|
+
type: 'text',
|
|
434
|
+
placeholder: '',
|
|
435
|
+
required: true,
|
|
436
|
+
width: 'full',
|
|
437
|
+
order: 1,
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
id: 2,
|
|
441
|
+
label: 'Company Name',
|
|
442
|
+
type: 'text',
|
|
443
|
+
placeholder: '',
|
|
444
|
+
required: true,
|
|
445
|
+
width: 'full',
|
|
446
|
+
order: 2,
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
id: 3,
|
|
450
|
+
label: 'ABN',
|
|
451
|
+
type: 'text',
|
|
452
|
+
placeholder: '',
|
|
453
|
+
required: false,
|
|
454
|
+
width: 'full',
|
|
455
|
+
order: 3,
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
id: 4,
|
|
459
|
+
label: 'Signature',
|
|
460
|
+
type: 'signature',
|
|
461
|
+
placeholder: '',
|
|
462
|
+
required: true,
|
|
463
|
+
width: 'full',
|
|
464
|
+
order: 4,
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: 5,
|
|
468
|
+
label: 'Date',
|
|
469
|
+
type: 'date',
|
|
470
|
+
placeholder: '',
|
|
471
|
+
required: true,
|
|
472
|
+
width: 'full',
|
|
473
|
+
order: 5,
|
|
474
|
+
},
|
|
475
|
+
],
|
|
300
476
|
});
|
|
301
477
|
return;
|
|
302
478
|
}
|
|
303
|
-
|
|
479
|
+
|
|
304
480
|
const data = await response.json();
|
|
305
|
-
|
|
481
|
+
|
|
306
482
|
if (data.success) {
|
|
307
483
|
setTemplate(data.template);
|
|
308
484
|
} else {
|
|
309
485
|
// Use default template if none exists
|
|
310
486
|
setTemplate({
|
|
311
487
|
headerText: 'APPROVAL TO PROCEED',
|
|
312
|
-
bodyText:
|
|
488
|
+
bodyText:
|
|
489
|
+
'By signing the Approval to Proceed, you agree to engage {{company_name}} to complete the Scope of Works outlined within this Fee Proposal in accordance with our Terms of Engagement.',
|
|
313
490
|
fields: [
|
|
314
|
-
{
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
491
|
+
{
|
|
492
|
+
id: 1,
|
|
493
|
+
label: 'Name',
|
|
494
|
+
type: 'text',
|
|
495
|
+
placeholder: '',
|
|
496
|
+
required: true,
|
|
497
|
+
width: 'full',
|
|
498
|
+
order: 1,
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
id: 2,
|
|
502
|
+
label: 'Company Name',
|
|
503
|
+
type: 'text',
|
|
504
|
+
placeholder: '',
|
|
505
|
+
required: true,
|
|
506
|
+
width: 'full',
|
|
507
|
+
order: 2,
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: 3,
|
|
511
|
+
label: 'ABN',
|
|
512
|
+
type: 'text',
|
|
513
|
+
placeholder: '',
|
|
514
|
+
required: false,
|
|
515
|
+
width: 'full',
|
|
516
|
+
order: 3,
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
id: 4,
|
|
520
|
+
label: 'Signature',
|
|
521
|
+
type: 'signature',
|
|
522
|
+
placeholder: '',
|
|
523
|
+
required: true,
|
|
524
|
+
width: 'full',
|
|
525
|
+
order: 4,
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
id: 5,
|
|
529
|
+
label: 'Date',
|
|
530
|
+
type: 'date',
|
|
531
|
+
placeholder: '',
|
|
532
|
+
required: true,
|
|
533
|
+
width: 'full',
|
|
534
|
+
order: 5,
|
|
535
|
+
},
|
|
536
|
+
],
|
|
320
537
|
});
|
|
321
538
|
}
|
|
322
539
|
} catch (error) {
|
|
323
|
-
console.warn(
|
|
540
|
+
console.warn(
|
|
541
|
+
'Error loading agreement signature template, using default:',
|
|
542
|
+
error.message
|
|
543
|
+
);
|
|
324
544
|
// Use default template instead of showing error
|
|
325
545
|
setTemplate({
|
|
326
546
|
headerText: 'APPROVAL TO PROCEED',
|
|
327
|
-
bodyText:
|
|
547
|
+
bodyText:
|
|
548
|
+
'By signing the Approval to Proceed, you agree to engage {{company_name}} to complete the Scope of Works outlined within this Fee Proposal in accordance with our Terms of Engagement.',
|
|
328
549
|
fields: [
|
|
329
|
-
{
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
550
|
+
{
|
|
551
|
+
id: 1,
|
|
552
|
+
label: 'Name',
|
|
553
|
+
type: 'text',
|
|
554
|
+
placeholder: '',
|
|
555
|
+
required: true,
|
|
556
|
+
width: 'full',
|
|
557
|
+
order: 1,
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
id: 2,
|
|
561
|
+
label: 'Company Name',
|
|
562
|
+
type: 'text',
|
|
563
|
+
placeholder: '',
|
|
564
|
+
required: true,
|
|
565
|
+
width: 'full',
|
|
566
|
+
order: 2,
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
id: 3,
|
|
570
|
+
label: 'ABN',
|
|
571
|
+
type: 'text',
|
|
572
|
+
placeholder: '',
|
|
573
|
+
required: false,
|
|
574
|
+
width: 'full',
|
|
575
|
+
order: 3,
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: 4,
|
|
579
|
+
label: 'Signature',
|
|
580
|
+
type: 'signature',
|
|
581
|
+
placeholder: '',
|
|
582
|
+
required: true,
|
|
583
|
+
width: 'full',
|
|
584
|
+
order: 4,
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
id: 5,
|
|
588
|
+
label: 'Date',
|
|
589
|
+
type: 'date',
|
|
590
|
+
placeholder: '',
|
|
591
|
+
required: true,
|
|
592
|
+
width: 'full',
|
|
593
|
+
order: 5,
|
|
594
|
+
},
|
|
595
|
+
],
|
|
335
596
|
});
|
|
336
597
|
} finally {
|
|
337
598
|
setIsLoading(false);
|
|
@@ -341,15 +602,19 @@ const AgreementSignatureEditor = ({
|
|
|
341
602
|
const handleFieldsReorder = ({ oldIndex, newIndex }) => {
|
|
342
603
|
if (oldIndex === newIndex) return;
|
|
343
604
|
|
|
344
|
-
const newFields = arrayMoveImmutable(
|
|
605
|
+
const newFields = arrayMoveImmutable(
|
|
606
|
+
template.fields,
|
|
607
|
+
oldIndex,
|
|
608
|
+
newIndex
|
|
609
|
+
);
|
|
345
610
|
const reorderedFields = newFields.map((field, index) => ({
|
|
346
611
|
...field,
|
|
347
|
-
order: index + 1
|
|
612
|
+
order: index + 1,
|
|
348
613
|
}));
|
|
349
614
|
|
|
350
615
|
setTemplate({
|
|
351
616
|
...template,
|
|
352
|
-
fields: reorderedFields
|
|
617
|
+
fields: reorderedFields,
|
|
353
618
|
});
|
|
354
619
|
};
|
|
355
620
|
|
|
@@ -366,23 +631,25 @@ const AgreementSignatureEditor = ({
|
|
|
366
631
|
const handleSaveField = (fieldData) => {
|
|
367
632
|
if (editingField) {
|
|
368
633
|
// Update existing field
|
|
369
|
-
const updatedFields = template.fields.map(field =>
|
|
370
|
-
field.id === editingField.id
|
|
634
|
+
const updatedFields = template.fields.map((field) =>
|
|
635
|
+
field.id === editingField.id
|
|
636
|
+
? { ...fieldData, id: editingField.id }
|
|
637
|
+
: field
|
|
371
638
|
);
|
|
372
639
|
setTemplate({
|
|
373
640
|
...template,
|
|
374
|
-
fields: updatedFields
|
|
641
|
+
fields: updatedFields,
|
|
375
642
|
});
|
|
376
643
|
} else {
|
|
377
644
|
// Add new field
|
|
378
645
|
const newField = {
|
|
379
646
|
...fieldData,
|
|
380
647
|
id: Date.now(), // Temporary ID - backend will assign real ID
|
|
381
|
-
order: template.fields.length + 1
|
|
648
|
+
order: template.fields.length + 1,
|
|
382
649
|
};
|
|
383
650
|
setTemplate({
|
|
384
651
|
...template,
|
|
385
|
-
fields: [...template.fields, newField]
|
|
652
|
+
fields: [...template.fields, newField],
|
|
386
653
|
});
|
|
387
654
|
}
|
|
388
655
|
setShowFieldEditor(false);
|
|
@@ -391,14 +658,14 @@ const AgreementSignatureEditor = ({
|
|
|
391
658
|
|
|
392
659
|
const handleDeleteField = (fieldId) => {
|
|
393
660
|
if (!confirm('Are you sure you want to delete this field?')) return;
|
|
394
|
-
|
|
661
|
+
|
|
395
662
|
const updatedFields = template.fields
|
|
396
|
-
.filter(field => field.id !== fieldId)
|
|
663
|
+
.filter((field) => field.id !== fieldId)
|
|
397
664
|
.map((field, index) => ({ ...field, order: index + 1 }));
|
|
398
|
-
|
|
665
|
+
|
|
399
666
|
setTemplate({
|
|
400
667
|
...template,
|
|
401
|
-
fields: updatedFields
|
|
668
|
+
fields: updatedFields,
|
|
402
669
|
});
|
|
403
670
|
};
|
|
404
671
|
|
|
@@ -412,68 +679,81 @@ const AgreementSignatureEditor = ({
|
|
|
412
679
|
const before = text.substring(0, start);
|
|
413
680
|
const after = text.substring(end, text.length);
|
|
414
681
|
const variableText = `{{${variableName}}}`;
|
|
415
|
-
|
|
682
|
+
|
|
416
683
|
const newText = before + variableText + after;
|
|
417
|
-
|
|
684
|
+
|
|
418
685
|
setTemplate({
|
|
419
686
|
...template,
|
|
420
|
-
bodyText: newText
|
|
687
|
+
bodyText: newText,
|
|
421
688
|
});
|
|
422
689
|
|
|
423
690
|
// Reset cursor position after the inserted variable
|
|
424
691
|
setTimeout(() => {
|
|
425
692
|
textarea.focus();
|
|
426
|
-
textarea.setSelectionRange(
|
|
693
|
+
textarea.setSelectionRange(
|
|
694
|
+
start + variableText.length,
|
|
695
|
+
start + variableText.length
|
|
696
|
+
);
|
|
427
697
|
}, 0);
|
|
428
|
-
|
|
698
|
+
|
|
429
699
|
setShowVariableInserter(false);
|
|
430
700
|
};
|
|
431
701
|
|
|
432
702
|
const handleSaveTemplate = async () => {
|
|
433
703
|
try {
|
|
434
704
|
setIsLoading(true);
|
|
435
|
-
|
|
705
|
+
|
|
436
706
|
// For now, if no templateId or API is not available, just save locally
|
|
437
707
|
if (!templateId) {
|
|
438
708
|
// Update section with template reference
|
|
439
709
|
const updatedSection = {
|
|
440
710
|
...section,
|
|
441
711
|
template: template,
|
|
442
|
-
content: generatePreviewContent(template)
|
|
712
|
+
content: generatePreviewContent(template),
|
|
443
713
|
};
|
|
444
|
-
|
|
714
|
+
|
|
445
715
|
onSave(updatedSection);
|
|
446
716
|
toast.success('Agreement signature template saved locally');
|
|
447
717
|
return;
|
|
448
718
|
}
|
|
449
|
-
|
|
719
|
+
|
|
450
720
|
// Save template to backend
|
|
451
|
-
const response = await fetch(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
721
|
+
const response = await fetch(
|
|
722
|
+
buildUrlWithTemplate(
|
|
723
|
+
apiEndpoints.agreementSignatureTemplateSave ||
|
|
724
|
+
'/ajax/agreement-signature-template/{templateId}'
|
|
725
|
+
),
|
|
726
|
+
{
|
|
727
|
+
method: 'POST',
|
|
728
|
+
headers: {
|
|
729
|
+
'Content-Type': 'application/json',
|
|
730
|
+
'X-CSRF-TOKEN': document
|
|
731
|
+
.querySelector('meta[name="csrf-token"]')
|
|
732
|
+
?.getAttribute('content'),
|
|
733
|
+
},
|
|
734
|
+
body: JSON.stringify({
|
|
735
|
+
template: template,
|
|
736
|
+
}),
|
|
737
|
+
}
|
|
738
|
+
);
|
|
461
739
|
|
|
462
740
|
// Check if the response is ok and contains JSON
|
|
463
741
|
if (!response.ok) {
|
|
464
742
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
465
743
|
}
|
|
466
|
-
|
|
744
|
+
|
|
467
745
|
const contentType = response.headers.get('content-type');
|
|
468
746
|
if (!contentType || !contentType.includes('application/json')) {
|
|
469
747
|
// If the API doesn't exist, save locally instead
|
|
470
|
-
console.warn(
|
|
748
|
+
console.warn(
|
|
749
|
+
'Agreement signature template save API not available, saving locally'
|
|
750
|
+
);
|
|
471
751
|
const updatedSection = {
|
|
472
752
|
...section,
|
|
473
753
|
template: template,
|
|
474
|
-
content: generatePreviewContent(template)
|
|
754
|
+
content: generatePreviewContent(template),
|
|
475
755
|
};
|
|
476
|
-
|
|
756
|
+
|
|
477
757
|
onSave(updatedSection);
|
|
478
758
|
toast.success('Agreement signature template saved locally');
|
|
479
759
|
return;
|
|
@@ -485,24 +765,27 @@ const AgreementSignatureEditor = ({
|
|
|
485
765
|
const updatedSection = {
|
|
486
766
|
...section,
|
|
487
767
|
template: data.template,
|
|
488
|
-
content: generatePreviewContent(data.template)
|
|
768
|
+
content: generatePreviewContent(data.template),
|
|
489
769
|
};
|
|
490
|
-
|
|
770
|
+
|
|
491
771
|
onSave(updatedSection);
|
|
492
772
|
toast.success('Agreement signature template saved');
|
|
493
773
|
} else {
|
|
494
774
|
throw new Error(data.message || 'Failed to save template');
|
|
495
775
|
}
|
|
496
776
|
} catch (error) {
|
|
497
|
-
console.warn(
|
|
498
|
-
|
|
777
|
+
console.warn(
|
|
778
|
+
'Error saving template to backend, saving locally:',
|
|
779
|
+
error.message
|
|
780
|
+
);
|
|
781
|
+
|
|
499
782
|
// Fallback to local save
|
|
500
783
|
const updatedSection = {
|
|
501
784
|
...section,
|
|
502
785
|
template: template,
|
|
503
|
-
content: generatePreviewContent(template)
|
|
786
|
+
content: generatePreviewContent(template),
|
|
504
787
|
};
|
|
505
|
-
|
|
788
|
+
|
|
506
789
|
onSave(updatedSection);
|
|
507
790
|
toast.success('Agreement signature template saved locally');
|
|
508
791
|
} finally {
|
|
@@ -513,10 +796,15 @@ const AgreementSignatureEditor = ({
|
|
|
513
796
|
const generatePreviewContent = (templateData) => {
|
|
514
797
|
const fieldsHtml = templateData.fields
|
|
515
798
|
.sort((a, b) => a.order - b.order)
|
|
516
|
-
.map(field => {
|
|
799
|
+
.map((field) => {
|
|
517
800
|
const required = field.required ? ' *' : '';
|
|
518
|
-
const width =
|
|
519
|
-
|
|
801
|
+
const width =
|
|
802
|
+
field.width === 'full'
|
|
803
|
+
? '100%'
|
|
804
|
+
: field.width === 'half'
|
|
805
|
+
? '48%'
|
|
806
|
+
: '30%';
|
|
807
|
+
|
|
520
808
|
switch (field.type) {
|
|
521
809
|
case 'signature':
|
|
522
810
|
return `<div style="margin-bottom: 2rem; width: ${width}; display: inline-block; margin-right: 2%;">
|
|
@@ -538,11 +826,16 @@ const AgreementSignatureEditor = ({
|
|
|
538
826
|
.join('');
|
|
539
827
|
|
|
540
828
|
return `
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
829
|
+
${
|
|
830
|
+
templateData.headerText
|
|
831
|
+
? `<h3 style="margin-bottom: 1rem; color: #333;">${templateData.headerText}</h3>`
|
|
832
|
+
: ''
|
|
833
|
+
}
|
|
834
|
+
<p style="margin-bottom: 2rem; line-height: 1.6;">${
|
|
835
|
+
templateData.bodyText
|
|
836
|
+
}</p>
|
|
544
837
|
<div style="clear: both;">${fieldsHtml}</div>
|
|
545
|
-
|
|
838
|
+
|
|
546
839
|
`;
|
|
547
840
|
};
|
|
548
841
|
|
|
@@ -559,16 +852,26 @@ const AgreementSignatureEditor = ({
|
|
|
559
852
|
<div className="signature-editor-container">
|
|
560
853
|
<div className="signature-editor-header">
|
|
561
854
|
<h3>Agreement Signature Template</h3>
|
|
562
|
-
<p>
|
|
855
|
+
<p>
|
|
856
|
+
Customize the text and fields for your agreement signature
|
|
857
|
+
section
|
|
858
|
+
</p>
|
|
563
859
|
</div>
|
|
564
860
|
|
|
565
861
|
<div className="signature-editor-content">
|
|
566
862
|
<div className="signature-editor-section">
|
|
567
|
-
<label className="signature-editor-label">
|
|
863
|
+
<label className="signature-editor-label">
|
|
864
|
+
Header Text (Optional)
|
|
865
|
+
</label>
|
|
568
866
|
<input
|
|
569
867
|
type="text"
|
|
570
868
|
value={template.headerText}
|
|
571
|
-
onChange={(e) =>
|
|
869
|
+
onChange={(e) =>
|
|
870
|
+
setTemplate({
|
|
871
|
+
...template,
|
|
872
|
+
headerText: e.target.value,
|
|
873
|
+
})
|
|
874
|
+
}
|
|
572
875
|
placeholder="e.g., APPROVAL TO PROCEED (leave empty for no header)"
|
|
573
876
|
className="signature-editor-input"
|
|
574
877
|
/>
|
|
@@ -579,7 +882,9 @@ const AgreementSignatureEditor = ({
|
|
|
579
882
|
|
|
580
883
|
<div className="signature-editor-section">
|
|
581
884
|
<div className="signature-editor-label-with-button">
|
|
582
|
-
<label className="signature-editor-label">
|
|
885
|
+
<label className="signature-editor-label">
|
|
886
|
+
Body Text
|
|
887
|
+
</label>
|
|
583
888
|
<button
|
|
584
889
|
type="button"
|
|
585
890
|
onClick={() => setShowVariableInserter(true)}
|
|
@@ -593,19 +898,28 @@ const AgreementSignatureEditor = ({
|
|
|
593
898
|
<textarea
|
|
594
899
|
ref={bodyTextRef}
|
|
595
900
|
value={template.bodyText}
|
|
596
|
-
onChange={(e) =>
|
|
901
|
+
onChange={(e) =>
|
|
902
|
+
setTemplate({
|
|
903
|
+
...template,
|
|
904
|
+
bodyText: e.target.value,
|
|
905
|
+
})
|
|
906
|
+
}
|
|
597
907
|
placeholder="Enter the agreement text. Use {{variable_name}} for dynamic content."
|
|
598
908
|
className="signature-editor-textarea"
|
|
599
909
|
rows="3"
|
|
600
910
|
/>
|
|
601
911
|
<small className="signature-editor-help">
|
|
602
|
-
Use variables like {'{{company_name}}'} for dynamic
|
|
912
|
+
Use variables like {'{{company_name}}'} for dynamic
|
|
913
|
+
content. Click "Insert Variable" to browse available
|
|
914
|
+
variables.
|
|
603
915
|
</small>
|
|
604
916
|
</div>
|
|
605
917
|
|
|
606
918
|
<div className="signature-editor-section">
|
|
607
919
|
<div className="signature-editor-fields-header">
|
|
608
|
-
<label className="signature-editor-label">
|
|
920
|
+
<label className="signature-editor-label">
|
|
921
|
+
Signature Fields
|
|
922
|
+
</label>
|
|
609
923
|
<button
|
|
610
924
|
onClick={handleAddField}
|
|
611
925
|
className="signature-btn primary small"
|
|
@@ -619,7 +933,10 @@ const AgreementSignatureEditor = ({
|
|
|
619
933
|
<div className="signature-editor-empty">
|
|
620
934
|
<Type size={48} />
|
|
621
935
|
<p>No fields added yet</p>
|
|
622
|
-
<button
|
|
936
|
+
<button
|
|
937
|
+
onClick={handleAddField}
|
|
938
|
+
className="signature-btn primary"
|
|
939
|
+
>
|
|
623
940
|
Add Your First Field
|
|
624
941
|
</button>
|
|
625
942
|
</div>
|
|
@@ -638,9 +955,11 @@ const AgreementSignatureEditor = ({
|
|
|
638
955
|
|
|
639
956
|
<div className="signature-editor-preview">
|
|
640
957
|
<h4>Preview</h4>
|
|
641
|
-
<div
|
|
958
|
+
<div
|
|
642
959
|
className="signature-editor-preview-content"
|
|
643
|
-
dangerouslySetInnerHTML={{
|
|
960
|
+
dangerouslySetInnerHTML={{
|
|
961
|
+
__html: generatePreviewContent(template),
|
|
962
|
+
}}
|
|
644
963
|
/>
|
|
645
964
|
</div>
|
|
646
965
|
</div>
|
|
@@ -691,4 +1010,4 @@ const AgreementSignatureEditor = ({
|
|
|
691
1010
|
);
|
|
692
1011
|
};
|
|
693
1012
|
|
|
694
|
-
export default AgreementSignatureEditor;
|
|
1013
|
+
export default AgreementSignatureEditor;
|