@visns-studio/visns-components 5.11.7 → 5.11.8
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 +1 -1
- package/src/components/crm/Field.jsx +209 -82
- package/src/components/crm/generic/GenericQuote.jsx +454 -121
- package/src/components/crm/generic/styles/GenericQuote.module.scss +571 -68
- package/src/components/crm/styles/Field.module.scss +301 -0
- package/src/components/proposal/ProposalTemplatePreview.css +26 -8
- package/src/components/proposal/ProposalTemplateSectionManager.css +47 -5
- package/src/components/proposal/ProposalTemplateSectionManager.jsx +33 -7
- package/src/components/proposal/SectionEditor.jsx +48 -14
- package/src/components/proposal/SectionTypeSelector.css +54 -0
- package/src/components/proposal/SectionTypeSelector.jsx +15 -3
|
@@ -816,3 +816,304 @@ input[type='file']:hover {
|
|
|
816
816
|
width: 1.2em;
|
|
817
817
|
height: 1.2em;
|
|
818
818
|
}
|
|
819
|
+
|
|
820
|
+
/* Enhanced File Upload Styling */
|
|
821
|
+
.single-file-container {
|
|
822
|
+
display: flex;
|
|
823
|
+
flex-direction: column;
|
|
824
|
+
gap: 0.75em;
|
|
825
|
+
width: 100%;
|
|
826
|
+
padding: 0.5em;
|
|
827
|
+
border: 2px dashed var(--border-color, #ddd);
|
|
828
|
+
border-radius: var(--br, 8px);
|
|
829
|
+
background: var(--tertiary-color, #fafafa);
|
|
830
|
+
transition: all 0.3s ease;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.single-file-container:hover {
|
|
834
|
+
border-color: var(--primary-color, #007bff);
|
|
835
|
+
background: rgba(var(--primary-color-rgb, 0, 123, 255), 0.02);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.single-file-image {
|
|
839
|
+
border-color: var(--success-color, #28a745);
|
|
840
|
+
background: rgba(var(--success-color-rgb, 40, 167, 69), 0.05);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
.single-file-image:hover {
|
|
844
|
+
border-color: var(--success-color, #28a745);
|
|
845
|
+
background: rgba(var(--success-color-rgb, 40, 167, 69), 0.1);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.file-upload-button {
|
|
849
|
+
display: flex;
|
|
850
|
+
align-items: center;
|
|
851
|
+
gap: 0.5em;
|
|
852
|
+
padding: 1em 1.5em;
|
|
853
|
+
border: 1px solid var(--border-color, #ccc);
|
|
854
|
+
border-radius: var(--br, 6px);
|
|
855
|
+
background: var(--tertiary-color, #f8f9fa);
|
|
856
|
+
color: var(--paragraph-color, #333);
|
|
857
|
+
cursor: pointer;
|
|
858
|
+
transition: all 0.3s ease;
|
|
859
|
+
font-size: 0.9em;
|
|
860
|
+
font-weight: 500;
|
|
861
|
+
text-decoration: none;
|
|
862
|
+
outline: none;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.file-upload-button:hover {
|
|
866
|
+
background: var(--primary-color, #007bff);
|
|
867
|
+
color: var(--secondary-color, #fff);
|
|
868
|
+
border-color: var(--primary-color, #007bff);
|
|
869
|
+
transform: translateY(-1px);
|
|
870
|
+
box-shadow: 0 2px 8px rgba(var(--primary-color-rgb, 0, 123, 255), 0.3);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.file-upload-button-image {
|
|
874
|
+
background: rgba(var(--success-color-rgb, 40, 167, 69), 0.1);
|
|
875
|
+
border-color: var(--success-color, #28a745);
|
|
876
|
+
color: var(--success-color, #28a745);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.file-upload-button-image:hover {
|
|
880
|
+
background: var(--success-color, #28a745);
|
|
881
|
+
color: var(--secondary-color, #fff);
|
|
882
|
+
border-color: var(--success-color, #28a745);
|
|
883
|
+
box-shadow: 0 2px 8px rgba(var(--success-color-rgb, 40, 167, 69), 0.3);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.file-button-icon {
|
|
887
|
+
font-size: 1.2em;
|
|
888
|
+
display: flex;
|
|
889
|
+
align-items: center;
|
|
890
|
+
justify-content: center;
|
|
891
|
+
width: 1.5em;
|
|
892
|
+
height: 1.5em;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.file-button-text {
|
|
896
|
+
font-weight: 500;
|
|
897
|
+
letter-spacing: 0.01em;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
.file-info {
|
|
901
|
+
display: flex;
|
|
902
|
+
align-items: center;
|
|
903
|
+
justify-content: space-between;
|
|
904
|
+
gap: 1em;
|
|
905
|
+
padding: 0.75em 1em;
|
|
906
|
+
background: rgba(var(--item-color-rgb, 255, 255, 255), 0.8);
|
|
907
|
+
border: 1px solid var(--border-color, #e9ecef);
|
|
908
|
+
border-radius: var(--br, 6px);
|
|
909
|
+
font-size: 0.85em;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.file-name-display {
|
|
913
|
+
flex: 1;
|
|
914
|
+
color: var(--paragraph-color, #495057);
|
|
915
|
+
font-weight: 500;
|
|
916
|
+
overflow: hidden;
|
|
917
|
+
text-overflow: ellipsis;
|
|
918
|
+
white-space: nowrap;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.download-button {
|
|
922
|
+
display: flex;
|
|
923
|
+
align-items: center;
|
|
924
|
+
justify-content: center;
|
|
925
|
+
padding: 0.5em;
|
|
926
|
+
background: transparent;
|
|
927
|
+
border: 1px solid var(--border-color, #ccc);
|
|
928
|
+
border-radius: var(--br, 4px);
|
|
929
|
+
color: var(--primary-color, #007bff);
|
|
930
|
+
cursor: pointer;
|
|
931
|
+
transition: all 0.3s ease;
|
|
932
|
+
outline: none;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
.download-button:hover {
|
|
936
|
+
background: var(--primary-color, #007bff);
|
|
937
|
+
color: var(--secondary-color, #fff);
|
|
938
|
+
border-color: var(--primary-color, #007bff);
|
|
939
|
+
transform: scale(1.05);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/* Enhanced Multiple File List Styling */
|
|
943
|
+
.file-list-item,
|
|
944
|
+
.file-list-item-image {
|
|
945
|
+
display: flex;
|
|
946
|
+
align-items: center;
|
|
947
|
+
gap: 0.75em;
|
|
948
|
+
padding: 0.75em 1em;
|
|
949
|
+
border-bottom: 1px solid var(--border-color, #e9ecef);
|
|
950
|
+
font-size: 0.9rem;
|
|
951
|
+
background: var(--item-color, #fff);
|
|
952
|
+
border-radius: var(--br, 6px);
|
|
953
|
+
transition: all 0.3s ease;
|
|
954
|
+
margin-bottom: 0.5em;
|
|
955
|
+
border: 1px solid transparent;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.file-list-item-image {
|
|
959
|
+
background: rgba(var(--success-color-rgb, 40, 167, 69), 0.05);
|
|
960
|
+
border-color: rgba(var(--success-color-rgb, 40, 167, 69), 0.2);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
.file-list-item:hover,
|
|
964
|
+
.file-list-item-image:hover {
|
|
965
|
+
background: rgba(var(--primary-color-rgb, 0, 123, 255), 0.05);
|
|
966
|
+
border-color: var(--primary-color, #007bff);
|
|
967
|
+
transform: translateY(-1px);
|
|
968
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.file-list-item-image:hover {
|
|
972
|
+
background: rgba(var(--success-color-rgb, 40, 167, 69), 0.1);
|
|
973
|
+
border-color: var(--success-color, #28a745);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.file-icon {
|
|
977
|
+
font-size: 1.5em;
|
|
978
|
+
display: flex;
|
|
979
|
+
align-items: center;
|
|
980
|
+
justify-content: center;
|
|
981
|
+
width: 2em;
|
|
982
|
+
height: 2em;
|
|
983
|
+
flex-shrink: 0;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.file-name {
|
|
987
|
+
flex: 1;
|
|
988
|
+
color: var(--paragraph-color, #495057);
|
|
989
|
+
font-weight: 500;
|
|
990
|
+
overflow: hidden;
|
|
991
|
+
text-overflow: ellipsis;
|
|
992
|
+
white-space: nowrap;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/* Image Preview Styling */
|
|
996
|
+
.image-preview-container {
|
|
997
|
+
display: flex;
|
|
998
|
+
justify-content: center;
|
|
999
|
+
align-items: center;
|
|
1000
|
+
margin-bottom: 1em;
|
|
1001
|
+
padding: 0.5em;
|
|
1002
|
+
background: rgba(var(--item-color-rgb, 255, 255, 255), 0.9);
|
|
1003
|
+
border: 1px solid var(--border-color, #e9ecef);
|
|
1004
|
+
border-radius: var(--br, 6px);
|
|
1005
|
+
overflow: hidden;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
.image-preview {
|
|
1009
|
+
max-width: 200px;
|
|
1010
|
+
max-height: 150px;
|
|
1011
|
+
width: auto;
|
|
1012
|
+
height: auto;
|
|
1013
|
+
object-fit: contain;
|
|
1014
|
+
border-radius: var(--br, 4px);
|
|
1015
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
1016
|
+
transition: transform 0.3s ease;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.image-preview:hover {
|
|
1020
|
+
transform: scale(1.05);
|
|
1021
|
+
cursor: pointer;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
.file-details {
|
|
1025
|
+
display: flex;
|
|
1026
|
+
align-items: center;
|
|
1027
|
+
justify-content: space-between;
|
|
1028
|
+
gap: 1em;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/* Multiple File Image Preview */
|
|
1032
|
+
.file-image-preview {
|
|
1033
|
+
display: flex;
|
|
1034
|
+
align-items: center;
|
|
1035
|
+
justify-content: center;
|
|
1036
|
+
width: 3em;
|
|
1037
|
+
height: 3em;
|
|
1038
|
+
flex-shrink: 0;
|
|
1039
|
+
background: rgba(var(--item-color-rgb, 255, 255, 255), 0.9);
|
|
1040
|
+
border: 1px solid var(--border-color, #e9ecef);
|
|
1041
|
+
border-radius: var(--br, 4px);
|
|
1042
|
+
overflow: hidden;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
.file-thumbnail {
|
|
1046
|
+
width: 100%;
|
|
1047
|
+
height: 100%;
|
|
1048
|
+
object-fit: cover;
|
|
1049
|
+
border-radius: var(--br, 4px);
|
|
1050
|
+
transition: transform 0.3s ease;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.file-thumbnail:hover {
|
|
1054
|
+
transform: scale(1.1);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/* Enhanced Multiple File List Layout */
|
|
1058
|
+
.file-list-item,
|
|
1059
|
+
.file-list-item-image {
|
|
1060
|
+
display: grid;
|
|
1061
|
+
grid-template-columns: auto 1fr auto;
|
|
1062
|
+
align-items: center;
|
|
1063
|
+
gap: 1em;
|
|
1064
|
+
padding: 0.75em 1em;
|
|
1065
|
+
border-bottom: 1px solid var(--border-color, #e9ecef);
|
|
1066
|
+
font-size: 0.9rem;
|
|
1067
|
+
background: var(--item-color, #fff);
|
|
1068
|
+
border-radius: var(--br, 6px);
|
|
1069
|
+
transition: all 0.3s ease;
|
|
1070
|
+
margin-bottom: 0.5em;
|
|
1071
|
+
border: 1px solid transparent;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.file-list-item-image {
|
|
1075
|
+
background: rgba(var(--success-color-rgb, 40, 167, 69), 0.05);
|
|
1076
|
+
border-color: rgba(var(--success-color-rgb, 40, 167, 69), 0.2);
|
|
1077
|
+
grid-template-columns: auto 1fr auto; /* Consistent grid for images */
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/* Responsive Design */
|
|
1081
|
+
@media (max-width: 768px) {
|
|
1082
|
+
.image-preview {
|
|
1083
|
+
max-width: 150px;
|
|
1084
|
+
max-height: 100px;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.file-image-preview {
|
|
1088
|
+
width: 2.5em;
|
|
1089
|
+
height: 2.5em;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
.file-info {
|
|
1093
|
+
flex-direction: column;
|
|
1094
|
+
align-items: stretch;
|
|
1095
|
+
gap: 0.75em;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.file-details {
|
|
1099
|
+
flex-direction: column;
|
|
1100
|
+
align-items: stretch;
|
|
1101
|
+
gap: 0.5em;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
@media (max-width: 480px) {
|
|
1106
|
+
.image-preview {
|
|
1107
|
+
max-width: 120px;
|
|
1108
|
+
max-height: 80px;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
.single-file-container {
|
|
1112
|
+
padding: 0.25em;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
.file-upload-button {
|
|
1116
|
+
padding: 0.75em 1em;
|
|
1117
|
+
font-size: 0.85em;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
@@ -243,16 +243,25 @@
|
|
|
243
243
|
.proposal-preview-prose ol {
|
|
244
244
|
font-size: 0.875rem !important;
|
|
245
245
|
line-height: 1.4 !important;
|
|
246
|
-
margin: 0 0 0.5rem 1rem !important;
|
|
247
|
-
padding: 0;
|
|
246
|
+
margin: 0.5rem 0 0.5rem 1rem !important;
|
|
247
|
+
padding-left: 0 !important;
|
|
248
248
|
color: #374151 !important;
|
|
249
|
+
list-style-type: disc !important;
|
|
250
|
+
list-style-position: outside !important;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.proposal-preview-prose ol {
|
|
254
|
+
list-style-type: decimal !important;
|
|
249
255
|
}
|
|
250
256
|
|
|
251
257
|
.proposal-preview-prose li {
|
|
252
|
-
margin: 0 !important;
|
|
253
|
-
padding:
|
|
258
|
+
margin: 0.25rem 0 !important;
|
|
259
|
+
padding-left: 1.2rem !important;
|
|
260
|
+
margin-left: 0 !important;
|
|
254
261
|
font-size: 0.875rem !important;
|
|
255
262
|
line-height: 1.4 !important;
|
|
263
|
+
display: list-item !important;
|
|
264
|
+
text-indent: 0 !important;
|
|
256
265
|
}
|
|
257
266
|
|
|
258
267
|
.proposal-preview-section {
|
|
@@ -304,16 +313,25 @@
|
|
|
304
313
|
.proposal-preview-section-content ol {
|
|
305
314
|
font-size: 0.875rem !important;
|
|
306
315
|
line-height: 1.4 !important;
|
|
307
|
-
margin: 0 0 0.5rem 1rem !important;
|
|
308
|
-
padding: 0;
|
|
316
|
+
margin: 0.5rem 0 0.5rem 1rem !important;
|
|
317
|
+
padding-left: 0 !important;
|
|
309
318
|
color: #4b5563 !important;
|
|
319
|
+
list-style-type: disc !important;
|
|
320
|
+
list-style-position: outside !important;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.proposal-preview-section-content ol {
|
|
324
|
+
list-style-type: decimal !important;
|
|
310
325
|
}
|
|
311
326
|
|
|
312
327
|
.proposal-preview-section-content li {
|
|
313
|
-
margin: 0 !important;
|
|
314
|
-
padding:
|
|
328
|
+
margin: 0.25rem 0 !important;
|
|
329
|
+
padding-left: 1.2rem !important;
|
|
330
|
+
margin-left: 0 !important;
|
|
315
331
|
font-size: 0.875rem !important;
|
|
316
332
|
line-height: 1.4 !important;
|
|
333
|
+
display: list-item !important;
|
|
334
|
+
text-indent: 0 !important;
|
|
317
335
|
}
|
|
318
336
|
|
|
319
337
|
.proposal-preview-empty {
|
|
@@ -15,10 +15,42 @@
|
|
|
15
15
|
overflow: hidden;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
.section-item:hover {
|
|
18
|
+
.section-item:hover:not(.sortable-helper) {
|
|
19
19
|
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.1);
|
|
20
20
|
border-color: #cbd5e1;
|
|
21
|
-
transform
|
|
21
|
+
/* Remove transform to prevent conflicts with sortable transforms */
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Sortable Helper Styles - Applied during drag */
|
|
25
|
+
.sortable-helper {
|
|
26
|
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15) !important;
|
|
27
|
+
border-color: #3b82f6 !important;
|
|
28
|
+
transform: rotate(2deg) !important;
|
|
29
|
+
z-index: 9999 !important;
|
|
30
|
+
background: rgba(255, 255, 255, 0.95) !important;
|
|
31
|
+
backdrop-filter: blur(4px);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.sortable-helper .section-header {
|
|
35
|
+
background: rgba(59, 130, 246, 0.1) !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Prevent hover effects during drag */
|
|
39
|
+
.sortable-helper:hover {
|
|
40
|
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15) !important;
|
|
41
|
+
border-color: #3b82f6 !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Smooth transitions for non-dragged items */
|
|
45
|
+
.section-item:not(.sortable-helper) {
|
|
46
|
+
transition: all 0.2s ease-in-out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Ghost/placeholder styling */
|
|
50
|
+
.sortable-ghost {
|
|
51
|
+
opacity: 0.4;
|
|
52
|
+
background: #f8fafc !important;
|
|
53
|
+
border: 2px dashed #cbd5e1 !important;
|
|
22
54
|
}
|
|
23
55
|
|
|
24
56
|
.section-header {
|
|
@@ -38,14 +70,24 @@
|
|
|
38
70
|
}
|
|
39
71
|
|
|
40
72
|
.drag-handle {
|
|
41
|
-
cursor:
|
|
73
|
+
cursor: grab;
|
|
42
74
|
padding: 0.5rem;
|
|
43
75
|
color: #9ca3af;
|
|
44
|
-
transition:
|
|
76
|
+
transition: all 0.2s ease;
|
|
77
|
+
border-radius: 4px;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
45
81
|
}
|
|
46
82
|
|
|
47
83
|
.drag-handle:hover {
|
|
48
|
-
color: #
|
|
84
|
+
color: #3b82f6;
|
|
85
|
+
background: rgba(59, 130, 246, 0.05);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.drag-handle:active {
|
|
89
|
+
cursor: grabbing;
|
|
90
|
+
background: rgba(59, 130, 246, 0.1);
|
|
49
91
|
}
|
|
50
92
|
|
|
51
93
|
.section-title {
|
|
@@ -90,7 +90,7 @@ const SortableSectionList = SortableContainer(({ sections, onEdit, onDelete, onT
|
|
|
90
90
|
<div>
|
|
91
91
|
{sections.map((section, index) => (
|
|
92
92
|
<SortableSection
|
|
93
|
-
key={
|
|
93
|
+
key={section.id}
|
|
94
94
|
index={index}
|
|
95
95
|
section={section}
|
|
96
96
|
onEdit={onEdit}
|
|
@@ -164,11 +164,12 @@ const ProposalTemplateSectionManager = ({
|
|
|
164
164
|
sort_order: index + 1
|
|
165
165
|
}));
|
|
166
166
|
|
|
167
|
+
// Immediately update UI for smooth experience
|
|
167
168
|
setSections(updatedSections);
|
|
168
169
|
|
|
169
170
|
try {
|
|
170
171
|
// Update sort orders on the backend
|
|
171
|
-
await fetch(buildUrlWithTemplate(apiEndpoints.sectionsReorder), {
|
|
172
|
+
const response = await fetch(buildUrlWithTemplate(apiEndpoints.sectionsReorder), {
|
|
172
173
|
method: 'POST',
|
|
173
174
|
headers: {
|
|
174
175
|
'Content-Type': 'application/json',
|
|
@@ -178,19 +179,33 @@ const ProposalTemplateSectionManager = ({
|
|
|
178
179
|
sections: updatedSections.map(s => ({ id: s.id, sort_order: s.sort_order }))
|
|
179
180
|
})
|
|
180
181
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
|
|
183
|
+
if (response.ok) {
|
|
184
|
+
// Don't refetch data immediately to prevent UI jumping
|
|
185
|
+
// The local state is already updated and accurate
|
|
186
|
+
toast.success('Section order updated');
|
|
187
|
+
} else {
|
|
188
|
+
throw new Error('Failed to update section order');
|
|
189
|
+
}
|
|
184
190
|
} catch (error) {
|
|
185
191
|
console.error('Error updating section order:', error);
|
|
186
192
|
toast.error('Failed to update section order');
|
|
187
|
-
// Revert on error
|
|
188
|
-
|
|
193
|
+
// Revert to original order on error
|
|
194
|
+
setSections(sections);
|
|
189
195
|
}
|
|
190
196
|
};
|
|
191
197
|
|
|
192
198
|
const handleAddSection = async (sectionType) => {
|
|
193
199
|
try {
|
|
200
|
+
// Check if a section with this type already exists
|
|
201
|
+
const existingSection = sections.find(section => section.section_type === sectionType);
|
|
202
|
+
|
|
203
|
+
if (existingSection) {
|
|
204
|
+
const sectionTypeName = sectionType.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
205
|
+
toast.error(`A ${sectionTypeName} section already exists in this template. Each template can only have one section of each type.`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
194
209
|
const response = await fetch(buildUrlWithTemplate(apiEndpoints.sectionsCreate), {
|
|
195
210
|
method: 'POST',
|
|
196
211
|
headers: {
|
|
@@ -447,6 +462,16 @@ const ProposalTemplateSectionManager = ({
|
|
|
447
462
|
onDelete={handleDeleteSection}
|
|
448
463
|
onToggleVisibility={handleToggleVisibility}
|
|
449
464
|
useDragHandle
|
|
465
|
+
lockAxis="y"
|
|
466
|
+
helperClass="sortable-helper"
|
|
467
|
+
transitionDuration={200}
|
|
468
|
+
shouldCancelStart={(e) => {
|
|
469
|
+
// Prevent drag from starting on interactive elements
|
|
470
|
+
if (e.target.closest('button, input, textarea, select, a')) {
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
}}
|
|
450
475
|
/>
|
|
451
476
|
)}
|
|
452
477
|
|
|
@@ -454,6 +479,7 @@ const ProposalTemplateSectionManager = ({
|
|
|
454
479
|
<SectionTypeSelector
|
|
455
480
|
onSelect={handleAddSection}
|
|
456
481
|
onCancel={() => setShowSectionTypeSelector(false)}
|
|
482
|
+
existingSections={sections}
|
|
457
483
|
/>
|
|
458
484
|
)}
|
|
459
485
|
|
|
@@ -37,12 +37,38 @@ const SectionEditor = ({ section, availableVariables = [], onSave, onCancel }) =
|
|
|
37
37
|
}
|
|
38
38
|
}, [section]);
|
|
39
39
|
|
|
40
|
+
// Function to extract variables from content
|
|
41
|
+
const extractVariablesFromContent = (content) => {
|
|
42
|
+
if (!content || typeof content !== 'string') return [];
|
|
43
|
+
|
|
44
|
+
const variableMatches = content.match(/{{(\w+)}}/g);
|
|
45
|
+
if (!variableMatches) return [];
|
|
46
|
+
|
|
47
|
+
const variables = variableMatches.map(match =>
|
|
48
|
+
match.replace(/[{}]/g, '')
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Return unique variables
|
|
52
|
+
return [...new Set(variables)];
|
|
53
|
+
};
|
|
54
|
+
|
|
40
55
|
const handleInputChange = (field, value) => {
|
|
41
56
|
console.log(`Field change: ${field} =`, value);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
|
|
58
|
+
// If content is being changed, update variables automatically
|
|
59
|
+
if (field === 'content') {
|
|
60
|
+
const extractedVariables = extractVariablesFromContent(value);
|
|
61
|
+
setFormData(prev => ({
|
|
62
|
+
...prev,
|
|
63
|
+
[field]: value,
|
|
64
|
+
variables: extractedVariables
|
|
65
|
+
}));
|
|
66
|
+
} else {
|
|
67
|
+
setFormData(prev => ({
|
|
68
|
+
...prev,
|
|
69
|
+
[field]: value
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
46
72
|
};
|
|
47
73
|
|
|
48
74
|
const handleStylingChange = (property, value) => {
|
|
@@ -61,17 +87,21 @@ const SectionEditor = ({ section, availableVariables = [], onSave, onCancel }) =
|
|
|
61
87
|
// Try to insert at cursor position in TinyMCE editor
|
|
62
88
|
if (window.tinymce && window.tinymce.activeEditor) {
|
|
63
89
|
window.tinymce.activeEditor.insertContent(variableTag);
|
|
90
|
+
|
|
91
|
+
// Get the updated content and extract variables
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
const updatedContent = window.tinymce.activeEditor.getContent();
|
|
94
|
+
const extractedVariables = extractVariablesFromContent(updatedContent);
|
|
95
|
+
setFormData(prev => ({
|
|
96
|
+
...prev,
|
|
97
|
+
content: updatedContent,
|
|
98
|
+
variables: extractedVariables
|
|
99
|
+
}));
|
|
100
|
+
}, 100);
|
|
64
101
|
} else {
|
|
65
102
|
// Fallback: append to content
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// Add to variables array if not already present
|
|
70
|
-
if (!formData.variables.includes(variable)) {
|
|
71
|
-
setFormData(prev => ({
|
|
72
|
-
...prev,
|
|
73
|
-
variables: [...prev.variables, variable]
|
|
74
|
-
}));
|
|
103
|
+
const updatedContent = formData.content + variableTag;
|
|
104
|
+
handleInputChange('content', updatedContent);
|
|
75
105
|
}
|
|
76
106
|
|
|
77
107
|
setShowVariableInserter(false);
|
|
@@ -105,9 +135,13 @@ const SectionEditor = ({ section, availableVariables = [], onSave, onCancel }) =
|
|
|
105
135
|
}
|
|
106
136
|
}
|
|
107
137
|
|
|
138
|
+
// Extract variables from final content before saving
|
|
139
|
+
const extractedVariables = extractVariablesFromContent(currentContent);
|
|
140
|
+
|
|
108
141
|
const finalFormData = {
|
|
109
142
|
...formData,
|
|
110
|
-
content: currentContent
|
|
143
|
+
content: currentContent,
|
|
144
|
+
variables: extractedVariables
|
|
111
145
|
};
|
|
112
146
|
|
|
113
147
|
console.log('Final form data being saved:', finalFormData);
|
|
@@ -274,4 +274,58 @@
|
|
|
274
274
|
.section-type-modal-body {
|
|
275
275
|
padding: 1rem;
|
|
276
276
|
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Disabled Section State */
|
|
280
|
+
.section-type-card.disabled {
|
|
281
|
+
opacity: 0.5;
|
|
282
|
+
cursor: not-allowed;
|
|
283
|
+
background: #f8f9fa !important;
|
|
284
|
+
border-color: #e9ecef !important;
|
|
285
|
+
transform: none !important;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.section-type-card.disabled:hover {
|
|
289
|
+
transform: none !important;
|
|
290
|
+
box-shadow: none !important;
|
|
291
|
+
border-color: #e9ecef !important;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.section-type-card.disabled .section-type-icon {
|
|
295
|
+
opacity: 0.6;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.section-type-card.disabled .section-type-name,
|
|
299
|
+
.section-type-card.disabled .section-type-description {
|
|
300
|
+
color: #6c757d !important;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Already Used Badge */
|
|
304
|
+
.used-badge {
|
|
305
|
+
display: inline-block;
|
|
306
|
+
background: #dc3545;
|
|
307
|
+
color: white;
|
|
308
|
+
font-size: 0.65rem;
|
|
309
|
+
font-weight: 500;
|
|
310
|
+
padding: 0.15rem 0.4rem;
|
|
311
|
+
border-radius: 0.25rem;
|
|
312
|
+
margin-left: 0.5rem;
|
|
313
|
+
text-transform: uppercase;
|
|
314
|
+
letter-spacing: 0.025em;
|
|
315
|
+
vertical-align: middle;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* Dynamic Badge - Enhanced */
|
|
319
|
+
.dynamic-badge {
|
|
320
|
+
display: inline-block;
|
|
321
|
+
background: #28a745;
|
|
322
|
+
color: white;
|
|
323
|
+
font-size: 0.65rem;
|
|
324
|
+
font-weight: 500;
|
|
325
|
+
padding: 0.15rem 0.4rem;
|
|
326
|
+
border-radius: 0.25rem;
|
|
327
|
+
margin-left: 0.5rem;
|
|
328
|
+
text-transform: uppercase;
|
|
329
|
+
letter-spacing: 0.025em;
|
|
330
|
+
vertical-align: middle;
|
|
277
331
|
}
|