@controleonline/ui-default 1.0.270 → 1.0.272

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.
Files changed (89) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/manager-worker.yml +19 -0
  3. package/AGENTS.md +46 -50
  4. package/docs/technical/Contrato-de-colunas.md +67 -0
  5. package/docs/technical/Default-Components.md +76 -0
  6. package/docs/technical/DefaultForm.md +80 -0
  7. package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
  8. package/docs/technical/DefaultTable.md +203 -0
  9. package/docs/wiki.md +1 -0
  10. package/package.json +22 -21
  11. package/scripts/validate-wiki-pointer.cjs +13 -0
  12. package/src/react/components/address/DefaultAddress.js +435 -0
  13. package/src/react/components/address/DefaultAddress.styles.js +133 -0
  14. package/src/react/components/address/DefaultAddressMapPane.js +55 -0
  15. package/src/react/components/address/LatLonReadonlyFields.js +51 -0
  16. package/src/react/components/address/defaultAddressHelpers.js +163 -0
  17. package/src/react/components/address/defaultAddressStyles.js +136 -0
  18. package/src/react/components/address/defaultAddressUtils.js +129 -0
  19. package/src/react/components/extra-data/DefaultExtraData.js +152 -0
  20. package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
  21. package/src/react/components/files/DefaultFile.js +231 -46
  22. package/src/react/components/files/DefaultFileColumn.js +123 -0
  23. package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
  24. package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
  25. package/src/react/components/filters/CompactFilterSelector.js +51 -35
  26. package/src/react/components/filters/DefaultColumnFilter.js +58 -41
  27. package/src/react/components/filters/DefaultExternalFilters.js +331 -265
  28. package/src/react/components/form/DefaultForm.js +99 -79
  29. package/src/react/components/help/DefaultTooltip.js +59 -33
  30. package/src/react/components/help/DefaultTooltip.styles.js +23 -3
  31. package/src/react/components/inputs/DefaultColorInput.js +153 -0
  32. package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
  33. package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
  34. package/src/react/components/inputs/DefaultInput.js +69 -5
  35. package/src/react/components/inputs/DefaultInput.styles.js +26 -0
  36. package/src/react/components/inputs/DefaultSelect.js +134 -123
  37. package/src/react/components/inputs/defaultInputUtils.js +69 -43
  38. package/src/react/components/map/DefaultMap.native.js +73 -73
  39. package/src/react/components/map/DefaultMap.shared.js +911 -762
  40. package/src/react/components/map/DefaultMap.web.js +124 -68
  41. package/src/react/components/table/DefaultColumnMenu.js +109 -108
  42. package/src/react/components/table/DefaultDebug.js +44 -37
  43. package/src/react/components/table/DefaultSearchModal.js +9 -1
  44. package/src/react/components/table/DefaultTable.grouping.js +98 -0
  45. package/src/react/components/table/DefaultTable.js +213 -107
  46. package/src/react/components/table/DefaultTable.styles.js +367 -314
  47. package/src/react/components/table/DefaultTableBody.js +13 -8
  48. package/src/react/components/table/DefaultTableCards.js +20 -1
  49. package/src/react/components/table/DefaultTableControls.js +180 -205
  50. package/src/react/components/table/DefaultTableImportModal.js +108 -0
  51. package/src/react/components/table/DefaultTableInput.js +1 -0
  52. package/src/react/components/table/DefaultTableRowActions.js +1 -1
  53. package/src/react/components/table/DefaultTableRows.js +224 -363
  54. package/src/react/components/table/DefaultTableSearch.js +28 -5
  55. package/src/react/components/table/DefaultTableToolbar.js +92 -3
  56. package/src/react/components/table/useDefaultTablePagination.js +56 -7
  57. package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
  58. package/src/react/components/upload/DefaultUpload.js +436 -0
  59. package/src/react/components/upload/DefaultUpload.styles.js +153 -0
  60. package/src/react/components/upload/DefaultUploadAttachmentsList.js +138 -0
  61. package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
  62. package/src/react/components/upload/defaultUploadHelpers.js +108 -0
  63. package/src/react/components/upload/defaultUploadLibrary.js +248 -0
  64. package/src/react/components/upload/fileUpload.js +93 -0
  65. package/src/react/hooks/usePostalCodeLookup.js +145 -0
  66. package/src/react/index.js +6 -0
  67. package/src/react/services/addressFormUtils.js +196 -0
  68. package/src/react/services/addressGeo.js +227 -0
  69. package/src/react/utils/tableVisibleColumnsPreferences.js +29 -12
  70. package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
  71. package/src/tests/react/components/files/DefaultFile.test.js +6 -6
  72. package/src/tests/react/components/form/DefaultForm.test.js +123 -0
  73. package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
  74. package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
  75. package/src/tests/react/components/table/DefaultTable.test.js +53 -2
  76. package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
  77. package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
  78. package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
  79. package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
  80. package/src/tests/react/components/upload/defaultUploadLibrary.test.js +82 -0
  81. package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
  82. package/src/tests/react/hooks/usePostalCodeLookup.test.js +40 -0
  83. package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
  84. package/src/tests/react/services/addressFormUtils.test.js +176 -0
  85. package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
  86. package/src/tests/react/services/addressGeoLookup.test.js +105 -0
  87. package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
  88. package/src/tests/react/services/smoke-637/index.html +132 -0
  89. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +84 -48
