@visns-studio/visns-components 5.11.8 → 5.11.10
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/Field.jsx +209 -5
- package/src/components/crm/Form.jsx +9 -2
- package/src/components/crm/MultiSelect.jsx +54 -7
- package/src/components/crm/QuickAction.jsx +15 -1
- package/src/components/crm/generic/GenericDashboard.jsx +1 -1
- package/src/components/crm/generic/GenericQuote.jsx +51 -6
- package/src/components/crm/generic/styles/GenericQuote.module.scss +20 -219
- package/src/components/crm/styles/BusinessCardOcr.module.scss +3 -2
- package/src/components/crm/styles/Field.module.scss +102 -12
- package/src/components/crm/styles/MultiSelect.module.scss +22 -1
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"@uiw/react-color": "^2.6.0",
|
|
17
17
|
"@visns-studio/visns-datagrid-community": "^1.0.14",
|
|
18
18
|
"@visns-studio/visns-datagrid-enterprise": "^1.0.14",
|
|
19
|
-
"@vitejs/plugin-react": "^4.
|
|
19
|
+
"@vitejs/plugin-react": "^4.6.0",
|
|
20
20
|
"add": "^2.0.6",
|
|
21
21
|
"array-move": "^4.0.0",
|
|
22
22
|
"awesome-debounce-promise": "^2.1.0",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
88
88
|
},
|
|
89
89
|
"name": "@visns-studio/visns-components",
|
|
90
|
-
"version": "5.11.
|
|
90
|
+
"version": "5.11.10",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
|
@@ -549,10 +549,10 @@ function Field({
|
|
|
549
549
|
);
|
|
550
550
|
case 'async-dropdown-ajax':
|
|
551
551
|
return (
|
|
552
|
-
<div className={styles.dropdownContainer}>
|
|
552
|
+
<div className={`${styles.dropdownContainer} ${settings.create ? styles.hasButton : ''}`}>
|
|
553
553
|
<VisnsAsyncSelect
|
|
554
554
|
settings={settings}
|
|
555
|
-
className={inputClass[settings.id]}
|
|
555
|
+
className={`${inputClass[settings.id]} ${settings.create ? styles.selectFullWidth : ''}`}
|
|
556
556
|
multi={
|
|
557
557
|
settings.hasOwnProperty('multi')
|
|
558
558
|
? settings.multi
|
|
@@ -568,6 +568,35 @@ function Field({
|
|
|
568
568
|
dataOptions={dataOptions}
|
|
569
569
|
isCreatable={settings.isCreatable || false}
|
|
570
570
|
/>
|
|
571
|
+
{settings.create && (
|
|
572
|
+
<button
|
|
573
|
+
className={styles.buttonRight}
|
|
574
|
+
onClick={(e) => {
|
|
575
|
+
e.preventDefault();
|
|
576
|
+
let allow = false;
|
|
577
|
+
|
|
578
|
+
if (settings.create.parent_id) {
|
|
579
|
+
if (
|
|
580
|
+
formData[settings.create.parent_id]
|
|
581
|
+
) {
|
|
582
|
+
allow = true;
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
allow = true;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (allow) {
|
|
589
|
+
handleAddEntry(settings.create);
|
|
590
|
+
} else {
|
|
591
|
+
toast.warning(
|
|
592
|
+
'Please select a parent item first'
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
}}
|
|
596
|
+
>
|
|
597
|
+
<CirclePlus strokeWidth={2} size={16} />
|
|
598
|
+
</button>
|
|
599
|
+
)}
|
|
571
600
|
</div>
|
|
572
601
|
);
|
|
573
602
|
case 'canvas':
|
|
@@ -778,7 +807,7 @@ function Field({
|
|
|
778
807
|
case 'dropdown':
|
|
779
808
|
case 'dropdown-ajax':
|
|
780
809
|
return (
|
|
781
|
-
<div className={styles.dropdownContainer}>
|
|
810
|
+
<div className={`${styles.dropdownContainer} ${settings.create ? styles.hasButton : ''}`}>
|
|
782
811
|
<Select
|
|
783
812
|
settings={settings}
|
|
784
813
|
className={
|
|
@@ -1287,7 +1316,7 @@ function Field({
|
|
|
1287
1316
|
case 'multi-dropdown':
|
|
1288
1317
|
case 'multi-dropdown-ajax':
|
|
1289
1318
|
return (
|
|
1290
|
-
<div className={styles.dropdownContainer}>
|
|
1319
|
+
<div className={`${styles.dropdownContainer} ${settings.create ? styles.hasButton : ''}`}>
|
|
1291
1320
|
<MultiSelect
|
|
1292
1321
|
settings={settings}
|
|
1293
1322
|
className={
|
|
@@ -2257,8 +2286,178 @@ function Field({
|
|
|
2257
2286
|
}
|
|
2258
2287
|
};
|
|
2259
2288
|
|
|
2260
|
-
const handleReload = () => {
|
|
2289
|
+
const handleReload = (createdItem = null) => {
|
|
2261
2290
|
setCounter(counter + 1);
|
|
2291
|
+
|
|
2292
|
+
// Auto-select the newly created item if provided
|
|
2293
|
+
if (createdItem && onChange) {
|
|
2294
|
+
console.log('Auto-selecting created item:', createdItem);
|
|
2295
|
+
|
|
2296
|
+
// Analyze existing options to understand the expected format
|
|
2297
|
+
const analyzeOptionsFormat = () => {
|
|
2298
|
+
let existingOptions = [];
|
|
2299
|
+
|
|
2300
|
+
// Get options based on dropdown type
|
|
2301
|
+
if (['dropdown', 'multi-dropdown'].includes(settings.type) && settings.options) {
|
|
2302
|
+
existingOptions = settings.options;
|
|
2303
|
+
} else if (['dropdown-ajax', 'multi-dropdown-ajax', 'async-dropdown-ajax'].includes(settings.type) && options) {
|
|
2304
|
+
existingOptions = options;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
if (existingOptions.length === 0) return null;
|
|
2308
|
+
|
|
2309
|
+
// Analyze the first few options to understand the label pattern
|
|
2310
|
+
const sampleOption = existingOptions[0];
|
|
2311
|
+
return {
|
|
2312
|
+
hasLabel: !!sampleOption.label,
|
|
2313
|
+
hasName: !!sampleOption.name,
|
|
2314
|
+
hasFirstname: !!(sampleOption.firstname || sampleOption.surname),
|
|
2315
|
+
hasTitle: !!sampleOption.title,
|
|
2316
|
+
hasDescription: !!sampleOption.description,
|
|
2317
|
+
labelPattern: sampleOption.label || sampleOption.name || 'name'
|
|
2318
|
+
};
|
|
2319
|
+
};
|
|
2320
|
+
|
|
2321
|
+
const formatAnalysis = analyzeOptionsFormat();
|
|
2322
|
+
|
|
2323
|
+
// Determine the label for the created item based on existing options format or common patterns
|
|
2324
|
+
const getItemLabel = (item) => {
|
|
2325
|
+
// If we have format analysis, try to match the existing pattern
|
|
2326
|
+
if (formatAnalysis) {
|
|
2327
|
+
if (formatAnalysis.hasLabel && item.label) return item.label;
|
|
2328
|
+
if (formatAnalysis.hasName && item.name) return item.name;
|
|
2329
|
+
if (formatAnalysis.hasFirstname && (item.firstname || item.surname)) {
|
|
2330
|
+
return `${item.firstname || ''} ${item.surname || ''}`.trim();
|
|
2331
|
+
}
|
|
2332
|
+
if (formatAnalysis.hasTitle && item.title) return item.title;
|
|
2333
|
+
if (formatAnalysis.hasDescription && item.description) return item.description;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// Fallback to common patterns
|
|
2337
|
+
if (item.label) return item.label;
|
|
2338
|
+
if (item.name) return item.name;
|
|
2339
|
+
if (item.firstname || item.surname) {
|
|
2340
|
+
return `${item.firstname || ''} ${item.surname || ''}`.trim();
|
|
2341
|
+
}
|
|
2342
|
+
if (item.title) return item.title;
|
|
2343
|
+
if (item.description) return item.description;
|
|
2344
|
+
return `Item ${item.id}`;
|
|
2345
|
+
};
|
|
2346
|
+
|
|
2347
|
+
const itemLabel = getItemLabel(createdItem);
|
|
2348
|
+
|
|
2349
|
+
// Create a properly formatted item for MultiSelect/Select components
|
|
2350
|
+
// Match the format of existing options as closely as possible
|
|
2351
|
+
const formattedItem = {
|
|
2352
|
+
id: createdItem.id,
|
|
2353
|
+
value: createdItem.id,
|
|
2354
|
+
label: itemLabel,
|
|
2355
|
+
...createdItem // Preserve all original properties
|
|
2356
|
+
};
|
|
2357
|
+
|
|
2358
|
+
console.log('Format analysis:', formatAnalysis);
|
|
2359
|
+
console.log('Formatted item for selection:', formattedItem);
|
|
2360
|
+
|
|
2361
|
+
// For different dropdown types, handle the selection appropriately
|
|
2362
|
+
if (['dropdown', 'dropdown-ajax'].includes(settings.type)) {
|
|
2363
|
+
// For single select fields
|
|
2364
|
+
if (onChangeSelect) {
|
|
2365
|
+
onChangeSelect(formattedItem, { name: settings.id });
|
|
2366
|
+
}
|
|
2367
|
+
// Also update form data with the ID for single selects
|
|
2368
|
+
if (setFormData) {
|
|
2369
|
+
setFormData((prevState) => ({
|
|
2370
|
+
...prevState,
|
|
2371
|
+
[settings.id]: createdItem.id
|
|
2372
|
+
}));
|
|
2373
|
+
}
|
|
2374
|
+
} else if (['multi-dropdown', 'multi-dropdown-ajax', 'async-dropdown-ajax'].includes(settings.type)) {
|
|
2375
|
+
// For multi-select fields, we need to handle existing selections
|
|
2376
|
+
if (onChangeSelect) {
|
|
2377
|
+
// Get current value to determine if it's multi-select
|
|
2378
|
+
const currentValue = inputValue || [];
|
|
2379
|
+
let newValue;
|
|
2380
|
+
|
|
2381
|
+
if (settings.hasOwnProperty('multi') && settings.multi === false) {
|
|
2382
|
+
// Single select in multi-dropdown component
|
|
2383
|
+
newValue = formattedItem;
|
|
2384
|
+
} else {
|
|
2385
|
+
// True multi-select - add to existing selections
|
|
2386
|
+
const currentArray = Array.isArray(currentValue) ? currentValue :
|
|
2387
|
+
(currentValue ? [currentValue] : []);
|
|
2388
|
+
|
|
2389
|
+
// Check if item already exists (avoid duplicates)
|
|
2390
|
+
const existsIndex = currentArray.findIndex(item =>
|
|
2391
|
+
(item.id && item.id === createdItem.id) ||
|
|
2392
|
+
(item.value && item.value === createdItem.id)
|
|
2393
|
+
);
|
|
2394
|
+
|
|
2395
|
+
if (existsIndex === -1) {
|
|
2396
|
+
// Add new item to the array
|
|
2397
|
+
newValue = [...currentArray, formattedItem];
|
|
2398
|
+
} else {
|
|
2399
|
+
// Replace existing item
|
|
2400
|
+
newValue = [...currentArray];
|
|
2401
|
+
newValue[existsIndex] = formattedItem;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
onChangeSelect(newValue, { name: settings.id });
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
// Update form data with the appropriate format
|
|
2409
|
+
if (setFormData) {
|
|
2410
|
+
setFormData((prevState) => {
|
|
2411
|
+
const currentValue = prevState[settings.id] || [];
|
|
2412
|
+
const currentArray = Array.isArray(currentValue) ? currentValue :
|
|
2413
|
+
(currentValue ? [currentValue] : []);
|
|
2414
|
+
|
|
2415
|
+
// Check if we're in multi mode
|
|
2416
|
+
if (settings.hasOwnProperty('multi') && settings.multi === false) {
|
|
2417
|
+
return {
|
|
2418
|
+
...prevState,
|
|
2419
|
+
[settings.id]: formattedItem
|
|
2420
|
+
};
|
|
2421
|
+
} else {
|
|
2422
|
+
// Check for duplicates
|
|
2423
|
+
const existsIndex = currentArray.findIndex(item =>
|
|
2424
|
+
(item.id && item.id === createdItem.id) ||
|
|
2425
|
+
(item.value && item.value === createdItem.id)
|
|
2426
|
+
);
|
|
2427
|
+
|
|
2428
|
+
let newArray;
|
|
2429
|
+
if (existsIndex === -1) {
|
|
2430
|
+
newArray = [...currentArray, formattedItem];
|
|
2431
|
+
} else {
|
|
2432
|
+
newArray = [...currentArray];
|
|
2433
|
+
newArray[existsIndex] = formattedItem;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
return {
|
|
2437
|
+
...prevState,
|
|
2438
|
+
[settings.id]: newArray
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
} else {
|
|
2444
|
+
// For regular input fields, use onChange
|
|
2445
|
+
const syntheticEvent = {
|
|
2446
|
+
target: {
|
|
2447
|
+
dataset: { name: settings.id },
|
|
2448
|
+
value: createdItem.id
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
onChange(syntheticEvent);
|
|
2452
|
+
|
|
2453
|
+
if (setFormData) {
|
|
2454
|
+
setFormData((prevState) => ({
|
|
2455
|
+
...prevState,
|
|
2456
|
+
[settings.id]: createdItem.id
|
|
2457
|
+
}));
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2262
2461
|
};
|
|
2263
2462
|
|
|
2264
2463
|
useEffect(() => {
|
|
@@ -2294,6 +2493,8 @@ function Field({
|
|
|
2294
2493
|
open={childFormShow}
|
|
2295
2494
|
onClose={childFormClose}
|
|
2296
2495
|
closeOnDocumentClick={false}
|
|
2496
|
+
overlayStyle={{ zIndex: 10001 }}
|
|
2497
|
+
contentStyle={{ zIndex: 10002 }}
|
|
2297
2498
|
>
|
|
2298
2499
|
<Form
|
|
2299
2500
|
ajaxSetting={childForm.ajaxSetting}
|
|
@@ -2309,12 +2510,15 @@ function Field({
|
|
|
2309
2510
|
paramValue={null}
|
|
2310
2511
|
style={style}
|
|
2311
2512
|
updateForm={setChildForm}
|
|
2513
|
+
userProfile={userProfile}
|
|
2312
2514
|
/>
|
|
2313
2515
|
</Popup>
|
|
2314
2516
|
<Popup
|
|
2315
2517
|
open={canvasPopupOpen}
|
|
2316
2518
|
onClose={() => setCanvasPopupOpen(false)}
|
|
2317
2519
|
closeOnDocumentClick={false}
|
|
2520
|
+
overlayStyle={{ zIndex: 10001 }}
|
|
2521
|
+
contentStyle={{ zIndex: 10002 }}
|
|
2318
2522
|
>
|
|
2319
2523
|
<div className={`${styles.modalwrap} ${styles['top--modal']}`}>
|
|
2320
2524
|
<div className={styles.modal}>
|
|
@@ -1068,7 +1068,11 @@ function Form({
|
|
|
1068
1068
|
location.reload();
|
|
1069
1069
|
}, 1000);
|
|
1070
1070
|
}
|
|
1071
|
-
if (fetchTable)
|
|
1071
|
+
if (fetchTable) {
|
|
1072
|
+
// Pass the created item data to fetchTable for auto-selection
|
|
1073
|
+
const createdItem = res.data.data || res.data;
|
|
1074
|
+
fetchTable(createdItem);
|
|
1075
|
+
}
|
|
1072
1076
|
if (closeModal) {
|
|
1073
1077
|
closeModal();
|
|
1074
1078
|
if (type === 'saveAnother') {
|
|
@@ -1107,7 +1111,10 @@ function Form({
|
|
|
1107
1111
|
|
|
1108
1112
|
if (resSaveAnother.data.error === '') {
|
|
1109
1113
|
toast.success(saveAnother.message);
|
|
1110
|
-
fetchTable
|
|
1114
|
+
if (fetchTable) {
|
|
1115
|
+
const createdItem = res.data.data || res.data;
|
|
1116
|
+
fetchTable(createdItem);
|
|
1117
|
+
}
|
|
1111
1118
|
}
|
|
1112
1119
|
} else {
|
|
1113
1120
|
fetchData();
|
|
@@ -62,6 +62,43 @@ const CustomMultiValue = (props) => {
|
|
|
62
62
|
);
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
+
// Custom MultiValueRemove component with debug logging
|
|
66
|
+
const CustomMultiValueRemove = (props) => {
|
|
67
|
+
const { innerProps, data } = props;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<components.MultiValueRemove
|
|
71
|
+
{...props}
|
|
72
|
+
innerProps={{
|
|
73
|
+
...innerProps,
|
|
74
|
+
onClick: (e) => {
|
|
75
|
+
console.log('MultiSelect: Remove button clicked for item:', data);
|
|
76
|
+
console.log('MultiSelect: Event details:', e);
|
|
77
|
+
|
|
78
|
+
// Prevent event bubbling
|
|
79
|
+
e.stopPropagation();
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
|
|
82
|
+
// Call the original handler
|
|
83
|
+
if (innerProps && innerProps.onClick) {
|
|
84
|
+
console.log('MultiSelect: Calling original onClick handler');
|
|
85
|
+
innerProps.onClick(e);
|
|
86
|
+
} else {
|
|
87
|
+
console.log('MultiSelect: No original onClick handler found');
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
onMouseDown: (e) => {
|
|
91
|
+
console.log('MultiSelect: Remove button mouse down');
|
|
92
|
+
e.stopPropagation();
|
|
93
|
+
if (innerProps && innerProps.onMouseDown) {
|
|
94
|
+
innerProps.onMouseDown(e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
65
102
|
function MultiSelect({
|
|
66
103
|
className,
|
|
67
104
|
inputValue = [], // Default to an empty array
|
|
@@ -230,12 +267,22 @@ function MultiSelect({
|
|
|
230
267
|
SelectList,
|
|
231
268
|
Option: CustomOption,
|
|
232
269
|
MultiValue: (props) => <CustomMultiValue {...props} settings={settings} />,
|
|
270
|
+
MultiValueRemove: CustomMultiValueRemove,
|
|
233
271
|
}}
|
|
234
272
|
options={selectOptions}
|
|
235
273
|
menuPortalTarget={document.body}
|
|
236
274
|
menuPlacement={menuPlacement}
|
|
237
275
|
onMenuOpen={handleMenuOpen}
|
|
238
276
|
onChange={(inputValue, action) => {
|
|
277
|
+
// Debug logging for delete actions
|
|
278
|
+
if (action.action === 'remove-value') {
|
|
279
|
+
console.log('MultiSelect: Individual item delete:', {
|
|
280
|
+
removedValue: action.removedValue,
|
|
281
|
+
newValue: inputValue,
|
|
282
|
+
action: action
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
239
286
|
// Special handling for limit=1: keep only the most recent selection
|
|
240
287
|
if (
|
|
241
288
|
settings.limit === 1 &&
|
|
@@ -333,7 +380,7 @@ function MultiSelect({
|
|
|
333
380
|
flex: '1',
|
|
334
381
|
minWidth: '0',
|
|
335
382
|
}),
|
|
336
|
-
multiValueRemove: (base) => ({
|
|
383
|
+
multiValueRemove: (base, state) => ({
|
|
337
384
|
...base,
|
|
338
385
|
padding: '0 4px',
|
|
339
386
|
display: 'flex',
|
|
@@ -341,12 +388,12 @@ function MultiSelect({
|
|
|
341
388
|
justifyContent: 'center',
|
|
342
389
|
margin: '0',
|
|
343
390
|
borderRadius: '0 4px 4px 0',
|
|
344
|
-
backgroundColor: 'transparent',
|
|
345
|
-
|
|
346
|
-
'
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
391
|
+
backgroundColor: state.isFocused ? '#dc2626' : 'transparent',
|
|
392
|
+
color: state.isFocused ? 'white' : '#666',
|
|
393
|
+
transition: 'all 0.2s ease',
|
|
394
|
+
cursor: 'pointer',
|
|
395
|
+
minWidth: '16px',
|
|
396
|
+
minHeight: '16px',
|
|
350
397
|
}),
|
|
351
398
|
}}
|
|
352
399
|
value={selectValue}
|
|
@@ -408,7 +408,21 @@ const QuickAction = ({ setting }) => {
|
|
|
408
408
|
</div>
|
|
409
409
|
</div>
|
|
410
410
|
|
|
411
|
-
<Popup
|
|
411
|
+
<Popup
|
|
412
|
+
open={modalShow}
|
|
413
|
+
onClose={modalClose}
|
|
414
|
+
closeOnDocumentClick={false}
|
|
415
|
+
contentStyle={{
|
|
416
|
+
width: '80vw',
|
|
417
|
+
maxWidth: '1200px',
|
|
418
|
+
minWidth: '600px',
|
|
419
|
+
padding: 0,
|
|
420
|
+
border: 'none',
|
|
421
|
+
borderRadius: '8px',
|
|
422
|
+
overflow: 'hidden',
|
|
423
|
+
maxHeight: '90vh',
|
|
424
|
+
}}
|
|
425
|
+
>
|
|
412
426
|
<Form
|
|
413
427
|
closeModal={modalClose}
|
|
414
428
|
fetchTable={() => {}}
|
|
@@ -1355,7 +1355,7 @@ function GenericDashboard({ setting, userProfile, dynamicDashboard = false }) {
|
|
|
1355
1355
|
)}
|
|
1356
1356
|
<div className={styles.grid} ref={container}>
|
|
1357
1357
|
{renderWidgets()}
|
|
1358
|
-
<Popup open={modalShow} onClose={closeModal}>
|
|
1358
|
+
<Popup open={modalShow} onClose={closeModal} closeOnDocumentClick={false}>
|
|
1359
1359
|
<div className="modalwrap top--modal modalWide">
|
|
1360
1360
|
<div className="modal">
|
|
1361
1361
|
<div className="modal__header">
|
|
@@ -20,6 +20,54 @@ import SortableTableBody from './SortableQuoteItems';
|
|
|
20
20
|
|
|
21
21
|
import styles from './styles/GenericQuote.module.scss';
|
|
22
22
|
|
|
23
|
+
// Isolated Proposal Preview Component
|
|
24
|
+
function ProposalPreviewFrame({ content }) {
|
|
25
|
+
const iframeRef = React.useRef(null);
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
if (iframeRef.current && content) {
|
|
29
|
+
const iframe = iframeRef.current;
|
|
30
|
+
const doc = iframe.contentDocument || iframe.contentWindow.document;
|
|
31
|
+
|
|
32
|
+
// Write the complete HTML document to iframe
|
|
33
|
+
doc.open();
|
|
34
|
+
doc.write(content);
|
|
35
|
+
doc.close();
|
|
36
|
+
|
|
37
|
+
// Optional: Add responsive styling to iframe body
|
|
38
|
+
const style = doc.createElement('style');
|
|
39
|
+
style.textContent = `
|
|
40
|
+
body {
|
|
41
|
+
margin: 0;
|
|
42
|
+
padding: 20px;
|
|
43
|
+
font-family: Arial, sans-serif;
|
|
44
|
+
background: white;
|
|
45
|
+
overflow-x: auto;
|
|
46
|
+
}
|
|
47
|
+
* {
|
|
48
|
+
box-sizing: border-box;
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
doc.head.appendChild(style);
|
|
52
|
+
}
|
|
53
|
+
}, [content]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<iframe
|
|
57
|
+
ref={iframeRef}
|
|
58
|
+
className={styles.proposalPreview}
|
|
59
|
+
title="Proposal Preview"
|
|
60
|
+
sandbox="allow-same-origin"
|
|
61
|
+
style={{
|
|
62
|
+
width: '100%',
|
|
63
|
+
height: '100%',
|
|
64
|
+
border: 'none',
|
|
65
|
+
background: 'white'
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
23
71
|
function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
24
72
|
const routeParams = useParams();
|
|
25
73
|
const navigate = useNavigate();
|
|
@@ -835,7 +883,7 @@ function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
|
835
883
|
proposal_section_type: data.proposal_section_type || 'content',
|
|
836
884
|
},
|
|
837
885
|
template_id: parseInt(selectedTemplate),
|
|
838
|
-
filename:
|
|
886
|
+
filename: `${(data.customer?.name || 'Customer').replace(/[/\\?%*:|"<>]/g, '-')} - Proposal #${data.id || 'draft'} - ${(data.label || 'Quote').replace(/[/\\?%*:|"<>]/g, '-')}.pdf`,
|
|
839
887
|
download: true,
|
|
840
888
|
paper: 'a4',
|
|
841
889
|
orientation: 'portrait'
|
|
@@ -844,7 +892,7 @@ function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
|
844
892
|
// Create a form for file download
|
|
845
893
|
const form = document.createElement('form');
|
|
846
894
|
form.method = 'POST';
|
|
847
|
-
form.action =
|
|
895
|
+
form.action = `/ajax/quotes/${data.id}/generate-proposal`;
|
|
848
896
|
form.style.display = 'none';
|
|
849
897
|
|
|
850
898
|
// Add CSRF token
|
|
@@ -1454,10 +1502,7 @@ function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
|
1454
1502
|
<X size={16} aria-hidden="true" />
|
|
1455
1503
|
</button>
|
|
1456
1504
|
</div>
|
|
1457
|
-
<
|
|
1458
|
-
className={styles.proposalPreview}
|
|
1459
|
-
dangerouslySetInnerHTML={{ __html: proposalPreview }}
|
|
1460
|
-
/>
|
|
1505
|
+
<ProposalPreviewFrame content={proposalPreview} />
|
|
1461
1506
|
</div>
|
|
1462
1507
|
)}
|
|
1463
1508
|
|
|
@@ -179,8 +179,10 @@
|
|
|
179
179
|
border: 1px solid #e9ecef;
|
|
180
180
|
border-radius: 8px;
|
|
181
181
|
background: white;
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
height: auto;
|
|
183
|
+
min-height: 1100px;
|
|
184
|
+
max-height: none; /* Remove height restriction */
|
|
185
|
+
overflow: visible; /* Allow content to be visible */
|
|
184
186
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
185
187
|
}
|
|
186
188
|
|
|
@@ -213,225 +215,16 @@
|
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
.proposalPreview {
|
|
216
|
-
/*
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
max-height:
|
|
218
|
+
/* Iframe container styles - content is completely isolated */
|
|
219
|
+
width: 100% !important;
|
|
220
|
+
height: 1000px !important;
|
|
221
|
+
min-height: 1000px !important;
|
|
222
|
+
max-height: none !important;
|
|
221
223
|
background: white;
|
|
222
224
|
box-sizing: border-box;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
/* Reset and apply clean typography for proposal content */
|
|
228
|
-
& * {
|
|
229
|
-
font-family: inherit;
|
|
230
|
-
color: inherit;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/* Base content styling */
|
|
234
|
-
font-family: 'Times', 'Times New Roman', serif;
|
|
235
|
-
font-size: 14px;
|
|
236
|
-
line-height: 1.6;
|
|
237
|
-
color: #333;
|
|
238
|
-
|
|
239
|
-
/* Headings */
|
|
240
|
-
& h1, & h2, & h3, & h4, & h5, & h6 {
|
|
241
|
-
font-family: 'Arial', 'Helvetica', sans-serif;
|
|
242
|
-
font-weight: bold;
|
|
243
|
-
color: #333;
|
|
244
|
-
margin-top: 0;
|
|
245
|
-
margin-bottom: 15px;
|
|
246
|
-
line-height: 1.2;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
& h1 {
|
|
250
|
-
font-size: 28px;
|
|
251
|
-
margin-bottom: 20px;
|
|
252
|
-
margin-top: 0;
|
|
253
|
-
}
|
|
254
|
-
& h2 {
|
|
255
|
-
font-size: 24px;
|
|
256
|
-
margin-top: 30px;
|
|
257
|
-
margin-bottom: 15px;
|
|
258
|
-
}
|
|
259
|
-
& h3 {
|
|
260
|
-
font-size: 20px;
|
|
261
|
-
margin-top: 25px;
|
|
262
|
-
margin-bottom: 12px;
|
|
263
|
-
}
|
|
264
|
-
& h4 { font-size: 18px; margin-bottom: 12px; }
|
|
265
|
-
& h5 { font-size: 16px; margin-bottom: 10px; }
|
|
266
|
-
& h6 { font-size: 14px; margin-bottom: 10px; }
|
|
267
|
-
|
|
268
|
-
/* Paragraphs and text */
|
|
269
|
-
& p {
|
|
270
|
-
font-family: 'Times', 'Times New Roman', serif;
|
|
271
|
-
font-size: 14px;
|
|
272
|
-
line-height: 1.6;
|
|
273
|
-
margin: 0 0 12px 0;
|
|
274
|
-
color: #333;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/* Page breaks */
|
|
278
|
-
& .page-break, & [style*="page-break"] {
|
|
279
|
-
margin-top: 40px;
|
|
280
|
-
padding-top: 40px;
|
|
281
|
-
border-top: 2px dashed #ccc;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/* Tables */
|
|
285
|
-
& table {
|
|
286
|
-
width: 100%;
|
|
287
|
-
border-collapse: collapse;
|
|
288
|
-
margin: 15px 0;
|
|
289
|
-
font-family: 'Arial', 'Helvetica', sans-serif;
|
|
290
|
-
border: 2px solid #333333;
|
|
291
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
292
|
-
|
|
293
|
-
& th, & td {
|
|
294
|
-
padding: 14px;
|
|
295
|
-
border: 1px solid #333333;
|
|
296
|
-
text-align: left;
|
|
297
|
-
font-size: 14px;
|
|
298
|
-
line-height: 1.4;
|
|
299
|
-
color: #333;
|
|
300
|
-
vertical-align: middle;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
& th {
|
|
304
|
-
background: #4fbfa5 !important;
|
|
305
|
-
color: white !important;
|
|
306
|
-
font-weight: bold;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
& td {
|
|
310
|
-
background: white;
|
|
311
|
-
font-weight: 500;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
& tr:nth-child(even) td {
|
|
315
|
-
background: #f9f9f9;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
& tfoot tr {
|
|
319
|
-
background: #f3f4f6;
|
|
320
|
-
|
|
321
|
-
& td {
|
|
322
|
-
font-weight: bold;
|
|
323
|
-
border-top: 2px solid #333333;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
&:last-child {
|
|
327
|
-
background: #333333 !important;
|
|
328
|
-
|
|
329
|
-
& td {
|
|
330
|
-
color: white !important;
|
|
331
|
-
font-weight: bold;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/* Lists */
|
|
338
|
-
& ul, & ol {
|
|
339
|
-
font-family: 'Times', 'Times New Roman', serif;
|
|
340
|
-
font-size: 14px;
|
|
341
|
-
line-height: 1.6;
|
|
342
|
-
margin: 12px 0;
|
|
343
|
-
padding-left: 25px;
|
|
344
|
-
color: #333;
|
|
345
|
-
|
|
346
|
-
& li {
|
|
347
|
-
margin: 8px 0;
|
|
348
|
-
color: #333;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/* Text formatting */
|
|
353
|
-
& strong, & b {
|
|
354
|
-
font-weight: bold;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
& em, & i {
|
|
358
|
-
font-style: italic;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/* Images */
|
|
362
|
-
& img {
|
|
363
|
-
max-width: 100%;
|
|
364
|
-
height: auto;
|
|
365
|
-
margin: 10px 0;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/* Divs and sections */
|
|
369
|
-
& div {
|
|
370
|
-
margin-bottom: 0;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/* Override any branding classes that might interfere */
|
|
374
|
-
& .primary-bg, & .secondary-bg, & .accent-bg,
|
|
375
|
-
& .primary-text, & .secondary-text, & .accent-text {
|
|
376
|
-
color: #333 !important;
|
|
377
|
-
background: transparent !important;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/* Terms and conditions specific styling */
|
|
381
|
-
& .terms-section, & .terms-conditions-section, & [class*="terms"] {
|
|
382
|
-
margin: 20px 0;
|
|
383
|
-
padding: 15px;
|
|
384
|
-
border: 1px solid #ddd;
|
|
385
|
-
background: #f9f9f9;
|
|
386
|
-
|
|
387
|
-
& h1, & h2, & h3, & h4 {
|
|
388
|
-
margin-top: 15px;
|
|
389
|
-
margin-bottom: 10px;
|
|
390
|
-
color: #333;
|
|
391
|
-
|
|
392
|
-
&:first-child {
|
|
393
|
-
margin-top: 0;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
& h1 {
|
|
398
|
-
font-size: 24px;
|
|
399
|
-
border-bottom: 2px solid #333;
|
|
400
|
-
padding-bottom: 10px;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
& h3 {
|
|
404
|
-
font-size: 16px;
|
|
405
|
-
margin-top: 20px;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
& p {
|
|
409
|
-
white-space: pre-wrap;
|
|
410
|
-
word-wrap: break-word;
|
|
411
|
-
margin-bottom: 12px;
|
|
412
|
-
text-align: justify;
|
|
413
|
-
hyphens: auto;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
& .terms-content {
|
|
417
|
-
max-width: none;
|
|
418
|
-
overflow: visible;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/* Ensure all content is visible and not truncated */
|
|
423
|
-
& * {
|
|
424
|
-
max-height: none !important;
|
|
425
|
-
overflow: visible !important;
|
|
426
|
-
text-overflow: clip !important;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/* Special handling for long content */
|
|
430
|
-
& div, & section {
|
|
431
|
-
overflow: visible;
|
|
432
|
-
height: auto;
|
|
433
|
-
max-height: none;
|
|
434
|
-
}
|
|
225
|
+
border: none;
|
|
226
|
+
border-radius: 4px;
|
|
227
|
+
display: block !important;
|
|
435
228
|
}
|
|
436
229
|
|
|
437
230
|
/* Content Editor Styles */
|
|
@@ -1419,12 +1212,18 @@
|
|
|
1419
1212
|
background-color: #f3f4f6;
|
|
1420
1213
|
}
|
|
1421
1214
|
|
|
1215
|
+
.subtotalLabel,
|
|
1216
|
+
.subtotalValue {
|
|
1217
|
+
color: #333 !important;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1422
1220
|
&:last-child {
|
|
1423
1221
|
background-color: #333333 !important;
|
|
1424
1222
|
|
|
1425
1223
|
.subtotalLabel,
|
|
1426
1224
|
.subtotalValue {
|
|
1427
1225
|
color: white !important;
|
|
1226
|
+
background-color: #333333 !important;
|
|
1428
1227
|
}
|
|
1429
1228
|
}
|
|
1430
1229
|
}
|
|
@@ -1436,6 +1235,7 @@
|
|
|
1436
1235
|
color: #374151;
|
|
1437
1236
|
font-size: 14px;
|
|
1438
1237
|
border: 1px solid #333333;
|
|
1238
|
+
background: inherit;
|
|
1439
1239
|
}
|
|
1440
1240
|
|
|
1441
1241
|
.subtotalValue {
|
|
@@ -1446,6 +1246,7 @@
|
|
|
1446
1246
|
font-size: 14px;
|
|
1447
1247
|
white-space: nowrap;
|
|
1448
1248
|
border: 1px solid #333333;
|
|
1249
|
+
background: inherit;
|
|
1449
1250
|
}
|
|
1450
1251
|
|
|
1451
1252
|
/* Responsive styles */
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
border-radius: var(--br, 8px);
|
|
21
21
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
|
22
22
|
overflow: hidden;
|
|
23
|
-
|
|
24
|
-
width:
|
|
23
|
+
width: 80vw;
|
|
24
|
+
max-width: 1200px;
|
|
25
|
+
min-width: 600px;
|
|
25
26
|
max-height: 90vh;
|
|
26
27
|
display: flex;
|
|
27
28
|
flex-direction: column;
|
|
@@ -3,28 +3,118 @@
|
|
|
3
3
|
width: 100%;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
/* Only apply flex layout when there's a create button */
|
|
7
|
+
.dropdownContainer:has(.buttonRight) {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: stretch;
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
.selectFullWidth {
|
|
7
13
|
width: 90%;
|
|
8
14
|
}
|
|
9
15
|
|
|
16
|
+
/* Only style the select when there's a create button */
|
|
17
|
+
.dropdownContainer:has(.buttonRight) .selectFullWidth {
|
|
18
|
+
flex: 1;
|
|
19
|
+
width: auto;
|
|
20
|
+
border-top-right-radius: 0;
|
|
21
|
+
border-bottom-right-radius: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Ensure the select component inside container integrates seamlessly - only when button exists */
|
|
25
|
+
.dropdownContainer:has(.buttonRight) .selectFullWidth > div,
|
|
26
|
+
.dropdownContainer:has(.buttonRight) .selectFullWidth .visns-select__control,
|
|
27
|
+
.dropdownContainer:has(.buttonRight) .selectFullWidth select {
|
|
28
|
+
border-top-right-radius: 0 !important;
|
|
29
|
+
border-bottom-right-radius: 0 !important;
|
|
30
|
+
border-right: none !important;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.dropdownContainer:has(.buttonRight) .selectFullWidth .visns-select__control--is-focused,
|
|
34
|
+
.dropdownContainer:has(.buttonRight) .selectFullWidth select:focus {
|
|
35
|
+
border-right: none !important;
|
|
36
|
+
box-shadow: none !important;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Ensure consistent height between select and button */
|
|
40
|
+
.dropdownContainer:has(.buttonRight) .selectFullWidth select {
|
|
41
|
+
height: auto;
|
|
42
|
+
min-height: 3.1rem; /* Match typical input height */
|
|
43
|
+
}
|
|
44
|
+
|
|
10
45
|
.buttonRight {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
min-width: 42px;
|
|
50
|
+
min-height: 3.1rem;
|
|
51
|
+
background: rgba(var(--primary-color-rgb), 0.08);
|
|
52
|
+
color: var(--primary-color);
|
|
53
|
+
border: 1px solid rgba(var(--primary-color-rgb), 0.25);
|
|
54
|
+
border-left: none;
|
|
55
|
+
border-top-right-radius: var(--br);
|
|
56
|
+
border-bottom-right-radius: var(--br);
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
transition: all 0.2s ease;
|
|
59
|
+
outline: none;
|
|
60
|
+
padding: 0;
|
|
61
|
+
position: relative;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.buttonRight::before {
|
|
65
|
+
content: '';
|
|
11
66
|
position: absolute;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
67
|
+
top: 20%;
|
|
68
|
+
left: 0;
|
|
69
|
+
width: 1px;
|
|
70
|
+
height: 60%;
|
|
71
|
+
background: rgba(var(--primary-color-rgb), 0.15);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.buttonRight:hover {
|
|
16
75
|
background: var(--primary-color);
|
|
17
76
|
color: var(--secondary-color);
|
|
18
|
-
|
|
19
|
-
border: none;
|
|
20
|
-
border-radius: var(--br);
|
|
21
|
-
cursor: pointer;
|
|
22
|
-
transition: background 0.3s;
|
|
77
|
+
border-color: var(--primary-color);
|
|
23
78
|
}
|
|
24
79
|
|
|
25
|
-
.buttonRight:hover {
|
|
26
|
-
|
|
27
|
-
|
|
80
|
+
.buttonRight:hover::before {
|
|
81
|
+
display: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.buttonRight:focus {
|
|
85
|
+
box-shadow: 0 0 0 2px rgba(var(--primary-color-rgb), 0.2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Fallback for browsers that don't support :has() */
|
|
89
|
+
.dropdownContainer.hasButton {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: stretch;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.dropdownContainer.hasButton .selectFullWidth {
|
|
95
|
+
flex: 1;
|
|
96
|
+
width: auto;
|
|
97
|
+
border-top-right-radius: 0;
|
|
98
|
+
border-bottom-right-radius: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.dropdownContainer.hasButton .selectFullWidth > div,
|
|
102
|
+
.dropdownContainer.hasButton .selectFullWidth .visns-select__control,
|
|
103
|
+
.dropdownContainer.hasButton .selectFullWidth select {
|
|
104
|
+
border-top-right-radius: 0 !important;
|
|
105
|
+
border-bottom-right-radius: 0 !important;
|
|
106
|
+
border-right: none !important;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.dropdownContainer.hasButton .selectFullWidth .visns-select__control--is-focused,
|
|
110
|
+
.dropdownContainer.hasButton .selectFullWidth select:focus {
|
|
111
|
+
border-right: none !important;
|
|
112
|
+
box-shadow: none !important;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.dropdownContainer.hasButton .selectFullWidth select {
|
|
116
|
+
height: auto;
|
|
117
|
+
min-height: 3.1rem;
|
|
28
118
|
}
|
|
29
119
|
|
|
30
120
|
.formcontainer {
|
|
@@ -158,12 +158,33 @@
|
|
|
158
158
|
margin: 0 !important;
|
|
159
159
|
border-radius: 0 4px 4px 0 !important;
|
|
160
160
|
background-color: transparent !important;
|
|
161
|
-
transition:
|
|
161
|
+
transition: all 0.2s ease !important;
|
|
162
|
+
cursor: pointer !important;
|
|
163
|
+
pointer-events: auto !important;
|
|
164
|
+
position: relative !important;
|
|
165
|
+
z-index: 10 !important;
|
|
166
|
+
min-width: 20px !important;
|
|
167
|
+
min-height: 20px !important;
|
|
168
|
+
border: 1px solid transparent !important;
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
.visns-select__multi-value__remove:hover {
|
|
165
172
|
background-color: #dc2626 !important;
|
|
166
173
|
color: white !important;
|
|
174
|
+
border-color: #dc2626 !important;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.visns-select__multi-value__remove:focus {
|
|
178
|
+
background-color: #dc2626 !important;
|
|
179
|
+
color: white !important;
|
|
180
|
+
border-color: #dc2626 !important;
|
|
181
|
+
outline: none !important;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.visns-select__multi-value__remove:active {
|
|
185
|
+
background-color: #b91c1c !important;
|
|
186
|
+
color: white !important;
|
|
187
|
+
transform: scale(0.95) !important;
|
|
167
188
|
}
|
|
168
189
|
|
|
169
190
|
/* Fix for modals */
|