@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.
Files changed (62) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/publish-npm.yml +91 -0
  3. package/AGENTS.md +12 -6
  4. package/docs/technical/Client-Details-Criar-Usuario.md +113 -0
  5. package/package.json +5 -4
  6. package/src/react/components/tabs/AddressesTab.js +240 -335
  7. package/src/react/components/tabs/DocumentAttachments.js +270 -0
  8. package/src/react/components/tabs/DocumentsTab.js +355 -463
  9. package/src/react/components/tabs/DocumentsTab.styles.js +123 -0
  10. package/src/react/components/tabs/EmployeeCreateFormFields.js +203 -0
  11. package/src/react/components/tabs/EmployeesTab.js +17 -171
  12. package/src/react/components/tabs/EmployeesTab.styles.js +220 -172
  13. package/src/react/components/tabs/GeneralTab.js +84 -46
  14. package/src/react/components/tabs/GeneralTab.styles.js +81 -77
  15. package/src/react/components/tabs/MediaTab.js +50 -5
  16. package/src/react/components/tabs/PeopleCategoriesPanel.js +161 -78
  17. package/src/react/components/tabs/SalesmanTab.js +1 -1
  18. package/src/react/components/tabs/UserFormModal.js +26 -2
  19. package/src/react/components/tabs/UsersTab.js +30 -19
  20. package/src/react/components/tabs/UsersTab.styles.js +257 -257
  21. package/src/react/components/tabs/UsersTabApiKeyModal.js +115 -0
  22. package/src/react/components/tabs/UsersTabUserModal.js +222 -0
  23. package/src/react/components/tabs/addressesTabHelpers.js +190 -0
  24. package/src/react/components/tabs/documentsTabHelpers.js +142 -0
  25. package/src/react/components/tabs/employeeContacts.js +3 -23
  26. package/src/react/components/tabs/employeeUnlink.js +7 -4
  27. package/src/react/components/tabs/employeesTabHelpers.js +33 -9
  28. package/src/react/components/tabs/generalTabHelpers.js +30 -8
  29. package/src/react/components/tabs/useGeneralTabPeopleLink.js +68 -0
  30. package/src/react/components/tabs/usersTabHelpers.js +24 -23
  31. package/src/react/pages/details.page.styles.js +114 -100
  32. package/src/react/utils/humanCompanyLinkTypes.js +28 -0
  33. package/src/tests/browser/manager/client-details-add-user-modal.spec.js +281 -0
  34. package/src/tests/browser/manager/client-details-address-create-coords.spec.js +414 -0
  35. package/src/tests/browser/manager/client-details-address-edit.spec.js +331 -0
  36. package/src/tests/browser/manager/client-details-contact-edit.spec.js +198 -0
  37. package/src/tests/browser/manager/client-details-create-contact.spec.js +306 -0
  38. package/src/tests/browser/manager/client-details-documents.spec.js +346 -0
  39. package/src/tests/browser/manager/client-details-identity-case.spec.js +227 -0
  40. package/src/tests/browser/manager/client-details-people-type.manifest.md +18 -0
  41. package/src/tests/browser/manager/client-details-people-type.spec.js +345 -0
  42. package/src/tests/browser/manager/client-details-supplier-product-create.spec.js +334 -0
  43. package/src/tests/browser/manager/client-details-users-tab-load.spec.js +194 -0
  44. package/src/tests/browser/manager/employees-salesman-after-sales.spec.js +441 -0
  45. package/src/tests/react/components/SalesmanTab.company.test.js +40 -0
  46. package/src/tests/react/components/tabs/UsersTab.styles.test.js +39 -0
  47. package/src/tests/react/components/tabs/addressesTabHelpers.test.js +78 -0
  48. package/src/tests/react/components/tabs/documentsTabContract.test.mjs +37 -0
  49. package/src/tests/react/components/tabs/documentsTabHelpers.test.mjs +93 -0
  50. package/src/tests/react/components/tabs/employeeContacts.test.js +46 -4
  51. package/src/tests/react/components/tabs/employeeLinkTypes.contract.test.mjs +52 -0
  52. package/src/tests/react/components/tabs/employeeUnlink.test.js +11 -11
  53. package/src/tests/react/components/tabs/employeesTabHelpers.media.test.js +2 -23
  54. package/src/tests/react/components/tabs/employeesTabHelpers.test.js +59 -0
  55. package/src/tests/react/components/tabs/generalTabHelpers.test.js +35 -0
  56. package/src/tests/react/components/tabs/peopleMediaTabs.test.js +217 -217
  57. package/src/tests/react/components/tabs/peopleType.contract.test.mjs +47 -0
  58. package/src/tests/react/components/tabs/resolveSeededLinkType.test.mjs +20 -0
  59. package/src/tests/react/components/tabs/salesmanTabMedia.no-redeclare.test.mjs +33 -0
  60. package/src/tests/react/components/tabs/usersTabHelpers.test.js +20 -0
  61. package/src/tests/react/components/tabs/usersTabHelpers.test.mjs +63 -0
  62. package/src/tests/react/utils/humanCompanyLinkTypes.test.js +50 -0
