@visns-studio/visns-components 5.15.1 → 5.15.3
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/README.md +35 -1
- package/package.json +1 -1
- package/src/components/Call.jsx +6 -4
- package/src/components/DropZone.jsx +5 -4
- package/src/components/Field.jsx +9 -11
- package/src/components/QuickAction.jsx +5 -14
- package/src/components/generic/GenericFormBuilder.jsx +9 -9
- package/src/components/generic/GenericQuote.jsx +132 -90
- package/src/components/generic/StandardModal.jsx +103 -55
- package/src/components/modals/GalleryModal.jsx +9 -16
- package/src/components/styles/GenericQuote.module.scss +44 -5
package/README.md
CHANGED
|
@@ -9,10 +9,14 @@ A comprehensive React component library used by the VISNS Studio team for CRM an
|
|
|
9
9
|
|
|
10
10
|
VISNS Components is a React-based UI component library that provides a set of reusable, consistent, and customizable components for building web applications. It includes components for authentication, data grids, forms, navigation, and more, designed to work seamlessly together.
|
|
11
11
|
|
|
12
|
-
## Recent Updates (v5.15.
|
|
12
|
+
## Recent Updates (v5.15.3)
|
|
13
13
|
|
|
14
14
|
### Latest Enhancements
|
|
15
15
|
|
|
16
|
+
- **Complete StandardModal Migration**: All components now use StandardModal instead of reactjs-popup for consistent modal behavior across the entire library
|
|
17
|
+
- **HTML Content Support**: Terms and conditions now properly parse HTML content from TinyMCE editors with clean rendering
|
|
18
|
+
- **Improved Typography**: Refined font sizing in Terms and Conditions to match page typography hierarchy
|
|
19
|
+
- **Modal Component Refactoring**: StandardModal converted to pure React implementation with enhanced accessibility and mobile support
|
|
16
20
|
- **Enhanced JSON Table Field**: Improved json-table field type with better styling, user experience, and data editing capabilities
|
|
17
21
|
- **Dynamic Action Labels**: GenericDetail component now supports dynamic action labels and custom action buttons for better user interaction
|
|
18
22
|
- **Dependency Updates**: Updated lucide-react to version 0.536.0 for improved icon consistency and performance
|
|
@@ -3379,6 +3383,29 @@ npm run build
|
|
|
3379
3383
|
yarn build
|
|
3380
3384
|
```
|
|
3381
3385
|
|
|
3386
|
+
## Migration Notes
|
|
3387
|
+
|
|
3388
|
+
### StandardModal Migration (v5.15.3) - COMPLETED
|
|
3389
|
+
|
|
3390
|
+
We have successfully migrated all components from `reactjs-popup` to our standardized `StandardModal` component for improved consistency and better user experience.
|
|
3391
|
+
|
|
3392
|
+
**All Migrations Completed ✅:**
|
|
3393
|
+
- ✅ **GenericQuote**: Updated to use StandardModal for form modals
|
|
3394
|
+
- ✅ **StandardModal**: Refactored to pure React implementation with enhanced accessibility
|
|
3395
|
+
- ✅ **Field.jsx**: Migrated 2 Popup instances (childFormShow and canvasPopupOpen)
|
|
3396
|
+
- ✅ **GenericFormBuilder.jsx**: Migrated 2 Popup instances (modalShow and modalFormShow)
|
|
3397
|
+
- ✅ **DropZone.jsx**: Migrated 1 Popup instance (modalShow)
|
|
3398
|
+
- ✅ **modals/GalleryModal.jsx**: Migrated 1 Popup instance (modalGalleryShow) with custom overlay styling
|
|
3399
|
+
- ✅ **QuickAction.jsx**: Migrated 1 Popup instance (modalShow)
|
|
3400
|
+
- ✅ **Call.jsx**: Migrated 1 Popup instance (modalShow)
|
|
3401
|
+
|
|
3402
|
+
**Benefits of StandardModal:**
|
|
3403
|
+
- Consistent modal behavior across all components
|
|
3404
|
+
- Better keyboard navigation and accessibility
|
|
3405
|
+
- Unified styling and responsive design
|
|
3406
|
+
- No external dependency on reactjs-popup
|
|
3407
|
+
- Enhanced mobile support with virtual keyboard accommodation
|
|
3408
|
+
|
|
3382
3409
|
## Contributing
|
|
3383
3410
|
|
|
3384
3411
|
We welcome contributions! Please follow these steps:
|
|
@@ -3633,6 +3660,13 @@ All existing GenericQuote functionality remains unchanged:
|
|
|
3633
3660
|
|
|
3634
3661
|
Proposal features are **opt-in** and activated only when `proposalConfig.enabled: true` is set.
|
|
3635
3662
|
|
|
3663
|
+
**Recent Improvements (v5.15.3):**
|
|
3664
|
+
- **StandardModal Integration**: Replaced reactjs-popup with StandardModal for consistent modal behavior and better maintainability
|
|
3665
|
+
- **HTML Content Rendering**: Terms and conditions now support rich HTML content from TinyMCE editors with proper parsing via `html-react-parser`
|
|
3666
|
+
- **Typography Optimization**: Refined font sizing (14px desktop, 12px mobile) for better visual hierarchy and page consistency
|
|
3667
|
+
- **Enhanced Styling**: Improved CSS for HTML elements including lists, paragraphs, and formatted text in terms sections
|
|
3668
|
+
- **Complete Modal Consistency**: All modals throughout the library now use the same StandardModal component
|
|
3669
|
+
|
|
3636
3670
|
**Advanced Usage Example:**
|
|
3637
3671
|
|
|
3638
3672
|
```jsx
|
package/package.json
CHANGED
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
91
91
|
},
|
|
92
92
|
"name": "@visns-studio/visns-components",
|
|
93
|
-
"version": "5.15.
|
|
93
|
+
"version": "5.15.3",
|
|
94
94
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
95
95
|
"main": "src/index.js",
|
|
96
96
|
"files": [
|
package/src/components/Call.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import
|
|
2
|
+
import StandardModal from "./generic/StandardModal";
|
|
3
3
|
import moment from "moment";
|
|
4
4
|
|
|
5
5
|
import CustomFetch from "./Fetch";
|
|
@@ -124,12 +124,14 @@ function CallPop(props) {
|
|
|
124
124
|
</button>
|
|
125
125
|
</div>
|
|
126
126
|
</div>
|
|
127
|
-
<
|
|
128
|
-
|
|
127
|
+
<StandardModal
|
|
128
|
+
isOpen={modalShow}
|
|
129
129
|
onClose={() => {
|
|
130
130
|
setCallShow(false);
|
|
131
131
|
setModalShow(false);
|
|
132
132
|
}}
|
|
133
|
+
size="medium"
|
|
134
|
+
closeOnDocumentClick={false}
|
|
133
135
|
>
|
|
134
136
|
<div className="modalwrap">
|
|
135
137
|
<div className="grid">
|
|
@@ -210,7 +212,7 @@ function CallPop(props) {
|
|
|
210
212
|
</div>
|
|
211
213
|
</div>
|
|
212
214
|
</div>
|
|
213
|
-
</
|
|
215
|
+
</StandardModal>
|
|
214
216
|
</>
|
|
215
217
|
) : null}
|
|
216
218
|
</>
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from 'lucide-react';
|
|
16
16
|
import { toast } from 'react-toastify';
|
|
17
17
|
import { arrayMoveImmutable } from 'array-move';
|
|
18
|
-
import
|
|
18
|
+
import StandardModal from './generic/StandardModal';
|
|
19
19
|
import imageCompression from 'browser-image-compression';
|
|
20
20
|
|
|
21
21
|
import { confirmDialog } from './utils/ConfirmDialog';
|
|
@@ -637,9 +637,10 @@ function VisnsDropZone({ fetchData, files, settings, url, urlKey, deleteUrl, ent
|
|
|
637
637
|
</div>
|
|
638
638
|
) : null}
|
|
639
639
|
</div>
|
|
640
|
-
<
|
|
641
|
-
|
|
640
|
+
<StandardModal
|
|
641
|
+
isOpen={modalShow}
|
|
642
642
|
onClose={modalClose}
|
|
643
|
+
size="large"
|
|
643
644
|
closeOnDocumentClick={false}
|
|
644
645
|
>
|
|
645
646
|
<div className={`${styles.modalwrap} top--modal`}>
|
|
@@ -704,7 +705,7 @@ function VisnsDropZone({ fetchData, files, settings, url, urlKey, deleteUrl, ent
|
|
|
704
705
|
</div>
|
|
705
706
|
</div>
|
|
706
707
|
</div>
|
|
707
|
-
</
|
|
708
|
+
</StandardModal>
|
|
708
709
|
</>
|
|
709
710
|
);
|
|
710
711
|
}
|
package/src/components/Field.jsx
CHANGED
|
@@ -6,7 +6,7 @@ import _ from 'lodash';
|
|
|
6
6
|
import Toggle from 'react-toggle';
|
|
7
7
|
import moment from 'moment';
|
|
8
8
|
import parse from 'html-react-parser';
|
|
9
|
-
import
|
|
9
|
+
import StandardModal from './generic/StandardModal';
|
|
10
10
|
import { Sketch } from '@uiw/react-color';
|
|
11
11
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
12
12
|
import { NumericFormat, PatternFormat } from 'react-number-format';
|
|
@@ -3095,12 +3095,11 @@ function Field({
|
|
|
3095
3095
|
{renderFieldLabel()}
|
|
3096
3096
|
</label>
|
|
3097
3097
|
{renderAdditionalContainer()}
|
|
3098
|
-
<
|
|
3099
|
-
|
|
3098
|
+
<StandardModal
|
|
3099
|
+
isOpen={childFormShow}
|
|
3100
3100
|
onClose={childFormClose}
|
|
3101
|
+
size="large"
|
|
3101
3102
|
closeOnDocumentClick={false}
|
|
3102
|
-
overlayStyle={{ zIndex: 10001 }}
|
|
3103
|
-
contentStyle={{ zIndex: 10002 }}
|
|
3104
3103
|
>
|
|
3105
3104
|
<Form
|
|
3106
3105
|
ajaxSetting={childForm.ajaxSetting}
|
|
@@ -3118,13 +3117,12 @@ function Field({
|
|
|
3118
3117
|
updateForm={setChildForm}
|
|
3119
3118
|
userProfile={userProfile}
|
|
3120
3119
|
/>
|
|
3121
|
-
</
|
|
3122
|
-
<
|
|
3123
|
-
|
|
3120
|
+
</StandardModal>
|
|
3121
|
+
<StandardModal
|
|
3122
|
+
isOpen={canvasPopupOpen}
|
|
3124
3123
|
onClose={() => setCanvasPopupOpen(false)}
|
|
3124
|
+
size="large"
|
|
3125
3125
|
closeOnDocumentClick={false}
|
|
3126
|
-
overlayStyle={{ zIndex: 10001 }}
|
|
3127
|
-
contentStyle={{ zIndex: 10002 }}
|
|
3128
3126
|
>
|
|
3129
3127
|
<div className={`${styles.modalwrap} ${styles['top--modal']}`}>
|
|
3130
3128
|
<div className={styles.modal}>
|
|
@@ -3240,7 +3238,7 @@ function Field({
|
|
|
3240
3238
|
</div>
|
|
3241
3239
|
</div>
|
|
3242
3240
|
</div>
|
|
3243
|
-
</
|
|
3241
|
+
</StandardModal>
|
|
3244
3242
|
</div>
|
|
3245
3243
|
);
|
|
3246
3244
|
}
|
|
@@ -6,7 +6,7 @@ import React, {
|
|
|
6
6
|
useMemo,
|
|
7
7
|
} from 'react';
|
|
8
8
|
import { useNavigate } from 'react-router-dom';
|
|
9
|
-
import
|
|
9
|
+
import StandardModal from './generic/StandardModal';
|
|
10
10
|
import { Search, X, Zap } from 'lucide-react';
|
|
11
11
|
|
|
12
12
|
import CustomFetch from './Fetch';
|
|
@@ -408,20 +408,11 @@ const QuickAction = ({ setting }) => {
|
|
|
408
408
|
</div>
|
|
409
409
|
</div>
|
|
410
410
|
|
|
411
|
-
<
|
|
412
|
-
|
|
411
|
+
<StandardModal
|
|
412
|
+
isOpen={modalShow}
|
|
413
413
|
onClose={modalClose}
|
|
414
|
+
size="large"
|
|
414
415
|
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
416
|
>
|
|
426
417
|
<Form
|
|
427
418
|
closeModal={modalClose}
|
|
@@ -431,7 +422,7 @@ const QuickAction = ({ setting }) => {
|
|
|
431
422
|
columnId={formId}
|
|
432
423
|
childDropdownCallback={childDropdownCallback}
|
|
433
424
|
/>
|
|
434
|
-
</
|
|
425
|
+
</StandardModal>
|
|
435
426
|
</>
|
|
436
427
|
);
|
|
437
428
|
};
|
|
@@ -2,7 +2,7 @@ import '../styles/global.css';
|
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { useParams } from 'react-router-dom';
|
|
5
|
-
import
|
|
5
|
+
import StandardModal from './StandardModal';
|
|
6
6
|
import { arrayMove } from '@dnd-kit/sortable';
|
|
7
7
|
import {
|
|
8
8
|
DndContext,
|
|
@@ -1222,12 +1222,12 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
1222
1222
|
</div>
|
|
1223
1223
|
</div>
|
|
1224
1224
|
|
|
1225
|
-
<
|
|
1226
|
-
|
|
1225
|
+
<StandardModal
|
|
1226
|
+
isOpen={modalShow}
|
|
1227
1227
|
onClose={handleCloseModal}
|
|
1228
|
+
size="medium"
|
|
1228
1229
|
closeOnDocumentClick={false}
|
|
1229
1230
|
>
|
|
1230
|
-
{modalShow && <div className={styles.modalBackdrop} />}
|
|
1231
1231
|
<div className={styles.modalwrap}>
|
|
1232
1232
|
<div className={styles.modal}>
|
|
1233
1233
|
<div className={styles.modal__header}>
|
|
@@ -2254,14 +2254,14 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
2254
2254
|
</div>
|
|
2255
2255
|
</div>
|
|
2256
2256
|
</div>
|
|
2257
|
-
</
|
|
2257
|
+
</StandardModal>
|
|
2258
2258
|
|
|
2259
|
-
<
|
|
2260
|
-
|
|
2259
|
+
<StandardModal
|
|
2260
|
+
isOpen={modalFormShow}
|
|
2261
2261
|
onClose={handleCloseModalForm}
|
|
2262
|
+
size="medium"
|
|
2262
2263
|
closeOnDocumentClick={false}
|
|
2263
2264
|
>
|
|
2264
|
-
{modalFormShow && <div className={styles.modalBackdrop} />}
|
|
2265
2265
|
<div className={styles.modalwrap}>
|
|
2266
2266
|
<div className={styles.modal}>
|
|
2267
2267
|
<div className={styles.modal__header}>
|
|
@@ -2308,7 +2308,7 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
2308
2308
|
</div>
|
|
2309
2309
|
</div>
|
|
2310
2310
|
</div>
|
|
2311
|
-
</
|
|
2311
|
+
</StandardModal>
|
|
2312
2312
|
|
|
2313
2313
|
{/* React Tooltips */}
|
|
2314
2314
|
<Tooltip
|
|
@@ -2,11 +2,12 @@ import '../styles/global.css';
|
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect } from 'react';
|
|
4
4
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
5
|
-
import
|
|
5
|
+
import StandardModal from './StandardModal';
|
|
6
6
|
import moment from 'moment';
|
|
7
7
|
import get from 'lodash/get';
|
|
8
8
|
import { saveAs } from 'file-saver';
|
|
9
9
|
import { toast } from 'react-toastify';
|
|
10
|
+
import parse from 'html-react-parser';
|
|
10
11
|
import { arrayMoveImmutable } from 'array-move';
|
|
11
12
|
import { showConfirmDialog } from './ConfirmationDialog';
|
|
12
13
|
import { FileText, DollarSign, Clock, Eye, X, Edit3, FileEdit, Save, Settings } from 'lucide-react';
|
|
@@ -592,14 +593,9 @@ function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
|
592
593
|
console.log('Saved template ID not found in available templates:', data.proposal_template_id);
|
|
593
594
|
}
|
|
594
595
|
} else {
|
|
595
|
-
//
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
);
|
|
599
|
-
if (defaultTemplate) {
|
|
600
|
-
console.log('Setting default template:', defaultTemplate);
|
|
601
|
-
setSelectedTemplate(defaultTemplate.id);
|
|
602
|
-
}
|
|
596
|
+
// Leave template unselected if no saved template
|
|
597
|
+
console.log('No saved template found, leaving unselected');
|
|
598
|
+
setSelectedTemplate(null);
|
|
603
599
|
}
|
|
604
600
|
|
|
605
601
|
console.log('Templates successfully loaded:', templates.length, 'templates');
|
|
@@ -833,13 +829,22 @@ function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
|
833
829
|
};
|
|
834
830
|
|
|
835
831
|
const generateProposalPDF = async () => {
|
|
832
|
+
console.log('generateProposalPDF - Starting', {
|
|
833
|
+
selectedTemplate,
|
|
834
|
+
hasSelectedTemplate: !!selectedTemplate,
|
|
835
|
+
dataId: data.id,
|
|
836
|
+
proposalTemplateId: data.proposal_template_id,
|
|
837
|
+
});
|
|
838
|
+
|
|
836
839
|
if (!selectedTemplate) {
|
|
840
|
+
console.error('generateProposalPDF - No template selected');
|
|
837
841
|
toast.error('Please select a template first');
|
|
838
842
|
return;
|
|
839
843
|
}
|
|
840
844
|
|
|
841
845
|
setIsGeneratingProposal(true);
|
|
842
846
|
const toastId = toast.loading('Generating proposal PDF...');
|
|
847
|
+
console.log('generateProposalPDF - Toast created', { toastId });
|
|
843
848
|
|
|
844
849
|
try {
|
|
845
850
|
const proposalData = {
|
|
@@ -889,45 +894,79 @@ function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
|
889
894
|
orientation: 'portrait'
|
|
890
895
|
};
|
|
891
896
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
+
console.log('generateProposalPDF - Proposal data prepared', {
|
|
898
|
+
proposalDataKeys: Object.keys(proposalData.proposal_data),
|
|
899
|
+
templateId: proposalData.template_id,
|
|
900
|
+
filename: proposalData.filename,
|
|
901
|
+
dataLength: JSON.stringify(proposalData.proposal_data).length,
|
|
902
|
+
});
|
|
897
903
|
|
|
898
|
-
//
|
|
904
|
+
// Use fetch + file-saver for clean PDF download
|
|
899
905
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
+
console.log('generateProposalPDF - CSRF token', {
|
|
907
|
+
hasToken: !!csrfToken,
|
|
908
|
+
tokenLength: csrfToken?.length || 0
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
if (!csrfToken) {
|
|
912
|
+
console.error('generateProposalPDF - No CSRF token found!');
|
|
913
|
+
throw new Error('CSRF token not found');
|
|
906
914
|
}
|
|
907
915
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
916
|
+
console.log('generateProposalPDF - Making fetch request with FormData...');
|
|
917
|
+
|
|
918
|
+
// Create FormData for the request
|
|
919
|
+
const formData = new FormData();
|
|
920
|
+
formData.append('_token', csrfToken);
|
|
921
|
+
formData.append('proposal_data', JSON.stringify(proposalData.proposal_data));
|
|
922
|
+
formData.append('template_id', selectedTemplate);
|
|
923
|
+
formData.append('filename', proposalData.filename);
|
|
924
|
+
formData.append('download', 'false'); // Use stream instead of download for AJAX
|
|
925
|
+
|
|
926
|
+
console.log('generateProposalPDF - FormData prepared');
|
|
927
|
+
|
|
928
|
+
const response = await fetch(`/ajax/quotes/${data.id}/generate-proposal`, {
|
|
929
|
+
method: 'POST',
|
|
930
|
+
headers: {
|
|
931
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
932
|
+
// Don't set Content-Type for FormData - browser sets it automatically
|
|
933
|
+
},
|
|
934
|
+
body: formData,
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
console.log('generateProposalPDF - Fetch response received', {
|
|
938
|
+
status: response.status,
|
|
939
|
+
statusText: response.statusText,
|
|
940
|
+
contentType: response.headers.get('content-type'),
|
|
941
|
+
ok: response.ok,
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
if (!response.ok) {
|
|
945
|
+
let errorText = 'Unknown error';
|
|
946
|
+
try {
|
|
947
|
+
errorText = await response.text();
|
|
948
|
+
} catch (e) {
|
|
949
|
+
console.error('Failed to read error response', e);
|
|
950
|
+
}
|
|
951
|
+
console.error('generateProposalPDF - Server error response', {
|
|
952
|
+
status: response.status,
|
|
953
|
+
statusText: response.statusText,
|
|
954
|
+
errorText,
|
|
955
|
+
});
|
|
956
|
+
throw new Error(`Server error: ${response.status} - ${errorText}`);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
console.log('generateProposalPDF - Converting response to blob...');
|
|
960
|
+
const blob = await response.blob();
|
|
961
|
+
|
|
962
|
+
console.log('generateProposalPDF - Blob created', {
|
|
963
|
+
size: blob.size,
|
|
964
|
+
type: blob.type,
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
// Use file-saver to download the PDF
|
|
968
|
+
saveAs(blob, proposalData.filename);
|
|
969
|
+
console.log('generateProposalPDF - File saved using saveAs');
|
|
931
970
|
|
|
932
971
|
toast.update(toastId, {
|
|
933
972
|
render: 'Proposal PDF download started',
|
|
@@ -937,7 +976,13 @@ function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
|
937
976
|
closeButton: true,
|
|
938
977
|
});
|
|
939
978
|
} catch (error) {
|
|
940
|
-
console.error('
|
|
979
|
+
console.error('generateProposalPDF - Exception caught', {
|
|
980
|
+
error: error,
|
|
981
|
+
errorMessage: error.message,
|
|
982
|
+
errorStack: error.stack,
|
|
983
|
+
selectedTemplate,
|
|
984
|
+
dataId: data.id,
|
|
985
|
+
});
|
|
941
986
|
toast.update(toastId, {
|
|
942
987
|
render: 'Failed to generate proposal PDF: ' + error.message,
|
|
943
988
|
type: 'error',
|
|
@@ -946,6 +991,7 @@ function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
|
946
991
|
closeButton: true,
|
|
947
992
|
});
|
|
948
993
|
} finally {
|
|
994
|
+
console.log('generateProposalPDF - Finally block, setting isGeneratingProposal to false');
|
|
949
995
|
setIsGeneratingProposal(false);
|
|
950
996
|
}
|
|
951
997
|
};
|
|
@@ -1943,61 +1989,57 @@ function GenericQuote({ logo, setting, urlParam, proposalConfig = null }) {
|
|
|
1943
1989
|
Terms and Conditions
|
|
1944
1990
|
</h3>
|
|
1945
1991
|
<div className={styles.termsContent}>
|
|
1946
|
-
{data.terms_and_condition}
|
|
1992
|
+
{parse(data.terms_and_condition || '')}
|
|
1947
1993
|
</div>
|
|
1948
1994
|
</div>
|
|
1949
1995
|
)}
|
|
1950
1996
|
</div>
|
|
1951
1997
|
|
|
1952
1998
|
{/* Form Modal */}
|
|
1953
|
-
<
|
|
1954
|
-
|
|
1999
|
+
<StandardModal
|
|
2000
|
+
isOpen={modalShow}
|
|
1955
2001
|
onClose={modalClose}
|
|
2002
|
+
size="large"
|
|
1956
2003
|
closeOnDocumentClick={false}
|
|
1957
|
-
contentStyle={{ width: '80vw', maxWidth: '1200px' }}
|
|
1958
2004
|
>
|
|
1959
|
-
<
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
);
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
};
|
|
1986
|
-
fetchData();
|
|
1987
|
-
}}
|
|
1988
|
-
formSettings={formData}
|
|
1989
|
-
formType={formType}
|
|
1990
|
-
style={form.style || {}}
|
|
1991
|
-
updateForm={setFormData}
|
|
1992
|
-
paramValue={
|
|
1993
|
-
routeParams[urlParam]
|
|
1994
|
-
? routeParams[urlParam]
|
|
1995
|
-
: 0
|
|
2005
|
+
<Form
|
|
2006
|
+
closeModal={modalClose}
|
|
2007
|
+
columnId={formId}
|
|
2008
|
+
fetchTable={() => {
|
|
2009
|
+
// Reload the quote data after form submission
|
|
2010
|
+
const fetchData = async () => {
|
|
2011
|
+
if (
|
|
2012
|
+
routeParams[urlParam] &&
|
|
2013
|
+
routeParams[urlParam] > 0
|
|
2014
|
+
) {
|
|
2015
|
+
try {
|
|
2016
|
+
const res = await CustomFetch(
|
|
2017
|
+
`${page.fetchUrl}/${routeParams[urlParam]}`,
|
|
2018
|
+
'GET',
|
|
2019
|
+
{}
|
|
2020
|
+
);
|
|
2021
|
+
setData(res.data);
|
|
2022
|
+
} catch (error) {
|
|
2023
|
+
console.error(
|
|
2024
|
+
'Error fetching data:',
|
|
2025
|
+
error
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
1996
2028
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2029
|
+
};
|
|
2030
|
+
fetchData();
|
|
2031
|
+
}}
|
|
2032
|
+
formSettings={formData}
|
|
2033
|
+
formType={formType}
|
|
2034
|
+
style={form.style || {}}
|
|
2035
|
+
updateForm={setFormData}
|
|
2036
|
+
paramValue={
|
|
2037
|
+
routeParams[urlParam]
|
|
2038
|
+
? routeParams[urlParam]
|
|
2039
|
+
: 0
|
|
2040
|
+
}
|
|
2041
|
+
/>
|
|
2042
|
+
</StandardModal>
|
|
2001
2043
|
</div>
|
|
2002
2044
|
);
|
|
2003
2045
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import Popup from 'reactjs-popup';
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
3
2
|
import { X } from 'lucide-react';
|
|
4
3
|
import formStyles from '../styles/Form.module.scss';
|
|
5
4
|
|
|
@@ -67,6 +66,37 @@ const StandardModal = ({
|
|
|
67
66
|
window.addEventListener('resize', handleResize);
|
|
68
67
|
return () => window.removeEventListener('resize', handleResize);
|
|
69
68
|
}, []);
|
|
69
|
+
|
|
70
|
+
// Handle keyboard events
|
|
71
|
+
const handleKeyDown = useCallback((e) => {
|
|
72
|
+
if (!isOpen) return;
|
|
73
|
+
|
|
74
|
+
if (e.key === 'Escape' && closeOnEscape) {
|
|
75
|
+
onClose();
|
|
76
|
+
}
|
|
77
|
+
}, [isOpen, closeOnEscape, onClose]);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (isOpen) {
|
|
81
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
82
|
+
document.body.style.overflow = 'hidden';
|
|
83
|
+
} else {
|
|
84
|
+
document.body.style.overflow = 'unset';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return () => {
|
|
88
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
89
|
+
document.body.style.overflow = 'unset';
|
|
90
|
+
};
|
|
91
|
+
}, [isOpen, handleKeyDown]);
|
|
92
|
+
|
|
93
|
+
// Handle backdrop click
|
|
94
|
+
const handleBackdropClick = (e) => {
|
|
95
|
+
if (closeOnDocumentClick && e.target === e.currentTarget) {
|
|
96
|
+
onClose();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
70
100
|
// Apply vertical alignment class if needed
|
|
71
101
|
const getModalClasses = () => {
|
|
72
102
|
let classes = formStyles.modal;
|
|
@@ -76,32 +106,47 @@ const StandardModal = ({
|
|
|
76
106
|
return classes;
|
|
77
107
|
};
|
|
78
108
|
|
|
79
|
-
// Get
|
|
80
|
-
const
|
|
109
|
+
// Get overlay styles
|
|
110
|
+
const getOverlayStyle = () => {
|
|
81
111
|
const baseStyle = {
|
|
82
|
-
|
|
112
|
+
position: 'fixed',
|
|
113
|
+
top: 0,
|
|
114
|
+
left: 0,
|
|
115
|
+
right: 0,
|
|
116
|
+
bottom: 0,
|
|
117
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
118
|
+
display: 'flex',
|
|
119
|
+
alignItems: verticalAlign === 'top' ? 'flex-start' : 'center',
|
|
120
|
+
justifyContent: 'center',
|
|
121
|
+
zIndex: 1000,
|
|
122
|
+
padding: '20px',
|
|
123
|
+
...customStyles.overlay
|
|
83
124
|
};
|
|
84
125
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (isMobile || isTablet) {
|
|
88
|
-
// Position modal higher on mobile/tablet to accommodate virtual keyboard
|
|
89
|
-
positioningStyle = {
|
|
90
|
-
top: '5%', // Position higher (5% from top)
|
|
91
|
-
left: '50%', // Center horizontally
|
|
92
|
-
transform: 'translateX(-50%)', // Only center horizontally, no vertical transform
|
|
93
|
-
maxHeight: '85vh', // Limit height to prevent overflow
|
|
94
|
-
overflowY: 'auto', // Allow scrolling within modal if needed
|
|
95
|
-
position: 'fixed' // Ensure fixed positioning for proper centering
|
|
96
|
-
};
|
|
126
|
+
if (verticalAlign === 'top') {
|
|
127
|
+
baseStyle.paddingTop = '5%';
|
|
97
128
|
}
|
|
98
129
|
|
|
130
|
+
return baseStyle;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Get size-based styles for the modal content with device-aware positioning
|
|
134
|
+
const getContentStyle = () => {
|
|
135
|
+
const baseStyle = {
|
|
136
|
+
position: 'relative',
|
|
137
|
+
backgroundColor: 'white',
|
|
138
|
+
borderRadius: '8px',
|
|
139
|
+
boxShadow: '0 10px 30px rgba(0, 0, 0, 0.3)',
|
|
140
|
+
maxHeight: isMobile || isTablet ? '85vh' : '90vh',
|
|
141
|
+
overflowY: 'auto',
|
|
142
|
+
...customStyles.modal
|
|
143
|
+
};
|
|
144
|
+
|
|
99
145
|
// Apply size-based styles with device considerations
|
|
100
146
|
switch (size) {
|
|
101
147
|
case 'small':
|
|
102
148
|
return {
|
|
103
149
|
...baseStyle,
|
|
104
|
-
...positioningStyle,
|
|
105
150
|
width: isMobile ? '95vw' : '400px',
|
|
106
151
|
minWidth: isMobile ? '300px' : '400px',
|
|
107
152
|
maxWidth: isMobile ? '95vw' : '600px'
|
|
@@ -109,7 +154,6 @@ const StandardModal = ({
|
|
|
109
154
|
case 'large':
|
|
110
155
|
return {
|
|
111
156
|
...baseStyle,
|
|
112
|
-
...positioningStyle,
|
|
113
157
|
width: '95vw',
|
|
114
158
|
minWidth: isMobile ? '300px' : '800px',
|
|
115
159
|
maxWidth: isMobile ? '95vw' : '1600px'
|
|
@@ -118,51 +162,55 @@ const StandardModal = ({
|
|
|
118
162
|
default:
|
|
119
163
|
return {
|
|
120
164
|
...baseStyle,
|
|
121
|
-
|
|
122
|
-
width: isMobile ? '95vw' : '90vw',
|
|
165
|
+
width: isMobile ? '95vw' : '80vw',
|
|
123
166
|
minWidth: isMobile ? '300px' : '600px',
|
|
124
|
-
maxWidth: isMobile ? '95vw' : '
|
|
167
|
+
maxWidth: isMobile ? '95vw' : '1200px'
|
|
125
168
|
};
|
|
126
169
|
}
|
|
127
170
|
};
|
|
128
171
|
|
|
172
|
+
if (!isOpen) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
129
176
|
return (
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
modal
|
|
134
|
-
closeOnDocumentClick={closeOnDocumentClick}
|
|
135
|
-
closeOnEscape={closeOnEscape}
|
|
136
|
-
contentStyle={getContentStyle()}
|
|
177
|
+
<div
|
|
178
|
+
style={getOverlayStyle()}
|
|
179
|
+
onClick={handleBackdropClick}
|
|
137
180
|
>
|
|
138
|
-
<div
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
style={customStyles.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
181
|
+
<div
|
|
182
|
+
style={getContentStyle()}
|
|
183
|
+
onClick={(e) => e.stopPropagation()}
|
|
184
|
+
>
|
|
185
|
+
<div className={formStyles.modalwrap}>
|
|
186
|
+
<div
|
|
187
|
+
className={getModalClasses()}
|
|
188
|
+
>
|
|
189
|
+
{showHeader && (
|
|
190
|
+
<div className={formStyles.modal__header} style={customStyles.header}>
|
|
191
|
+
<h1 style={customStyles.title}>{title}</h1>
|
|
192
|
+
<button
|
|
193
|
+
className={formStyles.modal__close}
|
|
194
|
+
onClick={onClose}
|
|
195
|
+
style={customStyles.closeButton}
|
|
196
|
+
type="button"
|
|
197
|
+
aria-label="Close modal"
|
|
198
|
+
>
|
|
199
|
+
<X strokeWidth={1} size={24} />
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
{showHeader ? (
|
|
204
|
+
<div className={formStyles.modal__content} style={customStyles.content}>
|
|
205
|
+
{children}
|
|
206
|
+
</div>
|
|
207
|
+
) : (
|
|
208
|
+
children
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
163
211
|
</div>
|
|
164
212
|
</div>
|
|
165
|
-
</
|
|
213
|
+
</div>
|
|
166
214
|
);
|
|
167
215
|
};
|
|
168
216
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import
|
|
2
|
+
import StandardModal from '../generic/StandardModal';
|
|
3
3
|
import { toast } from 'react-toastify';
|
|
4
4
|
import imageCompression from 'browser-image-compression';
|
|
5
5
|
// import Vapor from 'laravel-vapor'; // Commented out to avoid build issues
|
|
@@ -169,22 +169,15 @@ const GalleryModal = ({
|
|
|
169
169
|
|
|
170
170
|
return (
|
|
171
171
|
<>
|
|
172
|
-
<
|
|
173
|
-
|
|
172
|
+
<StandardModal
|
|
173
|
+
isOpen={modalGalleryShow}
|
|
174
174
|
onClose={modalGalleryClose}
|
|
175
|
+
size="large"
|
|
175
176
|
closeOnDocumentClick={false}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
padding: 0,
|
|
181
|
-
border: 'none',
|
|
182
|
-
borderRadius: '8px',
|
|
183
|
-
overflow: 'hidden',
|
|
184
|
-
maxHeight: '90vh',
|
|
185
|
-
}}
|
|
186
|
-
overlayStyle={{
|
|
187
|
-
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
177
|
+
customStyles={{
|
|
178
|
+
overlay: {
|
|
179
|
+
backgroundColor: 'rgba(0, 0, 0, 0.7)'
|
|
180
|
+
}
|
|
188
181
|
}}
|
|
189
182
|
>
|
|
190
183
|
<div className={styles.modalwrap}>
|
|
@@ -390,7 +383,7 @@ const GalleryModal = ({
|
|
|
390
383
|
</div>
|
|
391
384
|
</div>
|
|
392
385
|
</div>
|
|
393
|
-
</
|
|
386
|
+
</StandardModal>
|
|
394
387
|
|
|
395
388
|
{/* Lightbox for full-size image viewing */}
|
|
396
389
|
<Lightbox
|
|
@@ -1061,10 +1061,49 @@
|
|
|
1061
1061
|
}
|
|
1062
1062
|
|
|
1063
1063
|
.termsContent {
|
|
1064
|
-
font-size:
|
|
1065
|
-
line-height: 1.
|
|
1064
|
+
font-size: 14px;
|
|
1065
|
+
line-height: 1.4;
|
|
1066
1066
|
color: var(--paragraph-color);
|
|
1067
|
-
|
|
1067
|
+
|
|
1068
|
+
/* Reset styles to avoid conflicts with TinyMCE HTML content */
|
|
1069
|
+
white-space: normal;
|
|
1070
|
+
|
|
1071
|
+
/* Basic styling for TinyMCE HTML elements */
|
|
1072
|
+
ul, ol {
|
|
1073
|
+
margin: 8px 0;
|
|
1074
|
+
padding-left: 20px;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
li {
|
|
1078
|
+
margin: 4px 0;
|
|
1079
|
+
padding: 0;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
p {
|
|
1083
|
+
margin: 8px 0;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
strong, b {
|
|
1087
|
+
font-weight: 600;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
em, i {
|
|
1091
|
+
font-style: italic;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/* Remove excessive spacing from nested elements */
|
|
1095
|
+
ul ul, ol ol, ul ol, ol ul {
|
|
1096
|
+
margin: 2px 0;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/* Ensure proper spacing for the first and last elements */
|
|
1100
|
+
> *:first-child {
|
|
1101
|
+
margin-top: 0;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
> *:last-child {
|
|
1105
|
+
margin-bottom: 0;
|
|
1106
|
+
}
|
|
1068
1107
|
}
|
|
1069
1108
|
|
|
1070
1109
|
.quoteTableSection {
|
|
@@ -1387,7 +1426,7 @@
|
|
|
1387
1426
|
}
|
|
1388
1427
|
|
|
1389
1428
|
.termsContent {
|
|
1390
|
-
font-size:
|
|
1391
|
-
line-height: 1.
|
|
1429
|
+
font-size: 12px;
|
|
1430
|
+
line-height: 1.3;
|
|
1392
1431
|
}
|
|
1393
1432
|
}
|