@controleonline/ui-customers 1.0.81 → 1.0.82
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/publish-npm.yml +91 -0
- package/AGENTS.md +12 -6
- package/docs/technical/Client-Details-Criar-Usuario.md +113 -0
- package/package.json +5 -4
- package/src/react/components/tabs/AddressesTab.js +240 -335
- package/src/react/components/tabs/DocumentAttachments.js +270 -0
- package/src/react/components/tabs/DocumentsTab.js +355 -463
- package/src/react/components/tabs/DocumentsTab.styles.js +123 -0
- package/src/react/components/tabs/EmployeeCreateFormFields.js +203 -0
- package/src/react/components/tabs/EmployeesTab.js +17 -171
- package/src/react/components/tabs/EmployeesTab.styles.js +220 -172
- package/src/react/components/tabs/GeneralTab.js +84 -46
- package/src/react/components/tabs/GeneralTab.styles.js +81 -77
- package/src/react/components/tabs/MediaTab.js +50 -5
- package/src/react/components/tabs/PeopleCategoriesPanel.js +161 -78
- package/src/react/components/tabs/SalesmanTab.js +1 -1
- package/src/react/components/tabs/UserFormModal.js +26 -2
- package/src/react/components/tabs/UsersTab.js +30 -19
- package/src/react/components/tabs/UsersTab.styles.js +257 -257
- package/src/react/components/tabs/UsersTabApiKeyModal.js +115 -0
- package/src/react/components/tabs/UsersTabUserModal.js +222 -0
- package/src/react/components/tabs/addressesTabHelpers.js +190 -0
- package/src/react/components/tabs/documentsTabHelpers.js +142 -0
- package/src/react/components/tabs/employeeContacts.js +3 -23
- package/src/react/components/tabs/employeeUnlink.js +7 -4
- package/src/react/components/tabs/employeesTabHelpers.js +33 -9
- package/src/react/components/tabs/generalTabHelpers.js +30 -8
- package/src/react/components/tabs/useGeneralTabPeopleLink.js +68 -0
- package/src/react/components/tabs/usersTabHelpers.js +24 -23
- package/src/react/pages/details.page.styles.js +114 -100
- package/src/react/utils/humanCompanyLinkTypes.js +28 -0
- package/src/tests/browser/manager/client-details-add-user-modal.spec.js +281 -0
- package/src/tests/browser/manager/client-details-address-create-coords.spec.js +414 -0
- package/src/tests/browser/manager/client-details-address-edit.spec.js +331 -0
- package/src/tests/browser/manager/client-details-contact-edit.spec.js +198 -0
- package/src/tests/browser/manager/client-details-create-contact.spec.js +306 -0
- package/src/tests/browser/manager/client-details-documents.spec.js +346 -0
- package/src/tests/browser/manager/client-details-identity-case.spec.js +227 -0
- package/src/tests/browser/manager/client-details-people-type.manifest.md +18 -0
- package/src/tests/browser/manager/client-details-people-type.spec.js +345 -0
- package/src/tests/browser/manager/client-details-supplier-product-create.spec.js +334 -0
- package/src/tests/browser/manager/client-details-users-tab-load.spec.js +194 -0
- package/src/tests/browser/manager/employees-salesman-after-sales.spec.js +441 -0
- package/src/tests/react/components/SalesmanTab.company.test.js +40 -0
- package/src/tests/react/components/tabs/UsersTab.styles.test.js +39 -0
- package/src/tests/react/components/tabs/addressesTabHelpers.test.js +78 -0
- package/src/tests/react/components/tabs/documentsTabContract.test.mjs +37 -0
- package/src/tests/react/components/tabs/documentsTabHelpers.test.mjs +93 -0
- package/src/tests/react/components/tabs/employeeContacts.test.js +46 -4
- package/src/tests/react/components/tabs/employeeLinkTypes.contract.test.mjs +52 -0
- package/src/tests/react/components/tabs/employeeUnlink.test.js +11 -11
- package/src/tests/react/components/tabs/employeesTabHelpers.media.test.js +2 -23
- package/src/tests/react/components/tabs/employeesTabHelpers.test.js +59 -0
- package/src/tests/react/components/tabs/generalTabHelpers.test.js +35 -0
- package/src/tests/react/components/tabs/peopleMediaTabs.test.js +217 -217
- package/src/tests/react/components/tabs/peopleType.contract.test.mjs +47 -0
- package/src/tests/react/components/tabs/resolveSeededLinkType.test.mjs +20 -0
- package/src/tests/react/components/tabs/salesmanTabMedia.no-redeclare.test.mjs +33 -0
- package/src/tests/react/components/tabs/usersTabHelpers.test.js +20 -0
- package/src/tests/react/components/tabs/usersTabHelpers.test.mjs +63 -0
- package/src/tests/react/utils/humanCompanyLinkTypes.test.js +50 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Image,
|
|
5
|
+
Linking,
|
|
6
|
+
Modal,
|
|
7
|
+
Platform,
|
|
8
|
+
ScrollView,
|
|
9
|
+
Share,
|
|
10
|
+
Text,
|
|
11
|
+
TouchableOpacity,
|
|
12
|
+
View,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
15
|
+
import { useMessage } from '@controleonline/ui-common/src/react/components/MessageService';
|
|
16
|
+
import { resolveFileDownloadUrl } from '@controleonline/ui-common/src/react/utils/fileUrl';
|
|
17
|
+
import { colors } from '@controleonline/../../src/styles/colors';
|
|
18
|
+
import DefaultUpload from '@controleonline/ui-default/src/react/components/upload/DefaultUpload';
|
|
19
|
+
import {
|
|
20
|
+
extractId,
|
|
21
|
+
isImageFile,
|
|
22
|
+
isPdfFile,
|
|
23
|
+
normalizeDocumentFiles,
|
|
24
|
+
toDocumentIri,
|
|
25
|
+
ACCEPTED_DOCUMENT_FILE_TYPES,
|
|
26
|
+
} from './documentsTabHelpers';
|
|
27
|
+
import {
|
|
28
|
+
attachmentActionButtonStyle,
|
|
29
|
+
attachmentActionLabelStyle,
|
|
30
|
+
attachmentActionsStyle,
|
|
31
|
+
attachmentCardStyle,
|
|
32
|
+
attachmentHeaderStyle,
|
|
33
|
+
attachmentListStyle,
|
|
34
|
+
attachmentMetaStyle,
|
|
35
|
+
attachmentNameStyle,
|
|
36
|
+
attachmentSubtextStyle,
|
|
37
|
+
emptyAttachmentTextStyle,
|
|
38
|
+
previewBackdropStyle,
|
|
39
|
+
previewCardStyle,
|
|
40
|
+
previewContentStyle,
|
|
41
|
+
previewFallbackStyle,
|
|
42
|
+
previewFallbackTextStyle,
|
|
43
|
+
previewHeaderStyle,
|
|
44
|
+
previewImageStyle,
|
|
45
|
+
previewTitleStyle,
|
|
46
|
+
} from './DocumentsTab.styles';
|
|
47
|
+
|
|
48
|
+
const DocumentAttachments = ({
|
|
49
|
+
documentItem,
|
|
50
|
+
isEditing,
|
|
51
|
+
companyId,
|
|
52
|
+
onFilesChanged,
|
|
53
|
+
}) => {
|
|
54
|
+
const { showError, showSuccess, showDialog } = useMessage();
|
|
55
|
+
const [previewAttachment, setPreviewAttachment] = useState(null);
|
|
56
|
+
const documentId = extractId(documentItem?.id);
|
|
57
|
+
|
|
58
|
+
const attachments = useMemo(
|
|
59
|
+
() =>
|
|
60
|
+
normalizeDocumentFiles(documentItem?.raw || documentItem, resolveFileDownloadUrl).concat(
|
|
61
|
+
Array.isArray(documentItem?.files) ? documentItem.files.filter(Boolean) : [],
|
|
62
|
+
).reduce((acc, item) => {
|
|
63
|
+
const key = String(item.id || item.fileId || item.url || '');
|
|
64
|
+
if (key && !acc.some(existing => String(existing.id || existing.fileId) === key)) {
|
|
65
|
+
acc.push(item);
|
|
66
|
+
}
|
|
67
|
+
return acc;
|
|
68
|
+
}, []),
|
|
69
|
+
[documentItem],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const activePreviewUrl = previewAttachment?.url || '';
|
|
73
|
+
const canPreviewImage = isImageFile(activePreviewUrl);
|
|
74
|
+
|
|
75
|
+
const handleOpen = useCallback(async attachment => {
|
|
76
|
+
const url = attachment?.url;
|
|
77
|
+
if (!url) {
|
|
78
|
+
showError(global.t?.t('customers', 'error', 'URL do arquivo indisponível.'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
if (Platform.OS === 'web' && typeof window !== 'undefined') {
|
|
83
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const canOpen = await Linking.canOpenURL(url);
|
|
87
|
+
if (canOpen) {
|
|
88
|
+
await Linking.openURL(url);
|
|
89
|
+
} else {
|
|
90
|
+
showError(global.t?.t('customers', 'error', 'Não foi possível abrir o arquivo.'));
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
showError(global.t?.t('customers', 'error', 'Falha ao abrir o arquivo.'));
|
|
94
|
+
}
|
|
95
|
+
}, [showError]);
|
|
96
|
+
|
|
97
|
+
const handleShare = useCallback(async attachment => {
|
|
98
|
+
const url = attachment?.url;
|
|
99
|
+
if (!url) {
|
|
100
|
+
showError(global.t?.t('customers', 'error', 'URL do arquivo indisponível.'));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await Share.share({
|
|
105
|
+
message: `${attachment.name || 'Documento'}: ${url}`,
|
|
106
|
+
url,
|
|
107
|
+
title: attachment.name || 'Documento',
|
|
108
|
+
});
|
|
109
|
+
} catch {
|
|
110
|
+
showError(global.t?.t('customers', 'error', 'Falha ao compartilhar o arquivo.'));
|
|
111
|
+
}
|
|
112
|
+
}, [showError]);
|
|
113
|
+
|
|
114
|
+
const handlePrint = useCallback(attachment => {
|
|
115
|
+
const url = attachment?.url;
|
|
116
|
+
if (!url) {
|
|
117
|
+
showError(global.t?.t('customers', 'error', 'URL do arquivo indisponível.'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (Platform.OS === 'web' && typeof window !== 'undefined') {
|
|
121
|
+
const printWindow = window.open(url, '_blank', 'noopener,noreferrer');
|
|
122
|
+
if (printWindow) {
|
|
123
|
+
const triggerPrint = () => {
|
|
124
|
+
try {
|
|
125
|
+
printWindow.focus();
|
|
126
|
+
printWindow.print();
|
|
127
|
+
} catch {
|
|
128
|
+
/* browser may block auto-print; user can print manually */
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
if (printWindow.document?.readyState === 'complete') {
|
|
132
|
+
triggerPrint();
|
|
133
|
+
} else {
|
|
134
|
+
printWindow.onload = triggerPrint;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
handleOpen(attachment);
|
|
140
|
+
}, [handleOpen, showError]);
|
|
141
|
+
|
|
142
|
+
const handlePreview = useCallback(attachment => {
|
|
143
|
+
setPreviewAttachment(attachment);
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
const handleChanged = useCallback(
|
|
147
|
+
nextAttachments => {
|
|
148
|
+
if (typeof onFilesChanged === 'function') {
|
|
149
|
+
onFilesChanged(documentId, nextAttachments || []);
|
|
150
|
+
}
|
|
151
|
+
showSuccess(global.t?.t('customers', 'success', 'Anexos do documento atualizados.'));
|
|
152
|
+
},
|
|
153
|
+
[documentId, onFilesChanged, showSuccess],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (!documentId) {
|
|
157
|
+
return (
|
|
158
|
+
<Text style={emptyAttachmentTextStyle}>
|
|
159
|
+
{global.t?.t('customers', 'label', 'Salve o documento antes de anexar arquivos.')}
|
|
160
|
+
</Text>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<View style={attachmentListStyle}>
|
|
166
|
+
<View style={attachmentHeaderStyle}>
|
|
167
|
+
<Text style={attachmentSubtextStyle}>
|
|
168
|
+
{global.t?.t('customers', 'label', 'Anexos (JPG, PNG, PDF)')}
|
|
169
|
+
</Text>
|
|
170
|
+
</View>
|
|
171
|
+
|
|
172
|
+
{attachments.length === 0 ? (
|
|
173
|
+
<Text style={emptyAttachmentTextStyle}>
|
|
174
|
+
{global.t?.t('customers', 'label', 'Nenhum arquivo anexado.')}
|
|
175
|
+
</Text>
|
|
176
|
+
) : (
|
|
177
|
+
attachments.map(attachment => (
|
|
178
|
+
<View key={String(attachment.id || attachment.fileId || attachment.url)} style={attachmentCardStyle}>
|
|
179
|
+
<View style={attachmentMetaStyle}>
|
|
180
|
+
<Icon
|
|
181
|
+
name={isPdfFile(attachment.url) ? 'picture-as-pdf' : 'image'}
|
|
182
|
+
size={20}
|
|
183
|
+
color={colors.primary}
|
|
184
|
+
/>
|
|
185
|
+
<TouchableOpacity onPress={() => handlePreview(attachment)} style={{ flex: 1 }}>
|
|
186
|
+
<Text style={attachmentNameStyle} numberOfLines={1}>
|
|
187
|
+
{attachment.name}
|
|
188
|
+
</Text>
|
|
189
|
+
</TouchableOpacity>
|
|
190
|
+
</View>
|
|
191
|
+
<View style={attachmentActionsStyle}>
|
|
192
|
+
<TouchableOpacity style={attachmentActionButtonStyle} onPress={() => handlePreview(attachment)}>
|
|
193
|
+
<Icon name="visibility" size={16} color={colors.primary} />
|
|
194
|
+
<Text style={attachmentActionLabelStyle}>{global.t?.t('customers', 'label', 'Ver')}</Text>
|
|
195
|
+
</TouchableOpacity>
|
|
196
|
+
<TouchableOpacity style={attachmentActionButtonStyle} onPress={() => handleOpen(attachment)}>
|
|
197
|
+
<Icon name="open-in-new" size={16} color={colors.primary} />
|
|
198
|
+
<Text style={attachmentActionLabelStyle}>{global.t?.t('customers', 'label', 'Abrir')}</Text>
|
|
199
|
+
</TouchableOpacity>
|
|
200
|
+
<TouchableOpacity style={attachmentActionButtonStyle} onPress={() => handleShare(attachment)}>
|
|
201
|
+
<Icon name="share" size={16} color={colors.primary} />
|
|
202
|
+
<Text style={attachmentActionLabelStyle}>{global.t?.t('customers', 'label', 'Compartilhar')}</Text>
|
|
203
|
+
</TouchableOpacity>
|
|
204
|
+
<TouchableOpacity style={attachmentActionButtonStyle} onPress={() => handlePrint(attachment)}>
|
|
205
|
+
<Icon name="print" size={16} color={colors.primary} />
|
|
206
|
+
<Text style={attachmentActionLabelStyle}>{global.t?.t('customers', 'label', 'Imprimir')}</Text>
|
|
207
|
+
</TouchableOpacity>
|
|
208
|
+
</View>
|
|
209
|
+
</View>
|
|
210
|
+
))
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{isEditing ? (
|
|
214
|
+
<DefaultUpload
|
|
215
|
+
relationStoreName="document_file"
|
|
216
|
+
relationField="document"
|
|
217
|
+
relationResource={toDocumentIri(documentId)}
|
|
218
|
+
entityId={documentId}
|
|
219
|
+
companyId={companyId}
|
|
220
|
+
context="document"
|
|
221
|
+
fileType="all"
|
|
222
|
+
acceptedTypes={ACCEPTED_DOCUMENT_FILE_TYPES}
|
|
223
|
+
title={global.t?.t('customers', 'label', 'Arquivos do documento')}
|
|
224
|
+
triggerLabel={global.t?.t('customers', 'label', 'Anexar arquivo')}
|
|
225
|
+
managerTitle={global.t?.t('customers', 'label', 'Gerenciar anexos do documento')}
|
|
226
|
+
emptyAttachmentLabel={global.t?.t('customers', 'label', 'Nenhum arquivo anexado.')}
|
|
227
|
+
uploadButtonLabel={global.t?.t('customers', 'label', 'Enviar arquivo')}
|
|
228
|
+
fileTypeLabel="arquivo"
|
|
229
|
+
showInlineContent={false}
|
|
230
|
+
onChanged={handleChanged}
|
|
231
|
+
/>
|
|
232
|
+
) : null}
|
|
233
|
+
|
|
234
|
+
{previewAttachment ? (
|
|
235
|
+
<Modal visible transparent animationType="fade" onRequestClose={() => setPreviewAttachment(null)}>
|
|
236
|
+
<View style={previewBackdropStyle}>
|
|
237
|
+
<View style={previewCardStyle}>
|
|
238
|
+
<View style={previewHeaderStyle}>
|
|
239
|
+
<Text style={previewTitleStyle} numberOfLines={1}>
|
|
240
|
+
{previewAttachment.name}
|
|
241
|
+
</Text>
|
|
242
|
+
<TouchableOpacity onPress={() => setPreviewAttachment(null)}>
|
|
243
|
+
<Icon name="close" size={22} color="#64748B" />
|
|
244
|
+
</TouchableOpacity>
|
|
245
|
+
</View>
|
|
246
|
+
<ScrollView contentContainerStyle={previewContentStyle}>
|
|
247
|
+
{canPreviewImage ? (
|
|
248
|
+
<Image source={{ uri: activePreviewUrl }} style={previewImageStyle} resizeMode="contain" />
|
|
249
|
+
) : (
|
|
250
|
+
<View style={previewFallbackStyle}>
|
|
251
|
+
<Icon name="insert-drive-file" size={48} color="#94A3B8" />
|
|
252
|
+
<Text style={previewFallbackTextStyle}>
|
|
253
|
+
{global.t?.t('customers', 'label', 'Pré-visualização indisponível. Use Abrir ou Imprimir.')}
|
|
254
|
+
</Text>
|
|
255
|
+
<TouchableOpacity style={attachmentActionButtonStyle} onPress={() => handleOpen(previewAttachment)}>
|
|
256
|
+
<Icon name="open-in-new" size={16} color={colors.primary} />
|
|
257
|
+
<Text style={attachmentActionLabelStyle}>{global.t?.t('customers', 'label', 'Abrir')}</Text>
|
|
258
|
+
</TouchableOpacity>
|
|
259
|
+
</View>
|
|
260
|
+
)}
|
|
261
|
+
</ScrollView>
|
|
262
|
+
</View>
|
|
263
|
+
</View>
|
|
264
|
+
</Modal>
|
|
265
|
+
) : null}
|
|
266
|
+
</View>
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export default DocumentAttachments;
|