@@ -0,0 +1,436 @@
1
+ import React, {useCallback, useEffect, useMemo, useState} from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Text,
5
+ TouchableOpacity,
6
+ View,
7
+ } from 'react-native';
8
+ import {MaterialCommunityIcons} from '@expo/vector-icons';
9
+ import {useStore} from '@store';
10
+ import DefaultUploadAttachmentsList from './DefaultUploadAttachmentsList';
11
+ import DefaultUploadManagerModal from './DefaultUploadManagerModal';
12
+ import {defaultUploadStyles as styles} from './DefaultUpload.styles';
13
+ import {selectFile, uploadFileToApi, toFileIri, extractFileId} from './fileUpload';
14
+ import {
15
+ DEFAULT_LIBRARY_CONTEXTS,
16
+ getEntityId,
17
+ normalizeAttachmentRelation,
18
+ getRelationFileId,
19
+ getFileName,
20
+ dedupeFiles,
21
+ } from './defaultUploadHelpers';
22
+ import {fetchLibraryFiles} from './defaultUploadLibrary';
23
+
24
+ const DefaultUpload = ({
25
+ relationStoreName,
26
+ relationField,
27
+ relationResource,
28
+ attachments = [],
29
+ entityId,
30
+ companyId,
31
+ company = null,
32
+ showAttachmentActions = true,
33
+ context = 'products',
34
+ libraryContexts = DEFAULT_LIBRARY_CONTEXTS,
35
+ fileStoreName = 'file',
36
+ onChanged,
37
+ coverRelationId,
38
+ onCoverChanged,
39
+ title = 'Imagens anexas',
40
+ triggerLabel = 'Gerenciar imagens',
41
+ managerTitle = 'Gerenciador de imagens',
42
+ searchPlaceholder = 'Buscar imagem',
43
+ uploadButtonLabel = 'Enviar nova',
44
+ loadingText = 'Carregando arquivos...',
45
+ emptyAttachmentLabel = 'Nenhum arquivo anexado.',
46
+ emptyLibraryLabel = 'Nenhum arquivo encontrado.',
47
+ saveBeforeLabel = 'Salve o registro antes de anexar arquivos.',
48
+ uploadSuccessMessage = 'Arquivo enviado e anexado.',
49
+ attachSuccessMessage = 'Arquivo anexado com sucesso.',
50
+ removeSuccessMessage = 'Arquivo removido.',
51
+ attachedLabel = 'Anexado',
52
+ fileType = 'image',
53
+ acceptedTypes = 'image/*',
54
+ fileTypeLabel = 'arquivo',
55
+ onBeforeOpen = null,
56
+ onUploadFile = null,
57
+ onAttachFile = null,
58
+ onRemoveAttachment = null,
59
+ renderTrigger = null,
60
+ requireEntity = true,
61
+ showInlineContent = true,
62
+ uploadResultAlreadyAttached = false,
63
+ knownFileIds = [],
64
+ }) => {
65
+ const relationStore = useStore(relationStoreName);
66
+ const fileStore = useStore(fileStoreName);
67
+ const themeStore = useStore('theme');
68
+ const relationActions = relationStore?.actions || {};
69
+ const fileActions = fileStore?.actions || {};
70
+ const themeColors = themeStore?.getters?.colors || {};
71
+
72
+ const buttonPalette = useMemo(
73
+ () => ({
74
+ buttonBackground: themeColors.buttonBackground,
75
+ buttonBorder: themeColors.buttonBorder,
76
+ buttonText: themeColors.buttonText,
77
+ buttonIcon: themeColors.buttonIcon || themeColors.buttonText,
78
+ buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
79
+ buttonBorderSecondary: themeColors.buttonBorderSecondary,
80
+ buttonTextSecondary: themeColors.buttonTextSecondary,
81
+ buttonIconSecondary: themeColors.buttonIconSecondary || themeColors.buttonTextSecondary,
82
+ iconSuccess: themeColors.iconSuccess,
83
+ iconDanger: themeColors.iconDanger,
84
+ iconActive: themeColors.iconActive,
85
+ textDanger: themeColors.textDanger,
86
+ cardBackground: themeColors.cardBackground,
87
+ cardBorder: themeColors.cardBorder,
88
+ }),
89
+ [
90
+ themeColors.buttonBackground,
91
+ themeColors.buttonBackgroundSecondary,
92
+ themeColors.buttonBorder,
93
+ themeColors.buttonBorderSecondary,
94
+ themeColors.buttonIcon,
95
+ themeColors.buttonIconSecondary,
96
+ themeColors.buttonText,
97
+ themeColors.buttonTextSecondary,
98
+ themeColors.iconActive,
99
+ themeColors.iconDanger,
100
+ themeColors.iconSuccess,
101
+ themeColors.textDanger,
102
+ themeColors.cardBackground,
103
+ themeColors.cardBorder,
104
+ ],
105
+ );
106
+
107
+ const attachmentRows = Array.isArray(attachments) ? attachments : [];
108
+ const resolvedKnownIds = useMemo(() => {
109
+ const fromProp = Array.isArray(knownFileIds) ? knownFileIds : [knownFileIds];
110
+ const fromAttachments = attachmentRows.map(getRelationFileId);
111
+ return [...fromProp, ...fromAttachments].map(extractFileId).filter(Boolean);
112
+ }, [attachmentRows, knownFileIds]);
113
+ const resolvedKnownIdsKey = resolvedKnownIds.join('|');
114
+ const libraryContextsKey = Array.isArray(libraryContexts)
115
+ ? libraryContexts.join('|')
116
+ : String(libraryContexts || '');
117
+
118
+ const [uploading, setUploading] = useState(false);
119
+ const [error, setError] = useState('');
120
+ const [status, setStatus] = useState('');
121
+ const [coverId, setCoverId] = useState(coverRelationId || null);
122
+ const [managerOpen, setManagerOpen] = useState(false);
123
+ const [libraryFiles, setLibraryFiles] = useState([]);
124
+ const [libraryLoading, setLibraryLoading] = useState(false);
125
+ const [libraryError, setLibraryError] = useState('');
126
+ const [librarySearch, setLibrarySearch] = useState('');
127
+ const [savingFileId, setSavingFileId] = useState(null);
128
+
129
+ const sortedAttachments = useMemo(() => {
130
+ if (!coverId) return attachmentRows;
131
+ const index = attachmentRows.findIndex(item => String(item.id) === String(coverId));
132
+ if (index < 0) return attachmentRows;
133
+ const copy = [...attachmentRows];
134
+ const [selected] = copy.splice(index, 1);
135
+ copy.unshift(selected);
136
+ return copy;
137
+ }, [attachmentRows, coverId]);
138
+
139
+ const attachedFileIds = useMemo(
140
+ () =>
141
+ new Set(
142
+ [...attachmentRows.map(getRelationFileId), ...resolvedKnownIds]
143
+ .filter(Boolean)
144
+ .map(String),
145
+ ),
146
+ [attachmentRows, resolvedKnownIds],
147
+ );
148
+
149
+ const filteredLibraryFiles = useMemo(() => {
150
+ const query = String(librarySearch || '').trim().toLowerCase();
151
+ if (!query) return libraryFiles;
152
+ return libraryFiles.filter(file => {
153
+ const name = getFileName(file).toLowerCase();
154
+ const fileContext = String(file?.context || '').toLowerCase();
155
+ const type = String(file?.fileType || '').toLowerCase();
156
+ const label = String(fileTypeLabel || '').toLowerCase();
157
+ return name.includes(query) || fileContext.includes(query) || type.includes(query) || label.includes(query);
158
+ });
159
+ }, [fileTypeLabel, libraryFiles, librarySearch]);
160
+
161
+ useEffect(() => {
162
+ setCoverId(coverRelationId || null);
163
+ }, [coverRelationId]);
164
+
165
+ const peopleStoreForMedia = useStore('people');
166
+ const peopleActionsForLibrary = (() => {
167
+ const fromRelation =
168
+ relationStoreName === 'people' && typeof relationActions?.getPeopleMedia === 'function'
169
+ ? relationActions
170
+ : null;
171
+ if (fromRelation) return fromRelation;
172
+ const fromPeople = peopleStoreForMedia?.actions;
173
+ if (typeof fromPeople?.getPeopleMedia === 'function') return fromPeople;
174
+ return null;
175
+ })();
176
+
177
+ const loadLibrary = useCallback(async (extraFiles = []) => {
178
+ setLibraryLoading(true);
179
+ setLibraryError('');
180
+ try {
181
+ const files = await fetchLibraryFiles({
182
+ fileActions,
183
+ companyId,
184
+ fileType,
185
+ libraryContexts: libraryContexts || DEFAULT_LIBRARY_CONTEXTS,
186
+ peopleActions: peopleActionsForLibrary,
187
+ knownFileIds: resolvedKnownIds,
188
+ });
189
+ const preferImageMeta = String(fileType || '').toLowerCase() === 'image';
190
+ const withMeta = (list) =>
191
+ (Array.isArray(list) ? list : []).map(item => {
192
+ if (!item || typeof item !== 'object') return item;
193
+ const id = extractFileId(item);
194
+ if (!id) return item;
195
+ return {
196
+ ...item,
197
+ id: item.id || id,
198
+ '@id': item['@id'] || `/files/${id}`,
199
+ context: item.context || context || 'people_media',
200
+ fileType: item.fileType || item.mimeType || (preferImageMeta ? 'image' : item.fileType),
201
+ fileName: item.fileName || item.name || item.originalName || `Arquivo ${id}`,
202
+ };
203
+ });
204
+ setLibraryFiles(
205
+ dedupeFiles(
206
+ withMeta([...(Array.isArray(extraFiles) ? extraFiles : []), ...files]),
207
+ ),
208
+ );
209
+ } catch (e) {
210
+ setLibraryError(e?.message || 'Falha ao carregar biblioteca de arquivos.');
211
+ setLibraryFiles(dedupeFiles(extraFiles));
212
+ } finally {
213
+ setLibraryLoading(false);
214
+ }
215
+ }, [companyId, fileActions, fileType, libraryContextsKey, peopleActionsForLibrary, resolvedKnownIdsKey]);
216
+
217
+ useEffect(() => {
218
+ if (managerOpen) {
219
+ if (typeof onBeforeOpen === 'function') onBeforeOpen();
220
+ void loadLibrary();
221
+ }
222
+ }, [loadLibrary, managerOpen, onBeforeOpen]);
223
+
224
+ const attachFileToEntity = useCallback(
225
+ async (fileObj, options = {}) => {
226
+ const {successMessage = attachSuccessMessage, closeManager = false} = options;
227
+ setError('');
228
+ setStatus('');
229
+ if (requireEntity && !entityId) {
230
+ setError(saveBeforeLabel);
231
+ return null;
232
+ }
233
+ const fileIri = toFileIri(fileObj);
234
+ if (!fileIri) throw new Error('Arquivo sem identificador.');
235
+ const fileId = extractFileId(fileObj);
236
+ if (fileId && attachedFileIds.has(String(fileId))) {
237
+ setStatus('Arquivo ja anexado.');
238
+ return null;
239
+ }
240
+ if (typeof onAttachFile === 'function') {
241
+ const savedRelation = await onAttachFile(fileObj);
242
+ setStatus(successMessage);
243
+ if (onChanged) await onChanged();
244
+ if (closeManager) setManagerOpen(false);
245
+ return savedRelation;
246
+ }
247
+ if (typeof relationActions.save !== 'function') {
248
+ throw new Error('Fluxo de vinculo indisponivel.');
249
+ }
250
+ try {
251
+ const savedRelation = await relationActions.save({
252
+ [relationField]: `/${relationResource}/${entityId}`,
253
+ file: fileIri,
254
+ });
255
+ setStatus(successMessage);
256
+ if (onChanged) await onChanged();
257
+ if (closeManager) setManagerOpen(false);
258
+ return savedRelation;
259
+ } catch (e) {
260
+ const message = String(e?.message || e?.response?.data?.detail || '');
261
+ if (/unique|duplicate|duplic/i.test(message)) {
262
+ setStatus('Arquivo ja anexado.');
263
+ if (onChanged) await onChanged();
264
+ return null;
265
+ }
266
+ throw e;
267
+ }
268
+ },
269
+ [attachSuccessMessage, attachedFileIds, entityId, onChanged, relationActions, relationField, relationResource, saveBeforeLabel, onAttachFile, requireEntity],
270
+ );
271
+
272
+ const handleUpload = useCallback(async () => {
273
+ setError('');
274
+ setStatus('');
275
+ if (requireEntity && !entityId) {
276
+ setError(saveBeforeLabel);
277
+ return;
278
+ }
279
+ const file = await selectFile(acceptedTypes);
280
+ if (!file) return;
281
+ try {
282
+ setUploading(true);
283
+ const uploadedFile =
284
+ typeof onUploadFile === 'function'
285
+ ? await onUploadFile({acceptedTypes, companyId, context, entityId, file})
286
+ : await uploadFileToApi({file, context, peopleId: companyId, entityId});
287
+ setLibraryFiles(current => dedupeFiles([uploadedFile, ...current]));
288
+ if (uploadResultAlreadyAttached) {
289
+ setStatus(uploadSuccessMessage);
290
+ if (onChanged) await onChanged();
291
+ await loadLibrary([uploadedFile]);
292
+ return;
293
+ }
294
+ await attachFileToEntity(uploadedFile, {successMessage: uploadSuccessMessage});
295
+ await loadLibrary([uploadedFile]);
296
+ } catch (e) {
297
+ setError(e?.message || 'Falha ao anexar arquivo.');
298
+ } finally {
299
+ setUploading(false);
300
+ }
301
+ }, [acceptedTypes, attachFileToEntity, companyId, context, entityId, loadLibrary, onChanged, onUploadFile, requireEntity, saveBeforeLabel, uploadSuccessMessage, uploadResultAlreadyAttached]);
302
+
303
+ const handleAttachExisting = useCallback(
304
+ async file => {
305
+ const fileId = extractFileId(file) || getFileName(file);
306
+ try {
307
+ setSavingFileId(fileId);
308
+ await attachFileToEntity(file, {successMessage: attachSuccessMessage, closeManager: true});
309
+ } catch (e) {
310
+ setError(e?.message || 'Falha ao anexar arquivo.');
311
+ } finally {
312
+ setSavingFileId(null);
313
+ }
314
+ },
315
+ [attachFileToEntity, attachSuccessMessage],
316
+ );
317
+
318
+ const handleRemove = useCallback(
319
+ async relation => {
320
+ try {
321
+ setError('');
322
+ setStatus('');
323
+ const attachmentRelation = normalizeAttachmentRelation(relation);
324
+ if (typeof onRemoveAttachment === 'function') {
325
+ await onRemoveAttachment(attachmentRelation);
326
+ const relationId = getEntityId(attachmentRelation);
327
+ if (String(coverId) === String(relationId)) setCoverId(null);
328
+ setStatus(removeSuccessMessage);
329
+ if (onChanged) await onChanged();
330
+ return;
331
+ }
332
+ const relationId = getEntityId(attachmentRelation);
333
+ if (!relationId) throw new Error('Anexo sem identificador para remocao.');
334
+ if (typeof relationActions.remove !== 'function') {
335
+ throw new Error('Fluxo de remocao indisponivel.');
336
+ }
337
+ await relationActions.remove(relationId);
338
+ if (String(coverId) === String(relationId)) setCoverId(null);
339
+ setStatus(removeSuccessMessage);
340
+ if (onChanged) await onChanged();
341
+ } catch (e) {
342
+ setError(e?.message || 'Falha ao remover arquivo.');
343
+ }
344
+ },
345
+ [coverId, onChanged, onRemoveAttachment, relationActions, removeSuccessMessage],
346
+ );
347
+
348
+ const handleSetCover = useCallback(
349
+ async row => {
350
+ setCoverId(row.id);
351
+ if (onCoverChanged) await onCoverChanged(row);
352
+ },
353
+ [onCoverChanged],
354
+ );
355
+
356
+ const openManager = () => setManagerOpen(true);
357
+ const triggerContent =
358
+ typeof renderTrigger === 'function'
359
+ ? renderTrigger({disabled: uploading, openManager, uploading})
360
+ : (
361
+ <TouchableOpacity
362
+ onPress={openManager}
363
+ disabled={uploading}
364
+ style={[styles.attachmentsTrigger, {backgroundColor: buttonPalette.buttonBackground, borderColor: buttonPalette.buttonBorder, borderWidth: 1}, uploading && styles.disabledButton]}>
365
+ <View style={styles.headerButtonContent}>
366
+ <MaterialCommunityIcons name="folder-image" size={16} color={buttonPalette.buttonIcon} />
367
+ <Text style={[styles.attachmentsTriggerText, {color: buttonPalette.buttonText}]}>{uploading ? 'Enviando...' : triggerLabel}</Text>
368
+ </View>
369
+ </TouchableOpacity>
370
+ );
371
+
372
+ const managerModal = (
373
+ <DefaultUploadManagerModal
374
+ visible={managerOpen}
375
+ onClose={() => setManagerOpen(false)}
376
+ managerTitle={managerTitle}
377
+ context={context}
378
+ buttonPalette={buttonPalette}
379
+ librarySearch={librarySearch}
380
+ setLibrarySearch={setLibrarySearch}
381
+ searchPlaceholder={searchPlaceholder}
382
+ handleUpload={handleUpload}
383
+ uploading={uploading}
384
+ uploadButtonLabel={uploadButtonLabel}
385
+ libraryLoading={libraryLoading}
386
+ loadLibrary={loadLibrary}
387
+ attachLabel={attachedLabel}
388
+ savingFileId={savingFileId}
389
+ loadingText={loadingText}
390
+ libraryError={libraryError}
391
+ filteredLibraryFiles={filteredLibraryFiles}
392
+ attachedFileIds={attachedFileIds}
393
+ handleAttachExisting={handleAttachExisting}
394
+ attachmentRows={attachmentRows}
395
+ sortedAttachments={sortedAttachments}
396
+ coverId={coverId}
397
+ handleSetCover={handleSetCover}
398
+ handleRemove={handleRemove}
399
+ emptyLibraryLabel={emptyLibraryLabel}
400
+ emptyAttachmentsLabel={emptyAttachmentLabel}
401
+ status={status}
402
+ error={error}
403
+ fileType={fileType}
404
+ company={company || (companyId ? {id: companyId} : null)}
405
+ />
406
+ );
407
+
408
+ if (!showInlineContent) {
409
+ return (
410
+ <View>
411
+ {triggerContent}
412
+ {managerModal}
413
+ </View>
414
+ );
415
+ }
416
+
417
+ return (
418
+ <DefaultUploadAttachmentsList
419
+ title={title}
420
+ triggerContent={triggerContent}
421
+ status={status}
422
+ error={error}
423
+ sortedAttachments={sortedAttachments}
424
+ emptyAttachmentLabel={emptyAttachmentLabel}
425
+ coverId={coverId}
426
+ handleSetCover={handleSetCover}
427
+ handleRemove={handleRemove}
428
+ buttonPalette={buttonPalette}
429
+ managerModal={managerModal}
430
+ company={company || (companyId ? {id: companyId} : null)}
431
+ showAttachmentActions={showAttachmentActions}
432
+ />
433
+ );
434
+ };
435
+
436
+ export default DefaultUpload;
@@ -0,0 +1,153 @@
1
+ export const defaultUploadStyles = {
2
+ disabledButton: {opacity: 0.65},
3
+ headerButtonContent: {flexDirection: 'row', alignItems: 'center', gap: 6},
4
+ modalContainer: {
5
+ backgroundColor: '#fff',
6
+ borderTopLeftRadius: 18,
7
+ borderTopRightRadius: 18,
8
+ paddingBottom: 24,
9
+ maxHeight: '86%',
10
+ },
11
+ modalHeader: {
12
+ flexDirection: 'row',
13
+ justifyContent: 'space-between',
14
+ alignItems: 'flex-start',
15
+ paddingHorizontal: 18,
16
+ paddingTop: 18,
17
+ paddingBottom: 12,
18
+ borderBottomWidth: 1,
19
+ borderBottomColor: '#E2E8F0',
20
+ },
21
+ modalTitle: {color: '#0F172A', fontSize: 17, fontWeight: '800'},
22
+ modalSubtitle: {color: '#64748B', fontSize: 12, marginTop: 3},
23
+ iconButton: {padding: 4},
24
+ toolbar: {flexDirection: 'row', alignItems: 'center', gap: 8, padding: 14},
25
+ searchBox: {
26
+ flex: 1,
27
+ minHeight: 42,
28
+ borderWidth: 1,
29
+ borderColor: '#E2E8F0',
30
+ borderRadius: 10,
31
+ paddingHorizontal: 10,
32
+ flexDirection: 'row',
33
+ alignItems: 'center',
34
+ gap: 8,
35
+ backgroundColor: '#F8FAFC',
36
+ },
37
+ searchInput: {flex: 1, minWidth: 80, color: '#0F172A', fontSize: 14},
38
+ uploadButton: {
39
+ minHeight: 42,
40
+ borderRadius: 10,
41
+ backgroundColor: '#0F172A',
42
+ paddingHorizontal: 12,
43
+ flexDirection: 'row',
44
+ alignItems: 'center',
45
+ gap: 6,
46
+ },
47
+ uploadButtonText: {color: '#fff', fontWeight: '700', fontSize: 13},
48
+ refreshButton: {
49
+ width: 42,
50
+ height: 42,
51
+ borderRadius: 10,
52
+ borderWidth: 1,
53
+ borderColor: '#E2E8F0',
54
+ alignItems: 'center',
55
+ justifyContent: 'center',
56
+ backgroundColor: '#fff',
57
+ },
58
+ modalError: {color: '#B91C1C', fontSize: 13, marginHorizontal: 16, marginBottom: 8},
59
+ loadingState: {minHeight: 170, alignItems: 'center', justifyContent: 'center', gap: 8},
60
+ loadingText: {color: '#64748B', fontSize: 13},
61
+ emptyState: {minHeight: 170, alignItems: 'center', justifyContent: 'center', padding: 20, gap: 8},
62
+ emptyText: {color: '#64748B', fontSize: 13, textAlign: 'center'},
63
+ modalList: {maxHeight: 520},
64
+ libraryGrid: {flexDirection: 'row', flexWrap: 'wrap', gap: 10, paddingHorizontal: 14, paddingBottom: 18},
65
+ fileCard: {
66
+ width: 176,
67
+ borderWidth: 1,
68
+ borderColor: '#E2E8F0',
69
+ borderRadius: 8,
70
+ backgroundColor: '#fff',
71
+ overflow: 'hidden',
72
+ },
73
+ fileCardAttached: {borderColor: '#86EFAC', backgroundColor: '#F0FDF4'},
74
+ fileThumb: {height: 112, backgroundColor: '#F1F5F9', alignItems: 'center', justifyContent: 'center'},
75
+ fileThumbFallback: {alignItems: 'center', justifyContent: 'center', gap: 6, height: '100%', width: '100%', backgroundColor: '#EEF2FF'},
76
+ fileExtBadge: {
77
+ color: '#312E81',
78
+ fontSize: 11,
79
+ fontWeight: '800',
80
+ letterSpacing: 0.6,
81
+ backgroundColor: '#E0E7FF',
82
+ paddingHorizontal: 8,
83
+ paddingVertical: 3,
84
+ borderRadius: 999,
85
+ overflow: 'hidden',
86
+ },
87
+ fileImage: {width: '100%', height: '100%'},
88
+ fileInfo: {padding: 9, minHeight: 74},
89
+ fileName: {color: '#0F172A', fontSize: 12, fontWeight: '700', lineHeight: 16},
90
+ mediaTypesLine: {color: '#64748B', fontSize: 11, fontWeight: '600', lineHeight: 14, marginTop: 3},
91
+ fileMetaRow: {flexDirection: 'row', flexWrap: 'wrap', gap: 5, marginTop: 7},
92
+ contextBadge: {
93
+ color: '#475569',
94
+ backgroundColor: '#E2E8F0',
95
+ borderRadius: 999,
96
+ overflow: 'hidden',
97
+ paddingHorizontal: 7,
98
+ paddingVertical: 3,
99
+ fontSize: 10,
100
+ fontWeight: '700',
101
+ },
102
+ attachedBadge: {
103
+ color: '#15803D',
104
+ backgroundColor: '#DCFCE7',
105
+ borderRadius: 999,
106
+ overflow: 'hidden',
107
+ paddingHorizontal: 7,
108
+ paddingVertical: 3,
109
+ fontSize: 10,
110
+ fontWeight: '700',
111
+ },
112
+ fileAction: {
113
+ position: 'absolute',
114
+ top: 8,
115
+ right: 8,
116
+ width: 30,
117
+ height: 30,
118
+ borderRadius: 15,
119
+ backgroundColor: 'rgba(255,255,255,0.92)',
120
+ alignItems: 'center',
121
+ justifyContent: 'center',
122
+ },
123
+ attachmentsTitleRow: {width: '100%', marginTop: 8},
124
+ attachmentsHeader: {flexDirection: 'row', alignItems: 'center', marginBottom: 8},
125
+ attachmentsTitle: {flex: 1, fontWeight: '600'},
126
+ attachmentsTrigger: {backgroundColor: '#000', paddingVertical: 8, paddingHorizontal: 12, borderRadius: 6},
127
+ attachmentsTriggerText: {color: '#fff'},
128
+ attachmentsStatus: {color: '#1b7f34', marginBottom: 6},
129
+ attachmentsError: {color: '#b00020', marginBottom: 6},
130
+ attachmentsEmpty: {borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 10},
131
+ attachmentsEmptyText: {color: '#666'},
132
+ attachmentsList: {flexDirection: 'row', gap: 10},
133
+ attachmentCard: {width: 170, borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 8, backgroundColor: '#fff'},
134
+ attachmentThumb: {height: 120, backgroundColor: '#f5f5f5', borderRadius: 6, overflow: 'hidden', marginBottom: 8, alignItems: 'center', justifyContent: 'center'},
135
+ attachmentImage: {width: '100%', height: '100%'},
136
+ attachmentActionsRow: {flexDirection: 'row', justifyContent: 'center', alignItems: 'center', gap: 8, marginTop: 2},
137
+ attachmentIconButton: {width: 36, height: 36, borderRadius: 8, alignItems: 'center', justifyContent: 'center'},
138
+ attachmentRemoveButton: {backgroundColor: '#b00020', paddingVertical: 6, borderRadius: 4},
139
+ attachmentRemoveText: {textAlign: 'center', color: '#fff', fontSize: 12},
140
+ };
141
+
142
+ export const getAttachmentCoverButtonStyle = (coverId, row) => ({
143
+ backgroundColor: String(coverId) === String(row.id) ? '#00695c' : '#efefef',
144
+ paddingVertical: 6,
145
+ borderRadius: 4,
146
+ marginBottom: 6,
147
+ });
148
+
149
+ export const getAttachmentCoverTextStyle = (coverId, row) => ({
150
+ textAlign: 'center',
151
+ color: String(coverId) === String(row.id) ? '#fff' : '#111',
152
+ fontSize: 12,
153
+ });