@visns-studio/visns-components 5.7.7 → 5.7.9
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 +2 -2
- package/src/components/crm/generic/ActionButtons.jsx +22 -0
- package/src/components/crm/generic/GenericQuote.jsx +403 -151
- package/src/components/crm/generic/styles/ActionButtons.module.scss +65 -0
- package/src/components/crm/generic/styles/GenericQuote.module.scss +464 -130
- package/src/components/crm/generic/utils/formatters.js +43 -0
package/package.json
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"reactjs-popup": "^2.0.6",
|
|
55
55
|
"style-loader": "^4.0.0",
|
|
56
56
|
"swapy": "^1.0.5",
|
|
57
|
-
"sweetalert2": "^11.
|
|
57
|
+
"sweetalert2": "^11.20.0",
|
|
58
58
|
"truncate": "^3.0.0",
|
|
59
59
|
"uuid": "^11.1.0",
|
|
60
60
|
"validator": "^13.15.0",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
83
83
|
},
|
|
84
84
|
"name": "@visns-studio/visns-components",
|
|
85
|
-
"version": "5.7.
|
|
85
|
+
"version": "5.7.9",
|
|
86
86
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
87
87
|
"main": "src/index.js",
|
|
88
88
|
"files": [
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './styles/ActionButtons.module.scss';
|
|
3
|
+
|
|
4
|
+
function ActionButtons({ actions, onActionClick }) {
|
|
5
|
+
return (
|
|
6
|
+
<div className={styles.actionButtons}>
|
|
7
|
+
{actions &&
|
|
8
|
+
actions.map((action) => (
|
|
9
|
+
<button
|
|
10
|
+
key={`action-${action.id}`}
|
|
11
|
+
className={styles.actionButton}
|
|
12
|
+
onClick={() => onActionClick(action)}
|
|
13
|
+
title={action.label}
|
|
14
|
+
>
|
|
15
|
+
{action.label}
|
|
16
|
+
</button>
|
|
17
|
+
))}
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default ActionButtons;
|
|
@@ -1,35 +1,50 @@
|
|
|
1
1
|
import '../../styles/global.css';
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { useParams, useNavigate } from 'react-router-dom';
|
|
5
|
+
import Popup from 'reactjs-popup';
|
|
5
6
|
import moment from 'moment';
|
|
6
7
|
import get from 'lodash/get';
|
|
7
8
|
|
|
8
|
-
import Breadcrumb from '../../crm/Breadcrumb';
|
|
9
9
|
import CustomFetch from '../../crm/Fetch';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import ActionButtons from './ActionButtons';
|
|
11
|
+
import Form from '../../crm/Form';
|
|
12
|
+
import { formatCurrency } from './utils/formatters';
|
|
12
13
|
|
|
13
14
|
import styles from './styles/GenericQuote.module.scss';
|
|
14
15
|
|
|
15
|
-
function GenericQuote({
|
|
16
|
-
extraUrlParam,
|
|
17
|
-
layout = 'full',
|
|
18
|
-
setting,
|
|
19
|
-
setActiveNav = null,
|
|
20
|
-
urlParam,
|
|
21
|
-
userProfile,
|
|
22
|
-
}) {
|
|
16
|
+
function GenericQuote({ logo, setting, urlParam }) {
|
|
23
17
|
const routeParams = useParams();
|
|
18
|
+
const navigate = useNavigate();
|
|
24
19
|
|
|
25
|
-
const {
|
|
20
|
+
const { actions: actionItems, page, tables, form } = setting;
|
|
26
21
|
|
|
27
22
|
const [data, setData] = useState({});
|
|
28
|
-
const [
|
|
23
|
+
const [actions, setActions] = useState(actionItems || []);
|
|
24
|
+
|
|
25
|
+
// Modal state variables
|
|
26
|
+
const [modalShow, setModalShow] = useState(false);
|
|
27
|
+
const [formType, setFormType] = useState('');
|
|
28
|
+
const [formId, setFormId] = useState(0);
|
|
29
|
+
const [formData, setFormData] = useState({});
|
|
30
|
+
|
|
31
|
+
// Modal functions
|
|
32
|
+
const modalOpen = (type, id) => {
|
|
33
|
+
setModalShow(true);
|
|
34
|
+
setFormType(type);
|
|
35
|
+
setFormId(id);
|
|
36
|
+
setFormData(form);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const modalClose = () => {
|
|
40
|
+
setModalShow(false);
|
|
41
|
+
};
|
|
29
42
|
|
|
30
43
|
useEffect(() => {
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
if (actionItems && actionItems.length > 0) {
|
|
45
|
+
setActions(actionItems);
|
|
46
|
+
}
|
|
47
|
+
}, [actionItems]);
|
|
33
48
|
|
|
34
49
|
useEffect(() => {
|
|
35
50
|
const fetchData = async () => {
|
|
@@ -50,177 +65,414 @@ function GenericQuote({
|
|
|
50
65
|
fetchData();
|
|
51
66
|
}, [routeParams['*']]);
|
|
52
67
|
|
|
68
|
+
const handleActionClick = (action) => {
|
|
69
|
+
// Handle different actions based on action properties
|
|
70
|
+
console.log(`Action ${action.id} clicked:`, action);
|
|
71
|
+
|
|
72
|
+
// Check if action has a type property
|
|
73
|
+
if (action.type === 'link' && action.url) {
|
|
74
|
+
// Handle link type actions
|
|
75
|
+
let url = action.url;
|
|
76
|
+
|
|
77
|
+
// If urlParam is specified, append the corresponding data value
|
|
78
|
+
if (action.urlParam && data[action.urlParam]) {
|
|
79
|
+
url = `${url}${data[action.urlParam]}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Navigate to the URL using React Router
|
|
83
|
+
navigate(url);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle editModal type
|
|
88
|
+
if (action.type === 'editModal') {
|
|
89
|
+
// Open the edit modal with the current quote data
|
|
90
|
+
modalOpen('update', data.id);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Handle windowOpen type - opens URL in a new browser window/tab
|
|
95
|
+
if (action.type === 'windowOpen' && action.url) {
|
|
96
|
+
// Handle window open actions
|
|
97
|
+
let url = action.url;
|
|
98
|
+
|
|
99
|
+
// If urlParam is specified, append the corresponding data value
|
|
100
|
+
if (action.urlParam && data[action.urlParam]) {
|
|
101
|
+
url = `${url}${data[action.urlParam]}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Open the URL in a new window/tab
|
|
105
|
+
window.open(url, '_blank');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Handle other actions based on their id if no specific type is defined
|
|
110
|
+
switch (action.id) {
|
|
111
|
+
case 'addItem':
|
|
112
|
+
// Handle add item action
|
|
113
|
+
console.log('Add item clicked');
|
|
114
|
+
break;
|
|
115
|
+
case 'editQuote':
|
|
116
|
+
// If no type is specified but id is editQuote, default to opening the edit modal
|
|
117
|
+
if (!action.type) {
|
|
118
|
+
modalOpen('update', data.id);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
case 'saveAsPdf':
|
|
122
|
+
// If no type is specified but id is saveAsPdf, default to opening PDF in new window
|
|
123
|
+
if (!action.type) {
|
|
124
|
+
console.log('Save as PDF clicked');
|
|
125
|
+
// You could implement a default PDF generation here if needed
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
case 'saveToTicket':
|
|
129
|
+
// Handle save to ticket action
|
|
130
|
+
console.log('Save to ticket clicked');
|
|
131
|
+
break;
|
|
132
|
+
default:
|
|
133
|
+
console.log(
|
|
134
|
+
`Action ${action.id} clicked with no specific handler`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
53
139
|
return (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
140
|
+
<div className={styles.quoteWrapper}>
|
|
141
|
+
{/* Top section with quote number and action buttons */}
|
|
142
|
+
<div className={styles.quoteTopSection}>
|
|
143
|
+
<div className={styles.quoteIdentifier}>
|
|
144
|
+
<h1 className={styles.quoteTitle}>
|
|
145
|
+
Quote
|
|
146
|
+
<span className={styles.quoteNumber}>
|
|
147
|
+
#{data.id || ''}
|
|
148
|
+
</span>
|
|
149
|
+
</h1>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{/* Action buttons */}
|
|
153
|
+
<div className={styles.actionButtonsContainer}>
|
|
154
|
+
<ActionButtons
|
|
155
|
+
actions={actions}
|
|
156
|
+
onActionClick={handleActionClick}
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
{/* Quote status banner */}
|
|
162
|
+
<div className={styles.quotestatus}>
|
|
163
|
+
<div className={styles.statusLeft}>
|
|
164
|
+
<span
|
|
165
|
+
className={
|
|
166
|
+
styles[
|
|
167
|
+
'q__' +
|
|
168
|
+
(data.status
|
|
169
|
+
? data.status.name.toLowerCase()
|
|
170
|
+
: '')
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
>
|
|
174
|
+
{data.status ? data.status.name : ''}
|
|
175
|
+
{data.task && data.task.ticket_id && (
|
|
176
|
+
<span className={styles.ticketId}>
|
|
177
|
+
- #{data.task.ticket_id}
|
|
178
|
+
</span>
|
|
179
|
+
)}
|
|
180
|
+
</span>
|
|
181
|
+
</div>
|
|
182
|
+
<div className={styles.statusRight}>
|
|
183
|
+
{data.label && (
|
|
184
|
+
<span className={styles.quoteReference}>
|
|
185
|
+
{data.label}
|
|
186
|
+
</span>
|
|
187
|
+
)}
|
|
60
188
|
</div>
|
|
61
189
|
</div>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
190
|
+
|
|
191
|
+
{/* Quote content */}
|
|
192
|
+
<div className={styles.quoteContent}>
|
|
193
|
+
{/* Quote header with logo and date */}
|
|
194
|
+
<div className={styles.quoteHeaderSection}>
|
|
195
|
+
<div className={styles.quoteLogoContainer}>
|
|
196
|
+
<img
|
|
197
|
+
src={logo}
|
|
198
|
+
alt="Company Logo"
|
|
199
|
+
className={styles.quoteLogo}
|
|
200
|
+
/>
|
|
66
201
|
</div>
|
|
67
|
-
<div className={styles.
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
202
|
+
<div className={styles.quoteTitleContainer}>
|
|
203
|
+
<h2 className={styles.quoteBigNumber}>
|
|
204
|
+
Quote #{data.id || ''}
|
|
205
|
+
</h2>
|
|
206
|
+
<div className={styles.quoteDate}>
|
|
207
|
+
<strong>Date:</strong>{' '}
|
|
208
|
+
{data.created_at
|
|
209
|
+
? moment(data.created_at).format('DD-MM-YYYY')
|
|
210
|
+
: ''}
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* Quote parties */}
|
|
216
|
+
<div className={styles.quoteParties}>
|
|
217
|
+
<div className={styles.quoteParty}>
|
|
218
|
+
<h3 className={styles.partyTitle}>Quote To:</h3>
|
|
219
|
+
<div className={styles.partyDetails}>
|
|
220
|
+
{data.customer ? data.customer.name : ''}
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
<div className={styles.quoteParty}>
|
|
224
|
+
<h3 className={styles.partyTitle}>Quote From:</h3>
|
|
225
|
+
<div className={styles.partyDetails}>
|
|
226
|
+
Visns Studio
|
|
227
|
+
<br />
|
|
228
|
+
660C Newcastle Street
|
|
229
|
+
<br />
|
|
230
|
+
Leederville, WA, 6007
|
|
231
|
+
<br />
|
|
232
|
+
08 6383 6600
|
|
233
|
+
<br />
|
|
234
|
+
team@visns.studio
|
|
235
|
+
<br />
|
|
236
|
+
ABN: 41 994 317 718
|
|
84
237
|
</div>
|
|
85
|
-
|
|
86
|
-
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* Quote items tables */}
|
|
242
|
+
<div className={styles.quoteItems}>
|
|
243
|
+
{tables.length > 0 &&
|
|
244
|
+
tables.map((table) => {
|
|
245
|
+
// Only show tables that have entries
|
|
246
|
+
if (
|
|
247
|
+
!data[table.id] ||
|
|
248
|
+
data[table.id].length === 0
|
|
249
|
+
) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Calculate subtotal for this table
|
|
254
|
+
const subtotal = table.total
|
|
255
|
+
? parseFloat(table.total)
|
|
256
|
+
: data[table.id].reduce((sum, row) => {
|
|
257
|
+
// Make sure we're parsing numbers correctly
|
|
258
|
+
let total;
|
|
259
|
+
if (typeof row.total === 'number') {
|
|
260
|
+
total = row.total;
|
|
261
|
+
} else if (row.total) {
|
|
262
|
+
total = parseFloat(
|
|
263
|
+
String(row.total).replace(
|
|
264
|
+
/[^0-9.-]+/g,
|
|
265
|
+
''
|
|
266
|
+
)
|
|
267
|
+
);
|
|
268
|
+
} else if (row.rate && row.qty) {
|
|
269
|
+
// If total is not available, calculate from rate and qty
|
|
270
|
+
const rate = parseFloat(
|
|
271
|
+
String(row.rate).replace(
|
|
272
|
+
/[^0-9.-]+/g,
|
|
273
|
+
''
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
const qty = parseFloat(
|
|
277
|
+
String(row.qty).replace(
|
|
278
|
+
/[^0-9.-]+/g,
|
|
279
|
+
''
|
|
280
|
+
)
|
|
281
|
+
);
|
|
282
|
+
total = rate * qty;
|
|
283
|
+
} else {
|
|
284
|
+
total = 0;
|
|
285
|
+
}
|
|
286
|
+
return sum + (isNaN(total) ? 0 : total);
|
|
287
|
+
}, 0);
|
|
288
|
+
|
|
289
|
+
// We're using the imported formatCurrency function
|
|
290
|
+
|
|
291
|
+
return (
|
|
87
292
|
<div
|
|
88
|
-
|
|
293
|
+
key={`table-section-${table.id}`}
|
|
294
|
+
className={styles.quoteTableSection}
|
|
89
295
|
>
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
alt="Visns Studio Quote"
|
|
94
|
-
/>
|
|
95
|
-
</span>
|
|
96
|
-
</div>
|
|
97
|
-
<div className={styles.qitem__half}>
|
|
98
|
-
<div className={styles.qtitle}>
|
|
99
|
-
Quote #{data.id || ''}
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
<div className={styles.qitem}>
|
|
104
|
-
<div className={styles.qitem__half}>
|
|
105
|
-
<div className={styles.qdate}>
|
|
106
|
-
<strong>Date:</strong>{' '}
|
|
107
|
-
{data.created_at
|
|
108
|
-
? moment(data.created_at).format(
|
|
109
|
-
'DD-MM-YYYY'
|
|
110
|
-
)
|
|
111
|
-
: ''}
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
<div className={styles.qitem__half}>
|
|
115
|
-
<div className={styles.qno}>
|
|
116
|
-
<strong>{data.label}</strong>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
<div className={styles.qitem}>
|
|
121
|
-
<div className={styles.qitem__half}>
|
|
122
|
-
<div className={styles.qdetails}>
|
|
123
|
-
<p style={{ margin: 0 }}>
|
|
124
|
-
<strong>Quote To:</strong>
|
|
125
|
-
<br />
|
|
126
|
-
{data.customer
|
|
127
|
-
? data.customer.name
|
|
128
|
-
: ''}
|
|
129
|
-
</p>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
<div className={styles.qitem__half}>
|
|
133
|
-
<div className={styles.qvisns}>
|
|
134
|
-
<p style={{ margin: 0 }}>
|
|
135
|
-
<strong>Quote From:</strong>
|
|
136
|
-
<br />
|
|
137
|
-
Visns Studio
|
|
138
|
-
<br />
|
|
139
|
-
660C Newcastle Street
|
|
140
|
-
<br />
|
|
141
|
-
Leederville, WA, 6007
|
|
142
|
-
<br />
|
|
143
|
-
08 6383 6600
|
|
144
|
-
<br />
|
|
145
|
-
team@visns.studio
|
|
146
|
-
<br />
|
|
147
|
-
ABN: 41 994 317 718
|
|
148
|
-
</p>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
<div className={styles.qitem}>
|
|
154
|
-
{tables.length > 0 &&
|
|
155
|
-
tables.map((table) => (
|
|
296
|
+
<h2 className={styles.tableTitle}>
|
|
297
|
+
{table.label}
|
|
298
|
+
</h2>
|
|
156
299
|
<table
|
|
157
|
-
key={`table-${table.id}`}
|
|
158
|
-
className={
|
|
300
|
+
key={`table-${table.id}`}
|
|
301
|
+
className={styles.quoteTable}
|
|
159
302
|
>
|
|
160
303
|
<thead>
|
|
161
|
-
<tr className={styles.splitheader}>
|
|
162
|
-
<th colSpan="8">
|
|
163
|
-
<h1
|
|
164
|
-
className={
|
|
165
|
-
styles.splittitle
|
|
166
|
-
}
|
|
167
|
-
>
|
|
168
|
-
{table.label}
|
|
169
|
-
</h1>
|
|
170
|
-
</th>
|
|
171
|
-
</tr>
|
|
172
304
|
<tr>
|
|
173
|
-
<th></th>
|
|
174
305
|
{table.columns.map((column) => (
|
|
175
306
|
<th
|
|
176
307
|
key={`${table.id}-column-${column.id}`}
|
|
177
308
|
style={
|
|
178
309
|
column.style || {}
|
|
179
310
|
}
|
|
311
|
+
className={
|
|
312
|
+
styles.tableHeader
|
|
313
|
+
}
|
|
180
314
|
>
|
|
181
|
-
{column.label
|
|
315
|
+
{column.label ===
|
|
316
|
+
'Total'
|
|
317
|
+
? 'Subtotal'
|
|
318
|
+
: column.label}
|
|
182
319
|
</th>
|
|
183
320
|
))}
|
|
184
321
|
</tr>
|
|
185
322
|
</thead>
|
|
186
323
|
<tbody>
|
|
187
|
-
{data[table.id]
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
324
|
+
{data[table.id].map(
|
|
325
|
+
(row, index) => (
|
|
326
|
+
<tr
|
|
327
|
+
key={`row-${index}`}
|
|
328
|
+
className={
|
|
329
|
+
styles.tableRow
|
|
330
|
+
}
|
|
331
|
+
>
|
|
332
|
+
{table.columns.map(
|
|
333
|
+
(column) => {
|
|
334
|
+
const cellValue =
|
|
335
|
+
get(
|
|
336
|
+
row,
|
|
337
|
+
column.id,
|
|
338
|
+
''
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
return (
|
|
196
342
|
<td
|
|
197
343
|
key={`row-${index}-column-${column.id}`}
|
|
198
344
|
style={
|
|
199
345
|
column.style ||
|
|
200
346
|
{}
|
|
201
347
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
get(
|
|
205
|
-
row,
|
|
206
|
-
column.id,
|
|
207
|
-
''
|
|
208
|
-
) // Safely access nested properties
|
|
348
|
+
className={
|
|
349
|
+
styles.tableCell
|
|
209
350
|
}
|
|
351
|
+
>
|
|
352
|
+
{column.id ===
|
|
353
|
+
'subtotal' ||
|
|
354
|
+
column.label ===
|
|
355
|
+
'Subtotal'
|
|
356
|
+
? formatCurrency(
|
|
357
|
+
parseFloat(
|
|
358
|
+
get(
|
|
359
|
+
row,
|
|
360
|
+
'rate',
|
|
361
|
+
0
|
|
362
|
+
)
|
|
363
|
+
) *
|
|
364
|
+
parseFloat(
|
|
365
|
+
get(
|
|
366
|
+
row,
|
|
367
|
+
'qty',
|
|
368
|
+
1
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
: column.type ===
|
|
373
|
+
'currency'
|
|
374
|
+
? formatCurrency(
|
|
375
|
+
cellValue
|
|
376
|
+
)
|
|
377
|
+
: cellValue}
|
|
210
378
|
</td>
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
)}
|
|
382
|
+
</tr>
|
|
383
|
+
)
|
|
384
|
+
)}
|
|
385
|
+
|
|
386
|
+
{/* Total row */}
|
|
387
|
+
<tr className={styles.subtotalRow}>
|
|
388
|
+
<td
|
|
389
|
+
colSpan={
|
|
390
|
+
table.columns.length - 1
|
|
391
|
+
}
|
|
392
|
+
className={
|
|
393
|
+
styles.subtotalLabel
|
|
394
|
+
}
|
|
395
|
+
>
|
|
396
|
+
Total
|
|
397
|
+
</td>
|
|
398
|
+
<td
|
|
399
|
+
className={
|
|
400
|
+
styles.subtotalValue
|
|
401
|
+
}
|
|
402
|
+
>
|
|
403
|
+
{formatCurrency(subtotal)}
|
|
404
|
+
</td>
|
|
405
|
+
</tr>
|
|
216
406
|
</tbody>
|
|
217
407
|
</table>
|
|
218
|
-
|
|
408
|
+
</div>
|
|
409
|
+
);
|
|
410
|
+
})}
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
{/* Terms and Condition Section */}
|
|
414
|
+
{data.terms_and_condition && (
|
|
415
|
+
<div className={styles.termsSection}>
|
|
416
|
+
<h3 className={styles.termsTitle}>
|
|
417
|
+
Terms and Conditions
|
|
418
|
+
</h3>
|
|
419
|
+
<div className={styles.termsContent}>
|
|
420
|
+
{data.terms_and_condition}
|
|
219
421
|
</div>
|
|
220
422
|
</div>
|
|
221
|
-
|
|
423
|
+
)}
|
|
222
424
|
</div>
|
|
223
|
-
|
|
425
|
+
|
|
426
|
+
{/* Form Modal */}
|
|
427
|
+
<Popup
|
|
428
|
+
open={modalShow}
|
|
429
|
+
onClose={modalClose}
|
|
430
|
+
closeOnDocumentClick={false}
|
|
431
|
+
contentStyle={{ width: '80vw', maxWidth: '1200px' }}
|
|
432
|
+
>
|
|
433
|
+
<div className="modalwrap top--modal modalWide">
|
|
434
|
+
<div className="modal">
|
|
435
|
+
<Form
|
|
436
|
+
closeModal={modalClose}
|
|
437
|
+
columnId={formId}
|
|
438
|
+
fetchTable={() => {
|
|
439
|
+
// Reload the quote data after form submission
|
|
440
|
+
const fetchData = async () => {
|
|
441
|
+
if (
|
|
442
|
+
routeParams[urlParam] &&
|
|
443
|
+
routeParams[urlParam] > 0
|
|
444
|
+
) {
|
|
445
|
+
try {
|
|
446
|
+
const res = await CustomFetch(
|
|
447
|
+
`${page.fetchUrl}/${routeParams[urlParam]}`,
|
|
448
|
+
'GET',
|
|
449
|
+
{}
|
|
450
|
+
);
|
|
451
|
+
setData(res.data);
|
|
452
|
+
} catch (error) {
|
|
453
|
+
console.error(
|
|
454
|
+
'Error fetching data:',
|
|
455
|
+
error
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
fetchData();
|
|
461
|
+
}}
|
|
462
|
+
formSettings={formData}
|
|
463
|
+
formType={formType}
|
|
464
|
+
style={form.style || {}}
|
|
465
|
+
updateForm={setFormData}
|
|
466
|
+
paramValue={
|
|
467
|
+
routeParams[urlParam]
|
|
468
|
+
? routeParams[urlParam]
|
|
469
|
+
: 0
|
|
470
|
+
}
|
|
471
|
+
/>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
</Popup>
|
|
475
|
+
</div>
|
|
224
476
|
);
|
|
225
477
|
}
|
|
226
478
|
|