@visns-studio/visns-components 5.11.6 → 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 +580 -40
- package/src/components/crm/styles/Field.module.scss +301 -0
- package/src/components/proposal/ProposalTemplatePreview.css +92 -0
- package/src/components/proposal/ProposalTemplatePreview.jsx +7 -1
- package/src/components/proposal/ProposalTemplateSectionManager.css +69 -10
- package/src/components/proposal/ProposalTemplateSectionManager.jsx +33 -7
- package/src/components/proposal/SectionEditor.css +62 -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
package/package.json
CHANGED
|
@@ -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.8",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
|
@@ -841,6 +841,88 @@ function Field({
|
|
|
841
841
|
</div>
|
|
842
842
|
);
|
|
843
843
|
case 'file':
|
|
844
|
+
const isImageFile = (file) => {
|
|
845
|
+
if (!file) return false;
|
|
846
|
+
|
|
847
|
+
// Handle different file object structures
|
|
848
|
+
const fileName = file.filename || file.name || file.file_name || '';
|
|
849
|
+
const fileExtension = file.file_extension || '';
|
|
850
|
+
const mimeType = file.type || file.mime_type || file.mimeType || '';
|
|
851
|
+
|
|
852
|
+
// Check by file extension property (from API)
|
|
853
|
+
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
|
|
854
|
+
if (fileExtension && imageExtensions.includes(fileExtension.toLowerCase())) return true;
|
|
855
|
+
|
|
856
|
+
// Check by filename extension
|
|
857
|
+
const imageExtensionRegex = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i;
|
|
858
|
+
if (imageExtensionRegex.test(fileName)) return true;
|
|
859
|
+
|
|
860
|
+
// Check by MIME type
|
|
861
|
+
if (mimeType && mimeType.startsWith('image/')) return true;
|
|
862
|
+
|
|
863
|
+
// If file is just a string (filename only), check extension
|
|
864
|
+
if (typeof file === 'string' && imageExtensionRegex.test(file)) return true;
|
|
865
|
+
|
|
866
|
+
return false;
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
const getFileIcon = (file) => {
|
|
870
|
+
if (isImageFile(file)) {
|
|
871
|
+
return '🖼️';
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Handle different file structures and string filenames
|
|
875
|
+
const fileName = typeof file === 'string' ? file : (file.filename || file.name || file.file_name || '');
|
|
876
|
+
const ext = fileName.split('.').pop()?.toLowerCase();
|
|
877
|
+
|
|
878
|
+
switch (ext) {
|
|
879
|
+
case 'pdf': return '📄';
|
|
880
|
+
case 'doc':
|
|
881
|
+
case 'docx': return '📝';
|
|
882
|
+
case 'xls':
|
|
883
|
+
case 'xlsx': return '📊';
|
|
884
|
+
case 'ppt':
|
|
885
|
+
case 'pptx': return '📽️';
|
|
886
|
+
case 'zip':
|
|
887
|
+
case 'rar': return '🗜️';
|
|
888
|
+
case 'mp4':
|
|
889
|
+
case 'avi':
|
|
890
|
+
case 'mov': return '🎥';
|
|
891
|
+
case 'mp3':
|
|
892
|
+
case 'wav': return '🎵';
|
|
893
|
+
default: return '📎';
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
const getImageSrc = (file) => {
|
|
898
|
+
if (!file || !isImageFile(file)) return null;
|
|
899
|
+
|
|
900
|
+
// Handle different image source formats - API file objects
|
|
901
|
+
if (file.file_url) return file.file_url; // S3 or API URLs
|
|
902
|
+
if (file.imageSrc) return file.imageSrc;
|
|
903
|
+
if (file.url) return file.url;
|
|
904
|
+
if (file.src) return file.src;
|
|
905
|
+
if (file.path) return file.path;
|
|
906
|
+
if (file.preview) return file.preview;
|
|
907
|
+
|
|
908
|
+
// Handle full path construction
|
|
909
|
+
if (file.file_full_path) return file.file_full_path;
|
|
910
|
+
if (file.file_path) return file.file_path;
|
|
911
|
+
|
|
912
|
+
// Handle base64 data
|
|
913
|
+
if (file.data && typeof file.data === 'string' && file.data.startsWith('data:image/')) {
|
|
914
|
+
return file.data;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Handle file download URLs (legacy support)
|
|
918
|
+
if (file.key && file.uuid) {
|
|
919
|
+
// This could be constructed based on your API structure
|
|
920
|
+
return `/api/files/download/${file.uuid}/${file.key}`;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
return null;
|
|
924
|
+
};
|
|
925
|
+
|
|
844
926
|
if (
|
|
845
927
|
settings.hasOwnProperty('multiple') &&
|
|
846
928
|
settings.multiple === true
|
|
@@ -857,73 +939,109 @@ function Field({
|
|
|
857
939
|
|
|
858
940
|
{inputValue && inputValue.length > 0 && (
|
|
859
941
|
<ul className={styles['file-list']}>
|
|
860
|
-
{inputValue.map((file, index) =>
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
942
|
+
{inputValue.map((file, index) => {
|
|
943
|
+
const fileIsImage = isImageFile(file);
|
|
944
|
+
const fileImageSrc = getImageSrc(file);
|
|
945
|
+
|
|
946
|
+
return (
|
|
947
|
+
<li
|
|
948
|
+
key={`file-${settings.id}-${index}`}
|
|
949
|
+
className={fileIsImage ? styles['file-list-item-image'] : styles['file-list-item']}
|
|
866
950
|
>
|
|
867
|
-
{
|
|
868
|
-
file
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
951
|
+
{fileIsImage && fileImageSrc ? (
|
|
952
|
+
<div className={styles['file-image-preview']}>
|
|
953
|
+
<img
|
|
954
|
+
src={fileImageSrc}
|
|
955
|
+
alt={file.name || file.file_name || 'Preview'}
|
|
956
|
+
className={styles['file-thumbnail']}
|
|
957
|
+
onError={(e) => {
|
|
958
|
+
e.target.style.display = 'none';
|
|
959
|
+
e.target.nextElementSibling.style.display = 'flex';
|
|
960
|
+
}}
|
|
961
|
+
/>
|
|
962
|
+
<span
|
|
963
|
+
className={styles['file-icon']}
|
|
964
|
+
style={{ display: 'none' }}
|
|
965
|
+
>
|
|
966
|
+
{getFileIcon(file)}
|
|
967
|
+
</span>
|
|
968
|
+
</div>
|
|
969
|
+
) : (
|
|
970
|
+
<span className={styles['file-icon']}>
|
|
971
|
+
{getFileIcon(file)}
|
|
972
|
+
</span>
|
|
973
|
+
)}
|
|
974
|
+
|
|
975
|
+
<span className={styles['file-name']}>
|
|
976
|
+
{file.name ||
|
|
977
|
+
file.file_name ||
|
|
978
|
+
''}
|
|
979
|
+
</span>
|
|
980
|
+
|
|
981
|
+
<button
|
|
982
|
+
className={styles['trash-button']}
|
|
983
|
+
onClick={(e) => {
|
|
984
|
+
e.preventDefault();
|
|
985
|
+
e.stopPropagation();
|
|
986
|
+
setFormData(
|
|
987
|
+
(prevFormData) => ({
|
|
988
|
+
...prevFormData,
|
|
989
|
+
[settings.id]:
|
|
990
|
+
inputValue.filter(
|
|
991
|
+
(
|
|
992
|
+
_,
|
|
993
|
+
fileIndex
|
|
994
|
+
) =>
|
|
995
|
+
fileIndex !==
|
|
996
|
+
index
|
|
997
|
+
),
|
|
998
|
+
})
|
|
999
|
+
);
|
|
1000
|
+
}}
|
|
1001
|
+
>
|
|
1002
|
+
<Trash2
|
|
1003
|
+
strokeWidth={2}
|
|
1004
|
+
size={20}
|
|
1005
|
+
/>
|
|
1006
|
+
</button>
|
|
1007
|
+
</li>
|
|
1008
|
+
);
|
|
1009
|
+
})}
|
|
901
1010
|
</ul>
|
|
902
1011
|
)}
|
|
903
1012
|
</div>
|
|
904
1013
|
);
|
|
905
1014
|
} else {
|
|
1015
|
+
const isImage = isImageFile(inputValue);
|
|
1016
|
+
const imageSrc = getImageSrc(inputValue);
|
|
1017
|
+
|
|
1018
|
+
// Debug logging to understand inputValue structure
|
|
1019
|
+
if (inputValue && process.env.NODE_ENV === 'development') {
|
|
1020
|
+
console.log('File upload inputValue:', inputValue, 'isImage:', isImage, 'imageSrc:', imageSrc);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
906
1023
|
return (
|
|
907
1024
|
<div
|
|
908
|
-
|
|
909
|
-
display: 'inline-flex',
|
|
910
|
-
alignItems: 'center',
|
|
911
|
-
}}
|
|
1025
|
+
className={`${styles['single-file-container']} ${isImage ? styles['single-file-image'] : ''}`}
|
|
912
1026
|
>
|
|
913
1027
|
<label
|
|
914
1028
|
htmlFor={settings.id}
|
|
915
|
-
|
|
916
|
-
padding: '6px 12px',
|
|
917
|
-
border: '1px solid #ccc',
|
|
918
|
-
display: 'inline-block',
|
|
919
|
-
backgroundColor: '#f8f8f8',
|
|
920
|
-
cursor: 'pointer',
|
|
921
|
-
}}
|
|
1029
|
+
className={`${styles['file-upload-button']} ${isImage ? styles['file-upload-button-image'] : ''}`}
|
|
922
1030
|
>
|
|
923
|
-
{
|
|
924
|
-
?
|
|
925
|
-
|
|
1031
|
+
<span className={styles['file-button-icon']}>
|
|
1032
|
+
{inputValue ? getFileIcon(inputValue) : '📁'}
|
|
1033
|
+
</span>
|
|
1034
|
+
<span className={styles['file-button-text']}>
|
|
1035
|
+
{inputValue?.filename || inputValue?.name
|
|
1036
|
+
? isImage
|
|
1037
|
+
? 'Replace Image...'
|
|
1038
|
+
: 'Replace File...'
|
|
1039
|
+
: isImage
|
|
1040
|
+
? 'Choose Image...'
|
|
1041
|
+
: 'Choose File...'}
|
|
1042
|
+
</span>
|
|
926
1043
|
</label>
|
|
1044
|
+
|
|
927
1045
|
<input
|
|
928
1046
|
id={settings.id}
|
|
929
1047
|
data-name={settings.id}
|
|
@@ -933,35 +1051,44 @@ function Field({
|
|
|
933
1051
|
onChange={onChange}
|
|
934
1052
|
style={{ display: 'none' }}
|
|
935
1053
|
/>
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
inputValue.filename
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1054
|
+
|
|
1055
|
+
{inputValue && (
|
|
1056
|
+
<div className={styles['file-info']}>
|
|
1057
|
+
{isImage && imageSrc && (
|
|
1058
|
+
<div className={styles['image-preview-container']}>
|
|
1059
|
+
<img
|
|
1060
|
+
src={imageSrc}
|
|
1061
|
+
alt={inputValue.filename || inputValue.name || 'Preview'}
|
|
1062
|
+
className={styles['image-preview']}
|
|
1063
|
+
onError={(e) => {
|
|
1064
|
+
e.target.style.display = 'none';
|
|
1065
|
+
console.warn('Failed to load image preview:', imageSrc);
|
|
1066
|
+
}}
|
|
1067
|
+
/>
|
|
1068
|
+
</div>
|
|
1069
|
+
)}
|
|
1070
|
+
|
|
1071
|
+
<div className={styles['file-details']}>
|
|
1072
|
+
<span className={styles['file-name-display']}>
|
|
1073
|
+
{inputValue.filename || inputValue.name || ''}
|
|
1074
|
+
</span>
|
|
1075
|
+
|
|
1076
|
+
{inputValue.key && inputValue.uuid && (
|
|
1077
|
+
<button
|
|
1078
|
+
className={styles['download-button']}
|
|
1079
|
+
data-tooltip-id="system-tooltip"
|
|
1080
|
+
data-tooltip-content={`Download ${inputValue.filename || inputValue.name}`}
|
|
1081
|
+
onClick={(e) => {
|
|
1082
|
+
e.preventDefault();
|
|
1083
|
+
onFileDownload(inputValue);
|
|
1084
|
+
}}
|
|
1085
|
+
>
|
|
1086
|
+
<Download size={20} />
|
|
1087
|
+
</button>
|
|
1088
|
+
)}
|
|
963
1089
|
</div>
|
|
964
|
-
|
|
1090
|
+
</div>
|
|
1091
|
+
)}
|
|
965
1092
|
</div>
|
|
966
1093
|
);
|
|
967
1094
|
}
|