@@ -0,0 +1,222 @@
1
+ import React from 'react';
2
+ import {
3
+ Text,
4
+ TouchableOpacity,
5
+ ScrollView,
6
+ TextInput,
7
+ View,
8
+ Keyboard,
9
+ } from 'react-native';
10
+ import AnimatedModal from '@controleonline/ui-common/src/react/components/AnimatedModal';
11
+ import FeatherIcon from 'react-native-vector-icons/Feather';
12
+ import Icon from 'react-native-vector-icons/MaterialIcons';
13
+ import {
14
+ inlineStyle_174_6,
15
+ inlineStyle_175_12,
16
+ inlineStyle_188_14,
17
+ inlineStyle_197_16,
18
+ inlineStyle_200_49,
19
+ inlineStyle_208_10,
20
+ inlineStyle_213_20,
21
+ inlineStyle_214_22,
22
+ inlineStyle_216_18,
23
+ inlineStyle_224_20,
24
+ inlineStyle_225_22,
25
+ inlineStyle_226_22,
26
+ inlineStyle_228_20,
27
+ inlineStyle_239_20,
28
+ inlineStyle_248_20,
29
+ inlineStyle_249_22,
30
+ inlineStyle_250_22,
31
+ inlineStyle_252_20,
32
+ inlineStyle_263_20,
33
+ inlineStyle_275_20,
34
+ inlineStyle_276_22,
35
+ inlineStyle_278_18,
36
+ inlineStyle_288_20,
37
+ inlineStyle_289_22,
38
+ inlineStyle_290_22,
39
+ inlineStyle_292_20,
40
+ inlineStyle_303_20,
41
+ inlineStyle_312_20,
42
+ inlineStyle_313_22,
43
+ inlineStyle_314_22,
44
+ inlineStyle_316_20,
45
+ inlineStyle_327_20,
46
+ inlineStyle_339_16,
47
+ inlineStyle_341_14,
48
+ inlineStyle_348_20,
49
+ } from './UsersTab.styles';
50
+
51
+ const UsersTabUserModal = ({
52
+ visible,
53
+ onClose,
54
+ editingItem,
55
+ formData,
56
+ setFormData,
57
+ showPassword,
58
+ setShowPassword,
59
+ showConfirmPassword,
60
+ setShowConfirmPassword,
61
+ customStyles,
62
+ onSave,
63
+ }) => (
64
+ ) => (
65
+ <AnimatedModal
66
+ visible={visible}
67
+ onRequestClose={onClose}
68
+ style={inlineStyle_174_6}>
69
+ <View style={inlineStyle_175_12}>
70
+ {/* Header */}
71
+ <View style={inlineStyle_188_14}>
72
+ <Text style={inlineStyle_197_16}>
73
+ {editingItem ? 'Editar Senha do Usuário' : 'Adicionar Usuário'}
74
+ </Text>
75
+ <TouchableOpacity onPress={onClose} style={inlineStyle_200_49}>
76
+ <Icon name="close" size={20} color="#64748B" />
77
+ </TouchableOpacity>
78
+ </View>
79
+
80
+ <ScrollView
81
+ style={inlineStyle_208_10}
82
+ keyboardShouldPersistTaps="handled"
83
+ keyboardDismissMode="on-drag">
84
+ {editingItem ? (
85
+ <View>
86
+ <View style={inlineStyle_213_20}>
87
+ <Text style={inlineStyle_214_22}>Usuário</Text>
88
+ <TextInput
89
+ style={inlineStyle_216_18}
90
+ value={formData.username}
91
+ editable={false}
92
+ />
93
+ </View>
94
+ <View style={inlineStyle_224_20}>
95
+ <Text style={inlineStyle_225_22}>Nova senha</Text>
96
+ <View style={inlineStyle_226_22}>
97
+ <TextInput
98
+ style={inlineStyle_228_20}
99
+ placeholder="Nova senha"
100
+ value={formData.password}
101
+ onChangeText={text => setFormData({ ...formData, password: text })}
102
+ secureTextEntry={!showPassword}
103
+ />
104
+ <TouchableOpacity
105
+ onPress={() => setShowPassword(prev => !prev)}
106
+ style={inlineStyle_239_20}>
107
+ <Icon
108
+ name={showPassword ? 'visibility-off' : 'visibility'}
109
+ size={20}
110
+ color="#6c757d"
111
+ />
112
+ </TouchableOpacity>
113
+ </View>
114
+ </View>
115
+ <View style={inlineStyle_248_20}>
116
+ <Text style={inlineStyle_249_22}>Confirmar nova senha</Text>
117
+ <View style={inlineStyle_250_22}>
118
+ <TextInput
119
+ style={inlineStyle_252_20}
120
+ placeholder="Confirmar nova senha"
121
+ value={formData.confirmPassword}
122
+ onChangeText={text => setFormData({ ...formData, confirmPassword: text })}
123
+ secureTextEntry={!showConfirmPassword}
124
+ />
125
+ <TouchableOpacity
126
+ onPress={() => setShowConfirmPassword(prev => !prev)}
127
+ style={inlineStyle_263_20}>
128
+ <Icon
129
+ name={showConfirmPassword ? 'visibility-off' : 'visibility'}
130
+ size={20}
131
+ color="#6c757d"
132
+ />
133
+ </TouchableOpacity>
134
+ </View>
135
+ </View>
136
+ </View>
137
+ ) : (
138
+ <View>
139
+ <View style={inlineStyle_275_20}>
140
+ <Text style={inlineStyle_276_22}>Usuário</Text>
141
+ <TextInput
142
+ style={inlineStyle_278_18}
143
+ placeholder="Nome de usuário"
144
+ value={formData.username}
145
+ onChangeText={text => setFormData({ ...formData, username: text })}
146
+ autoCapitalize="none"
147
+ />
148
+ </View>
149
+ <View style={inlineStyle_288_20}>
150
+ <Text style={inlineStyle_289_22}>Senha</Text>
151
+ <View style={inlineStyle_290_22}>
152
+ <TextInput
153
+ style={inlineStyle_292_20}
154
+ placeholder="Senha"
155
+ value={formData.password}
156
+ onChangeText={text => setFormData({ ...formData, password: text })}
157
+ secureTextEntry={!showPassword}
158
+ />
159
+ <TouchableOpacity
160
+ onPress={() => setShowPassword(prev => !prev)}
161
+ style={inlineStyle_303_20}>
162
+ <Icon
163
+ name={showPassword ? 'visibility-off' : 'visibility'}
164
+ size={20}
165
+ color="#6c757d"
166
+ />
167
+ </TouchableOpacity>
168
+ </View>
169
+ </View>
170
+ <View style={inlineStyle_312_20}>
171
+ <Text style={inlineStyle_313_22}>Confirmar Senha</Text>
172
+ <View style={inlineStyle_314_22}>
173
+ <TextInput
174
+ style={inlineStyle_316_20}
175
+ placeholder="Confirmar senha"
176
+ value={formData.confirmPassword}
177
+ onChangeText={text => setFormData({ ...formData, confirmPassword: text })}
178
+ secureTextEntry={!showConfirmPassword}
179
+ />
180
+ <TouchableOpacity
181
+ onPress={() => setShowConfirmPassword(prev => !prev)}
182
+ style={inlineStyle_327_20}>
183
+ <Icon
184
+ name={showConfirmPassword ? 'visibility-off' : 'visibility'}
185
+ size={20}
186
+ color="#6c757d"
187
+ />
188
+ </TouchableOpacity>
189
+ </View>
190
+ </View>
191
+ </View>
192
+ )}
193
+
194
+ <View style={inlineStyle_339_16}>
195
+ <TouchableOpacity
196
+ style={inlineStyle_341_14}
197
+ onPress={() => {
198
+ Keyboard.dismiss();
199
+ onClose();
200
+ }}>
201
+ <Text style={inlineStyle_348_20}>Cancelar</Text>
202
+ </TouchableOpacity>
203
+ <TouchableOpacity
204
+ style={customStyles.saveButton}
205
+ onPress={() => {
206
+ Keyboard.dismiss();
207
+ onSave();
208
+ }}>
209
+ <FeatherIcon
210
+ name="save"
211
+ size={16}
212
+ color={customStyles.iconButtonPrimaryIcon.color}
213
+ />
214
+ <Text style={customStyles.saveButtonText}>Salvar</Text>
215
+ </TouchableOpacity>
216
+ </View>
217
+ </ScrollView>
218
+ </View>
219
+ </AnimatedModal>
220
+ );
221
+
222
+ export default UsersTabUserModal;
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Pure helpers for AddressesTab — keep the tab under the 500-line limit
3
+ * and make address id / IRI handling testable (app-community#282).
4
+ */
5
+
6
+ export const extractId = value => String(value || '').replace(/\D/g, '');
7
+
8
+ /**
9
+ * Normalize an address id to digits only.
10
+ * Never invents a timestamp id for missing values (that caused PUT 404
11
+ * and silent create paths when editing existing franchise addresses).
12
+ */
13
+ export const normalizeId = value => {
14
+ const digits = extractId(value);
15
+ if (digits) return digits;
16
+ const raw = String(value || '').trim();
17
+ return raw || '';
18
+ };
19
+
20
+ export const toPeopleIri = person => {
21
+ const rawIri = String(person?.['@id'] || '').trim();
22
+ if (rawIri.startsWith('/people/')) {
23
+ return rawIri;
24
+ }
25
+
26
+ const nestedIri = String(person?.people?.['@id'] || person?.people || '').trim();
27
+ if (nestedIri.startsWith('/people/')) {
28
+ return nestedIri;
29
+ }
30
+
31
+ const id = extractId(person?.id || person?.people?.id || rawIri || nestedIri);
32
+ return id ? `/people/${id}` : '';
33
+ };
34
+
35
+ export const normalizeString = value => {
36
+ const text = String(value || '').trim();
37
+ return text.length > 0 ? text : undefined;
38
+ };
39
+
40
+ export const normalizeZipCode = value =>
41
+ String(value || '')
42
+ .replace(/\D/g, '')
43
+ .slice(0, 8);
44
+
45
+ /**
46
+ * Flatten API/people address shapes into a stable client model.
47
+ * Always preserves a numeric `id` and an `/addresses/{id}` `@id` when known.
48
+ */
49
+ export const normalizeAddress = address => {
50
+ if (!address || typeof address !== 'object') {
51
+ return {
52
+ id: '',
53
+ '@id': '',
54
+ street: '',
55
+ number: '',
56
+ city: '',
57
+ state: '',
58
+ zipCode: '',
59
+ complement: '',
60
+ district: '',
61
+ country: '',
62
+ nickname: '',
63
+ };
64
+ }
65
+
66
+ const rawId = address?.id ?? address?.['@id'];
67
+ const id = normalizeId(rawId);
68
+ const iri =
69
+ String(address?.['@id'] || '').startsWith('/addresses/')
70
+ ? String(address['@id'])
71
+ : id
72
+ ? `/addresses/${id}`
73
+ : '';
74
+
75
+ return {
76
+ id,
77
+ '@id': iri,
78
+ street: address?.street?.street || address?.street || '',
79
+ number: String(address?.number ?? '').trim(),
80
+ city:
81
+ address?.street?.district?.city?.city ||
82
+ address?.street?.city?.city ||
83
+ address?.city ||
84
+ '',
85
+ state:
86
+ address?.street?.district?.city?.state?.uf ||
87
+ address?.street?.district?.city?.state?.state ||
88
+ address?.street?.city?.state?.uf ||
89
+ address?.street?.city?.state?.state ||
90
+ address?.state ||
91
+ '',
92
+ zipCode:
93
+ (typeof address?.zipCode === 'object'
94
+ ? address?.zipCode?.cep
95
+ : address?.zipCode) ||
96
+ address?.street?.cep?.cep ||
97
+ address?.postal_code ||
98
+ address?.cep ||
99
+ '',
100
+ complement: address?.complement || '',
101
+ district:
102
+ address?.street?.district?.district ||
103
+ address?.district ||
104
+ '',
105
+ country:
106
+ address?.street?.district?.city?.state?.country?.countrycode ||
107
+ address?.street?.district?.city?.state?.country?.countryname ||
108
+ address?.street?.city?.state?.country?.countrycode ||
109
+ address?.street?.city?.state?.country?.countryname ||
110
+ address?.country ||
111
+ '',
112
+ nickname: address?.nickname || '',
113
+ };
114
+ };
115
+
116
+ export const mapAddressesForClient = list =>
117
+ (Array.isArray(list) ? list : []).map(item => {
118
+ const id = normalizeId(item?.id || item?.['@id']);
119
+ const iri =
120
+ String(item?.['@id'] || '').startsWith('/addresses/')
121
+ ? String(item['@id'])
122
+ : id
123
+ ? `/addresses/${id}`
124
+ : '';
125
+ return {
126
+ id,
127
+ '@id': iri,
128
+ street: item.street,
129
+ number: item.number,
130
+ complement: item.complement,
131
+ district: item.district,
132
+ city: item.city,
133
+ state: item.state,
134
+ zipCode: item.zipCode,
135
+ country: item.country,
136
+ nickname: item.nickname,
137
+ };
138
+ });
139
+
140
+ /**
141
+ * Resolve the id/IRI that must be sent on edit so the default store
142
+ * issues PUT /addresses/{id} instead of POST /addresses.
143
+ */
144
+ export const resolveAddressSaveId = (editingItem, payload = {}) => {
145
+ const candidates = [
146
+ payload?.id,
147
+ payload?.['@id'],
148
+ editingItem?.['@id'],
149
+ editingItem?.id,
150
+ ];
151
+ for (const candidate of candidates) {
152
+ const digits = normalizeId(candidate);
153
+ if (digits) return digits;
154
+ }
155
+ return '';
156
+ };
157
+
158
+ export const buildAddressMapQueryParts = address => {
159
+ const streetLine = [normalizeString(address?.street), normalizeString(address?.number)]
160
+ .filter(Boolean)
161
+ .join(', ');
162
+ const cityStateLine = [normalizeString(address?.city), normalizeString(address?.state)]
163
+ .filter(Boolean)
164
+ .join(' - ');
165
+
166
+ return [
167
+ streetLine,
168
+ normalizeString(address?.district),
169
+ cityStateLine,
170
+ normalizeString(address?.country),
171
+ normalizeZipCode(address?.zipCode),
172
+ normalizeString(address?.complement),
173
+ ];
174
+ };
175
+
176
+ export const buildAddressPrimaryLine = address =>
177
+ [normalizeString(address?.street), normalizeString(address?.number)]
178
+ .filter(Boolean)
179
+ .join(', ');
180
+
181
+ export const buildAddressSecondaryLine = address =>
182
+ [
183
+ normalizeString(address?.district),
184
+ [normalizeString(address?.city), normalizeString(address?.state)]
185
+ .filter(Boolean)
186
+ .join(' - '),
187
+ normalizeString(address?.country),
188
+ ]
189
+ .filter(Boolean)
190
+ .join(' • ');
@@ -0,0 +1,142 @@
1
+ const IMAGE_FILE_PATTERN = /\.(?:avif|bmp|gif|ico|jpe?g|png|svg|webp)(?:\?|$)/i;
2
+ const PDF_FILE_PATTERN = /\.pdf(?:\?|$)/i;
3
+
4
+ export const extractId = value => {
5
+ if (value === null || value === undefined || value === '') {
6
+ return '';
7
+ }
8
+ if (typeof value === 'number') {
9
+ return String(value);
10
+ }
11
+ const raw = typeof value === 'string' ? value : value?.id || value?.['@id'] || '';
12
+ const match = String(raw).match(/(\d+)(?:\D*)$/);
13
+ return match ? match[1] : String(raw).replace(/\D/g, '') || '';
14
+ };
15
+
16
+ export const normalizeText = value => String(value || '').trim();
17
+
18
+ export const isImageFile = url => IMAGE_FILE_PATTERN.test(String(url || ''));
19
+ export const isPdfFile = url => PDF_FILE_PATTERN.test(String(url || ''));
20
+
21
+ export const applyDocumentMask = (value, documentType) => {
22
+ if (!value) {
23
+ return '';
24
+ }
25
+ const numbers = String(value).replace(/\D/g, '');
26
+ const docType = String(documentType || '').toUpperCase();
27
+
28
+ if (docType === 'CPF') {
29
+ return numbers
30
+ .slice(0, 11)
31
+ .replace(/(\d{3})(\d)/, '$1.$2')
32
+ .replace(/(\d{3})(\d)/, '$1.$2')
33
+ .replace(/(\d{3})(\d{1,2})$/, '$1-$2');
34
+ }
35
+
36
+ if (docType === 'CNPJ') {
37
+ return numbers
38
+ .slice(0, 14)
39
+ .replace(/(\d{2})(\d)/, '$1.$2')
40
+ .replace(/(\d{3})(\d)/, '$1.$2')
41
+ .replace(/(\d{3})(\d)/, '$1/$2')
42
+ .replace(/(\d{4})(\d{1,2})$/, '$1-$2');
43
+ }
44
+
45
+ return numbers;
46
+ };
47
+
48
+ export const removeMask = value => String(value || '').replace(/\D/g, '');
49
+
50
+ export const toDocumentIri = documentId => {
51
+ const id = extractId(documentId);
52
+ return id ? `/documents/${id}` : '';
53
+ };
54
+
55
+ export const toFileIri = fileValue => {
56
+ if (!fileValue) {
57
+ return '';
58
+ }
59
+ if (typeof fileValue === 'string' && fileValue.startsWith('/files/')) {
60
+ return fileValue;
61
+ }
62
+ const id = extractId(fileValue?.id || fileValue?.['@id'] || fileValue);
63
+ return id ? `/files/${id}` : '';
64
+ };
65
+
66
+ export const normalizeAttachment = (attachment, resolveDownloadUrl) => {
67
+ if (!attachment) {
68
+ return null;
69
+ }
70
+ const sourceFile = attachment.file || attachment;
71
+ const downloadUrl =
72
+ typeof resolveDownloadUrl === 'function'
73
+ ? resolveDownloadUrl(sourceFile)
74
+ : sourceFile?.url || sourceFile?.downloadUrl || '';
75
+ const fileId = extractId(
76
+ sourceFile?.id || sourceFile?.['@id'] || sourceFile?.file_id || downloadUrl,
77
+ );
78
+ const name =
79
+ normalizeText(sourceFile?.name || sourceFile?.fileName || sourceFile?.originalName) ||
80
+ (fileId ? `arquivo-${fileId}` : 'arquivo');
81
+
82
+ return {
83
+ id: attachment.id || attachment['@id'] || (fileId ? `legacy-${fileId}` : ''),
84
+ fileId,
85
+ name,
86
+ url: downloadUrl,
87
+ file: sourceFile,
88
+ legacy: Boolean(attachment.__legacy) || (!attachment.id && !attachment['@id']),
89
+ };
90
+ };
91
+
92
+ export const normalizeDocumentFiles = (document, resolveDownloadUrl) => {
93
+ const linkedFiles = Array.isArray(document?.documentFiles)
94
+ ? document.documentFiles
95
+ : Array.isArray(document?.document_files)
96
+ ? document.document_files
97
+ : [];
98
+
99
+ const fromCollection = linkedFiles
100
+ .map(item => normalizeAttachment(item, resolveDownloadUrl))
101
+ .filter(Boolean);
102
+
103
+ if (fromCollection.length) {
104
+ return fromCollection;
105
+ }
106
+
107
+ const legacy = normalizeAttachment(
108
+ document?.file ? { ...document.file, __legacy: true } : null,
109
+ resolveDownloadUrl,
110
+ );
111
+ return legacy ? [{ ...legacy, legacy: true }] : [];
112
+ };
113
+
114
+ export const toDocumentItem = document => ({
115
+ id: extractId(document?.id || document?.['@id']),
116
+ value: document?.document ?? document?.value ?? '',
117
+ type: document?.documentType?.['@id'] || document?.documentType || document?.type || '',
118
+ files: [],
119
+ raw: document,
120
+ });
121
+
122
+ export const unwrapUploadFile = payload => {
123
+ const data = payload?.response?.data ?? payload?.data ?? payload;
124
+ if (!data) {
125
+ return null;
126
+ }
127
+ if (data?.file) {
128
+ return data.file;
129
+ }
130
+ if (Array.isArray(data)) {
131
+ return data[0] || null;
132
+ }
133
+ if (Array.isArray(data?.member)) {
134
+ return data.member[0] || null;
135
+ }
136
+ if (Array.isArray(data?.['hydra:member'])) {
137
+ return data['hydra:member'][0] || null;
138
+ }
139
+ return data;
140
+ };
141
+
142
+ export const ACCEPTED_DOCUMENT_FILE_TYPES = 'image/jpeg,image/png,image/jpg,application/pdf,.jpg,.jpeg,.png,.pdf';
@@ -1,12 +1,6 @@
1
- const EMPLOYEE_CONTACT_LINK_TYPES = [
2
- 'employee',
3
- 'owner',
4
- 'director',
5
- 'manager',
6
- 'salesman',
7
- 'after-sales',
8
- 'courier',
9
- ];
1
+ import {HUMAN_COMPANY_LINK_TYPES} from '../../utils/humanCompanyLinkTypes';
2
+
3
+ export const EMPLOYEE_CONTACT_LINK_TYPES = HUMAN_COMPANY_LINK_TYPES;
10
4
 
11
5
  const extractId = value => {
12
6
  if (value == null || value === '') {
@@ -181,19 +175,6 @@ export const buildEmployeeContactsFromPeopleLinks = (
181
175
  return null;
182
176
  }
183
177
 
184
- if (
185
- person &&
186
- typeof person === 'object' &&
187
- (person?.deleted === true || person?.deleted === 1 || person?.deleted === '1')
188
- ) {
189
- return null;
190
- }
191
-
192
- const enableRaw = link?.enable ?? link?.enabled;
193
- if (enableRaw === 0 || enableRaw === false || enableRaw === '0') {
194
- return null;
195
- }
196
-
197
178
  const base =
198
179
  person && typeof person === 'object'
199
180
  ? {...person}
@@ -205,7 +186,6 @@ export const buildEmployeeContactsFromPeopleLinks = (
205
186
  id: base.id != null && base.id !== '' ? base.id : personId,
206
187
  linkType: normalizedLinkType,
207
188
  peopleLink: link,
208
- peopleLinkId: extractId(link?.id || link?.['@id']),
209
189
  };
210
190
  })
211
191
  .filter(Boolean);
@@ -37,14 +37,17 @@ export const removeEmployeeFromCompany = async ({
37
37
  removePeople,
38
38
  removePeopleLink,
39
39
  }) => {
40
- if (peopleId && typeof removePeople === 'function') {
41
- await removePeople(peopleId);
42
- return {mode: 'people', peopleId, linkId};
43
- }
40
+ // Company Contatos: unlink people_links first. DELETE /people/{id} 404s when
41
+ // the item provider path is filtered (#694) and would soft-delete the person
42
+ // globally instead of only the company link.
44
43
  if (linkId && typeof removePeopleLink === 'function') {
45
44
  await removePeopleLink(linkId);
46
45
  return {mode: 'link', peopleId, linkId};
47
46
  }
47
+ if (peopleId && typeof removePeople === 'function') {
48
+ await removePeople(peopleId);
49
+ return {mode: 'people', peopleId, linkId};
50
+ }
48
51
  const error = new Error(
49
52
  'Não foi possível remover o colaborador (ação indisponível).',
50
53
  );
@@ -2,6 +2,7 @@ import { resolveFileImageUrl } from '@controleonline/ui-common/src/react/utils/f
2
2
  import {
3
3
  } from '@controleonline/ui-common/src/react/utils/entityDisplay';
4
4
  import { normalizeEmployeeLinkType } from './employeeContacts';
5
+ import { HUMAN_COMPANY_LINK_TYPE_OPTIONS } from '../../utils/humanCompanyLinkTypes';
5
6
 
6
7
  export const extractId = value => String(value || '').replace(/\D/g, '');
7
8
  export const normalizeIdentityValue = value => String(value);
@@ -139,14 +140,30 @@ export const parseBrDateToYmd = value => {
139
140
  .padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
140
141
  };
141
142
 
142
- export const LINK_TYPE_OPTIONS = [
143
- { value: 'employee', translationKey: 'employee' },
144
- { value: 'owner', translationKey: 'owner' },
145
- { value: 'director', translationKey: 'director' },
146
- { value: 'manager', translationKey: 'manager' },
147
- { value: 'courier', translationKey: 'courier' },
143
+ export const LINK_TYPE_OPTIONS = HUMAN_COMPANY_LINK_TYPE_OPTIONS;
144
+
145
+ /** Canonical People.peopleType values from API (enum F|J). Do not invent types. */
146
+ export const PEOPLE_TYPE_OPTIONS = [
147
+ { value: 'F', label: 'Pessoa Física' },
148
+ { value: 'J', label: 'Pessoa Jurídica' },
148
149
  ];
149
150
 
151
+ export const normalizePeopleType = value => {
152
+ const normalized = String(value || '')
153
+ .trim()
154
+ .toUpperCase();
155
+ if (normalized === 'J' || normalized === 'JURIDICA' || normalized === 'PJ') {
156
+ return 'J';
157
+ }
158
+ return 'F';
159
+ };
160
+
161
+ export const formatPeopleTypeLabel = value => {
162
+ const type = normalizePeopleType(value);
163
+ const option = PEOPLE_TYPE_OPTIONS.find(item => item.value === type);
164
+ return option?.label || type;
165
+ };
166
+
150
167
  export const resolveEmployeeLinkType = employee =>
151
168
  normalizeEmployeeLinkType(
152
169
  employee?.linkType ||
@@ -199,8 +216,14 @@ export const formatEmployeeContactTitle = employee => {
199
216
  export const formatEmployeeContactMeta = employee => {
200
217
  const id = extractId(employee?.id || employee?.['@id']) || '-';
201
218
  const linkType = resolveEmployeeContactLinkType(employee);
202
-
203
- return linkType ? `ID: ${id} / ${linkType}` : `ID: ${id}`;
219
+ const peopleType = formatPeopleTypeLabel(
220
+ employee?.peopleType || employee?.people?.peopleType,
221
+ );
222
+ const parts = [`ID: ${id}`, peopleType];
223
+ if (linkType) {
224
+ parts.push(linkType);
225
+ }
226
+ return parts.join(' / ');
204
227
  };
205
228
 
206
229
  export const buildEmployeeCreatePayload = ({
@@ -209,11 +232,12 @@ export const buildEmployeeCreatePayload = ({
209
232
  foundationDate,
210
233
  linkType,
211
234
  parentPeopleId,
235
+ peopleType,
212
236
  }) => {
213
237
  const payload = {
214
238
  name,
215
239
  alias,
216
- peopleType: 'F',
240
+ peopleType: normalizePeopleType(peopleType),
217
241
  company: `/people/${parentPeopleId}`,
218
242
  linkType: normalizeEmployeeLinkType(linkType),
219
243
  'extra-data': {},