@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.
- package/.github/workflows/integration-source-gate.yml +68 -0
- package/.github/workflows/manager-worker.yml +19 -0
- package/AGENTS.md +46 -50
- package/docs/technical/Contrato-de-colunas.md +67 -0
- package/docs/technical/Default-Components.md +76 -0
- package/docs/technical/DefaultForm.md +80 -0
- package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
- package/docs/technical/DefaultTable.md +203 -0
- package/docs/wiki.md +1 -0
- package/package.json +22 -21
- package/scripts/validate-wiki-pointer.cjs +13 -0
- package/src/react/components/address/DefaultAddress.js +435 -0
- package/src/react/components/address/DefaultAddress.styles.js +133 -0
- package/src/react/components/address/DefaultAddressMapPane.js +55 -0
- package/src/react/components/address/LatLonReadonlyFields.js +51 -0
- package/src/react/components/address/defaultAddressHelpers.js +163 -0
- package/src/react/components/address/defaultAddressStyles.js +136 -0
- package/src/react/components/address/defaultAddressUtils.js +129 -0
- package/src/react/components/extra-data/DefaultExtraData.js +152 -0
- package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
- package/src/react/components/files/DefaultFile.js +231 -46
- package/src/react/components/files/DefaultFileColumn.js +123 -0
- package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
- package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
- package/src/react/components/filters/CompactFilterSelector.js +51 -35
- package/src/react/components/filters/DefaultColumnFilter.js +58 -41
- package/src/react/components/filters/DefaultExternalFilters.js +331 -265
- package/src/react/components/form/DefaultForm.js +99 -79
- package/src/react/components/help/DefaultTooltip.js +59 -33
- package/src/react/components/help/DefaultTooltip.styles.js +23 -3
- package/src/react/components/inputs/DefaultColorInput.js +153 -0
- package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
- package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
- package/src/react/components/inputs/DefaultInput.js +69 -5
- package/src/react/components/inputs/DefaultInput.styles.js +26 -0
- package/src/react/components/inputs/DefaultSelect.js +134 -123
- package/src/react/components/inputs/defaultInputUtils.js +69 -43
- package/src/react/components/map/DefaultMap.native.js +73 -73
- package/src/react/components/map/DefaultMap.shared.js +911 -762
- package/src/react/components/map/DefaultMap.web.js +124 -68
- package/src/react/components/table/DefaultColumnMenu.js +109 -108
- package/src/react/components/table/DefaultDebug.js +44 -37
- package/src/react/components/table/DefaultSearchModal.js +9 -1
- package/src/react/components/table/DefaultTable.grouping.js +98 -0
- package/src/react/components/table/DefaultTable.js +213 -107
- package/src/react/components/table/DefaultTable.styles.js +367 -314
- package/src/react/components/table/DefaultTableBody.js +13 -8
- package/src/react/components/table/DefaultTableCards.js +20 -1
- package/src/react/components/table/DefaultTableControls.js +180 -205
- package/src/react/components/table/DefaultTableImportModal.js +108 -0
- package/src/react/components/table/DefaultTableInput.js +1 -0
- package/src/react/components/table/DefaultTableRowActions.js +1 -1
- package/src/react/components/table/DefaultTableRows.js +224 -363
- package/src/react/components/table/DefaultTableSearch.js +28 -5
- package/src/react/components/table/DefaultTableToolbar.js +92 -3
- package/src/react/components/table/useDefaultTablePagination.js +56 -7
- package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
- package/src/react/components/upload/DefaultUpload.js +436 -0
- package/src/react/components/upload/DefaultUpload.styles.js +153 -0
- package/src/react/components/upload/DefaultUploadAttachmentsList.js +138 -0
- package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
- package/src/react/components/upload/defaultUploadHelpers.js +108 -0
- package/src/react/components/upload/defaultUploadLibrary.js +248 -0
- package/src/react/components/upload/fileUpload.js +93 -0
- package/src/react/hooks/usePostalCodeLookup.js +145 -0
- package/src/react/index.js +6 -0
- package/src/react/services/addressFormUtils.js +196 -0
- package/src/react/services/addressGeo.js +227 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +29 -12
- package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
- package/src/tests/react/components/files/DefaultFile.test.js +6 -6
- package/src/tests/react/components/form/DefaultForm.test.js +123 -0
- package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
- package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
- package/src/tests/react/components/table/DefaultTable.test.js +53 -2
- package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
- package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
- package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
- package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
- package/src/tests/react/components/upload/defaultUploadLibrary.test.js +82 -0
- package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
- package/src/tests/react/hooks/usePostalCodeLookup.test.js +40 -0
- package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
- package/src/tests/react/services/addressFormUtils.test.js +176 -0
- package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
- package/src/tests/react/services/addressGeoLookup.test.js +105 -0
- package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
- package/src/tests/react/services/smoke-637/index.html +132 -0
- 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
|
+
});
|