@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
@@ -17,194 +17,90 @@ import {
17
17
  buildNavigationMapQuery,
18
18
  buildWazeNavigationUrl,
19
19
  } from '@controleonline/ui-common/src/react/utils/mapNavigation';
20
-
21
- const extractId = value => String(value || '').replace(/\D/g, '');
22
- const normalizeId = value => extractId(value) || value || Date.now();
23
-
24
- const toPeopleIri = person => {
25
- const rawIri = String(person?.['@id'] || '').trim();
26
- if (rawIri.startsWith('/people/')) {
27
- return rawIri;
28
- }
29
-
30
- const nestedIri = String(person?.people?.['@id'] || person?.people || '').trim();
31
- if (nestedIri.startsWith('/people/')) {
32
- return nestedIri;
33
- }
34
-
35
- const id = extractId(person?.id || person?.people?.id || rawIri || nestedIri);
36
- return id ? `/people/${id}` : '';
37
- };
38
-
39
- const normalizeString = value => {
40
- const text = String(value || '').trim();
41
- return text.length > 0 ? text : undefined;
42
- };
43
- const normalizeZipCode = value =>
44
- String(value || '')
45
- .replace(/\D/g, '')
46
- .slice(0, 8);
47
-
48
- const buildAddressMapQuery = address => {
49
- const streetLine = [normalizeString(address?.street), normalizeString(address?.number)]
50
- .filter(Boolean)
51
- .join(', ');
52
- const cityStateLine = [normalizeString(address?.city), normalizeString(address?.state)]
53
- .filter(Boolean)
54
- .join(' - ');
55
-
56
- return buildNavigationMapQuery([
57
- streetLine,
58
- normalizeString(address?.district),
59
- cityStateLine,
60
- normalizeString(address?.country),
61
- normalizeZipCode(address?.zipCode),
62
- normalizeString(address?.complement),
63
- ]);
64
- };
65
-
66
- const buildAddressPrimaryLine = address =>
67
- [normalizeString(address?.street), normalizeString(address?.number)]
68
- .filter(Boolean)
69
- .join(', ');
70
-
71
- const buildAddressSecondaryLine = address =>
72
- [
73
- normalizeString(address?.district),
74
- [normalizeString(address?.city), normalizeString(address?.state)]
75
- .filter(Boolean)
76
- .join(' - '),
77
- normalizeString(address?.country),
78
- ]
79
- .filter(Boolean)
80
- .join(' • ');
81
-
82
- const normalizeAddress = address => ({
83
- id: normalizeId(address?.id || address?.['@id']),
84
- street: address?.street?.street || address?.street || '',
85
- number: String(address?.number || '').trim(),
86
- city:
87
- address?.street?.district?.city?.city ||
88
- address?.street?.city?.city ||
89
- address?.city ||
90
- '',
91
- state:
92
- address?.street?.district?.city?.state?.uf ||
93
- address?.street?.district?.city?.state?.state ||
94
- address?.street?.city?.state?.uf ||
95
- address?.street?.city?.state?.state ||
96
- address?.state ||
97
- '',
98
- zipCode:
99
- (typeof address?.zipCode === 'object'
100
- ? address?.zipCode?.cep
101
- : address?.zipCode) ||
102
- address?.street?.cep?.cep ||
103
- address?.postal_code ||
104
- address?.cep ||
105
- '',
106
- complement: address?.complement || '',
107
- district:
108
- address?.street?.district?.district ||
109
- address?.district ||
110
- '',
111
- country:
112
- address?.street?.district?.city?.state?.country?.countrycode ||
113
- address?.street?.district?.city?.state?.country?.countryname ||
114
- address?.street?.city?.state?.country?.countrycode ||
115
- address?.street?.city?.state?.country?.countryname ||
116
- address?.country ||
117
- '',
118
- nickname: address?.nickname || '',
119
- });
120
-
121
- const mapAddressesForClient = list =>
122
- list.map(item => ({
123
- id: item.id,
124
- '@id': String(item?.id || '').startsWith('/addresses/')
125
- ? String(item.id)
126
- : `/addresses/${extractId(item.id || '')}`,
127
- street: item.street,
128
- number: item.number,
129
- complement: item.complement,
130
- district: item.district,
131
- city: item.city,
132
- state: item.state,
133
- zipCode: item.zipCode,
134
- country: item.country,
135
- nickname: item.nickname,
136
- }));
137
-
138
- const AddressesTab = ({client, customStyles, isEditing, onUpdateClient}) => {
139
- const {showError, showSuccess, showDialog} = useMessage();
20
+ import {
21
+ buildAddressMapQueryParts,
22
+ buildAddressPrimaryLine,
23
+ buildAddressSecondaryLine,
24
+ mapAddressesForClient,
25
+ normalizeAddress,
26
+ resolveAddressSaveId,
27
+ toPeopleIri,
28
+ } from './addressesTabHelpers';
29
+
30
+ const AddressesTab = ({client, customStyles, isEditing, onUpdateClient}) => {
31
+ const {showError, showSuccess, showDialog} = useMessage();
140
32
  const [addresses, setAddresses] = useState([]);
141
33
  const [showModal, setShowModal] = useState(false);
142
34
  const [editingItem, setEditingItem] = useState(null);
143
35
  const [showNavigationModal, setShowNavigationModal] = useState(false);
144
36
  const [navigationAddress, setNavigationAddress] = useState(null);
145
-
146
- const addressStore = useStores(state => state.address) || {};
147
- const actions = addressStore.actions || {};
148
- const peopleIri = useMemo(() => toPeopleIri(client), [client]);
149
-
150
- useEffect(() => {
151
- const rawAddresses = Array.isArray(client?.address)
152
- ? client.address.map(item => normalizeAddress(item))
153
- : [];
154
-
155
- setAddresses(rawAddresses);
156
- }, [client]);
157
-
158
- const openModal = item => {
159
- const normalizedItem = item ? normalizeAddress(item) : {};
160
- setEditingItem(item ? normalizedItem : null);
37
+
38
+ const addressStore = useStores(state => state.address) || {};
39
+ const actions = addressStore.actions || {};
40
+ const peopleIri = useMemo(() => toPeopleIri(client), [client]);
41
+
42
+ useEffect(() => {
43
+ const rawAddresses = Array.isArray(client?.address)
44
+ ? client.address.map(item => normalizeAddress(item))
45
+ : [];
46
+ setAddresses(rawAddresses);
47
+ }, [client]);
48
+
49
+ const openModal = item => {
50
+ setEditingItem(item ? normalizeAddress(item) : null);
161
51
  setShowModal(true);
162
52
  };
163
-
164
- const closeModal = () => {
53
+
54
+ const closeModal = () => {
165
55
  setShowModal(false);
166
56
  setEditingItem(null);
167
57
  };
168
-
169
- const closeNavigationModal = () => {
170
- setShowNavigationModal(false);
171
- setNavigationAddress(null);
172
- };
173
-
174
- const openNavigationModal = address => {
175
- const normalizedAddress = normalizeAddress(address);
176
- const mapQuery = buildAddressMapQuery(normalizedAddress);
177
-
178
- if (!mapQuery) {
179
- showError('Nao foi possivel montar a navegacao para este endereco.');
180
- return;
181
- }
182
-
183
- setNavigationAddress(normalizedAddress);
184
- setShowNavigationModal(true);
185
- };
186
-
187
- const handleOpenNavigation = async appName => {
188
- const mapQuery = buildAddressMapQuery(navigationAddress);
189
- const url =
190
- appName === 'waze'
191
- ? buildWazeNavigationUrl({mapQuery})
192
- : buildGoogleMapsNavigationUrl({mapQuery});
193
-
194
- if (!url) {
195
- showError('Nao foi possivel montar a navegacao para este endereco.');
196
- return;
197
- }
198
-
199
- closeNavigationModal();
200
-
201
- try {
202
- await Linking.openURL(url);
203
- } catch {
204
- showError('Nao foi possivel abrir o aplicativo de navegacao.');
205
- }
206
- };
207
-
58
+
59
+ const closeNavigationModal = () => {
60
+ setShowNavigationModal(false);
61
+ setNavigationAddress(null);
62
+ };
63
+
64
+ const openNavigationModal = address => {
65
+ const normalizedAddress = normalizeAddress(address);
66
+ const mapQuery = buildNavigationMapQuery(buildAddressMapQueryParts(normalizedAddress));
67
+
68
+ if (!mapQuery) {
69
+ showError('Nao foi possivel montar a navegacao para este endereco.');
70
+ return;
71
+ }
72
+
73
+ setNavigationAddress(normalizedAddress);
74
+ setShowNavigationModal(true);
75
+ };
76
+
77
+ const handleOpenNavigation = async appName => {
78
+ const mapQuery = buildNavigationMapQuery(
79
+ buildAddressMapQueryParts(navigationAddress),
80
+ );
81
+ const url =
82
+ appName === 'waze'
83
+ ? buildWazeNavigationUrl({mapQuery})
84
+ : buildGoogleMapsNavigationUrl({mapQuery});
85
+
86
+ if (!url) {
87
+ showError('Nao foi possivel montar a navegacao para este endereco.');
88
+ return;
89
+ }
90
+
91
+ closeNavigationModal();
92
+
93
+ try {
94
+ await Linking.openURL(url);
95
+ } catch {
96
+ showError('Nao foi possivel abrir o aplicativo de navegacao.');
97
+ }
98
+ };
99
+
100
+ /**
101
+ * Always attach a numeric id when editing so the default store issues PUT
102
+ * (update) instead of POST (create). Root cause of app-community#282.
103
+ */
208
104
  const saveAddress = async payload => {
209
105
  if (!actions?.save) {
210
106
  throw new Error('Servico de enderecos indisponivel no momento.');
@@ -214,17 +110,25 @@ const AddressesTab = ({client, customStyles, isEditing, onUpdateClient}) => {
214
110
  throw new Error('Nao foi possivel identificar o cliente para salvar o endereco.');
215
111
  }
216
112
 
217
- return actions.save({
113
+ const body = {
218
114
  ...payload,
219
115
  people: peopleIri,
220
- });
116
+ };
117
+
118
+ const editId = resolveAddressSaveId(editingItem, payload);
119
+ if (editId) {
120
+ body.id = editId;
121
+ }
122
+
123
+ return actions.save(body);
221
124
  };
222
125
 
223
126
  const handleAddressSaved = saved => {
224
127
  const normalizedSaved = normalizeAddress(saved || {});
225
- const updatedAddresses = editingItem
128
+ const editId = resolveAddressSaveId(editingItem);
129
+ const updatedAddresses = editId
226
130
  ? addresses.map(item =>
227
- item.id === editingItem.id ? normalizedSaved : item,
131
+ resolveAddressSaveId(item) === editId ? normalizedSaved : item,
228
132
  )
229
133
  : [...addresses, normalizedSaved];
230
134
 
@@ -232,44 +136,49 @@ const AddressesTab = ({client, customStyles, isEditing, onUpdateClient}) => {
232
136
  onUpdateClient?.('address', mapAddressesForClient(updatedAddresses));
233
137
 
234
138
  showSuccess(
235
- editingItem
139
+ editId
236
140
  ? 'Endereco atualizado com sucesso!'
237
141
  : 'Endereco criado com sucesso!',
238
142
  );
239
143
  closeModal();
240
144
  };
241
-
242
- const handleDelete = id => {
243
- showDialog({
244
- title: 'Confirmar exclusao',
245
- message: 'Deseja realmente remover este item?',
246
- confirmLabel: 'Remover',
247
- cancelLabel: 'Cancelar',
248
- onConfirm: async () => {
249
- try {
250
- if (!actions?.remove) {
251
- showError('Servico de enderecos indisponivel no momento.');
252
- return;
253
- }
254
- await actions.remove(id);
255
- const updatedAddresses = addresses.filter(item => item.id !== id);
256
- setAddresses(updatedAddresses);
257
- onUpdateClient?.('address', mapAddressesForClient(updatedAddresses));
258
- showSuccess('Endereco removido com sucesso!');
259
- } catch {
260
- showError('Falha ao remover endereco. Tente novamente.');
261
- }
262
- },
263
- });
264
- };
265
-
145
+
146
+ const handleDelete = id => {
147
+ const deleteId = resolveAddressSaveId({id});
148
+ showDialog({
149
+ title: 'Confirmar exclusao',
150
+ message: 'Deseja realmente remover este item?',
151
+ confirmLabel: 'Remover',
152
+ cancelLabel: 'Cancelar',
153
+ onConfirm: async () => {
154
+ try {
155
+ if (!actions?.remove || !deleteId) {
156
+ showError('Servico de enderecos indisponivel no momento.');
157
+ return;
158
+ }
159
+ await actions.remove(deleteId);
160
+ const updatedAddresses = addresses.filter(
161
+ item => resolveAddressSaveId(item) !== deleteId,
162
+ );
163
+ setAddresses(updatedAddresses);
164
+ onUpdateClient?.('address', mapAddressesForClient(updatedAddresses));
165
+ showSuccess('Endereco removido com sucesso!');
166
+ } catch {
167
+ showError('Falha ao remover endereco. Tente novamente.');
168
+ }
169
+ },
170
+ });
171
+ };
172
+
266
173
  const renderModal = () => (
267
174
  <Modal visible={showModal} transparent animationType="slide">
268
175
  <View style={[customStyles.modalOverlay, addressModalStyles.overlay]}>
269
176
  <View style={addressModalStyles.container}>
270
177
  <View style={addressModalStyles.header}>
271
178
  <Text style={[customStyles.modalTitle, addressModalStyles.title]}>
272
- {editingItem ? global.t?.t('address', 'title', 'editAddress') : global.t?.t('address', 'title', 'address')}
179
+ {editingItem
180
+ ? global.t?.t('address', 'title', 'editAddress')
181
+ : global.t?.t('address', 'title', 'address')}
273
182
  </Text>
274
183
  <TouchableOpacity
275
184
  onPress={closeModal}
@@ -278,15 +187,23 @@ const AddressesTab = ({client, customStyles, isEditing, onUpdateClient}) => {
278
187
  </TouchableOpacity>
279
188
  </View>
280
189
  <DefaultAddress
281
- mode={editingItem ? 'edit' : 'create'}
282
- row={editingItem
283
- ? {
284
- ...editingItem,
285
- cep: editingItem.zipCode,
286
- uf: editingItem.state,
287
- country: editingItem.country,
288
- }
289
- : null}
190
+ mode={
191
+ editingItem && resolveAddressSaveId(editingItem) ? 'edit' : 'create'
192
+ }
193
+ row={
194
+ editingItem && resolveAddressSaveId(editingItem)
195
+ ? {
196
+ ...editingItem,
197
+ id: resolveAddressSaveId(editingItem),
198
+ '@id':
199
+ editingItem['@id'] ||
200
+ `/addresses/${resolveAddressSaveId(editingItem)}`,
201
+ cep: editingItem.zipCode,
202
+ uf: editingItem.state,
203
+ country: editingItem.country,
204
+ }
205
+ : null
206
+ }
290
207
  peopleIri={peopleIri}
291
208
  saveAction={saveAddress}
292
209
  onCancel={closeModal}
@@ -296,74 +213,76 @@ const AddressesTab = ({client, customStyles, isEditing, onUpdateClient}) => {
296
213
  </View>
297
214
  </View>
298
215
  </Modal>
299
- );
300
-
301
- const renderNavigationModal = () => {
302
- const primaryLine = buildAddressPrimaryLine(navigationAddress);
303
- const secondaryLine = buildAddressSecondaryLine(navigationAddress);
304
-
305
- return (
306
- <Modal
307
- visible={showNavigationModal}
308
- transparent
309
- animationType="fade"
310
- onRequestClose={closeNavigationModal}>
311
- <View style={customStyles.modalOverlay}>
312
- <View style={customStyles.modalContainer}>
313
- <Text style={customStyles.modalTitle}>Abrir endereco</Text>
314
- <Text style={customStyles.navigationModalDescription}>
315
- Escolha qual aplicativo deseja usar para navegar ate este endereco.
316
- </Text>
317
- <View style={customStyles.navigationAddressCard}>
318
- <Text style={customStyles.navigationAddressTitle}>
319
- {primaryLine || 'Endereco'}
320
- </Text>
321
- {secondaryLine ? (
322
- <Text style={customStyles.navigationAddressSubtitle}>
323
- {secondaryLine}
324
- </Text>
325
- ) : null}
326
- </View>
327
- <View style={customStyles.navigationOptions}>
328
- <TouchableOpacity
329
- style={[
330
- customStyles.navigationOptionButton,
331
- customStyles.navigationOptionButtonPrimary,
332
- ]}
333
- onPress={() => handleOpenNavigation('google')}>
334
- <Text
335
- style={[
336
- customStyles.navigationOptionButtonText,
337
- customStyles.navigationOptionButtonTextPrimary,
338
- ]}>
339
- Google Maps
340
- </Text>
341
- </TouchableOpacity>
342
- <TouchableOpacity
343
- style={customStyles.navigationOptionButton}
344
- onPress={() => handleOpenNavigation('waze')}>
345
- <Text style={customStyles.navigationOptionButtonText}>
346
- Waze
347
- </Text>
348
- </TouchableOpacity>
349
- </View>
350
- <TouchableOpacity
351
- style={customStyles.modalCancelButton}
352
- onPress={closeNavigationModal}>
353
- <Text style={customStyles.modalCancelText}>Cancelar</Text>
354
- </TouchableOpacity>
355
- </View>
356
- </View>
357
- </Modal>
358
- );
359
- };
360
-
361
- return (
362
- <>
363
- <View style={customStyles.tabContent}>
364
- <View style={customStyles.section}>
365
- <View style={customStyles.sectionHeader}>
366
- <Text style={customStyles.sectionTitle}>{global.t?.t('address', 'title', 'addresses')}</Text>
216
+ );
217
+
218
+ const renderNavigationModal = () => {
219
+ const primaryLine = buildAddressPrimaryLine(navigationAddress);
220
+ const secondaryLine = buildAddressSecondaryLine(navigationAddress);
221
+
222
+ return (
223
+ <Modal
224
+ visible={showNavigationModal}
225
+ transparent
226
+ animationType="fade"
227
+ onRequestClose={closeNavigationModal}>
228
+ <View style={customStyles.modalOverlay}>
229
+ <View style={customStyles.modalContainer}>
230
+ <Text style={customStyles.modalTitle}>Abrir endereco</Text>
231
+ <Text style={customStyles.navigationModalDescription}>
232
+ Escolha qual aplicativo deseja usar para navegar ate este endereco.
233
+ </Text>
234
+ <View style={customStyles.navigationAddressCard}>
235
+ <Text style={customStyles.navigationAddressTitle}>
236
+ {primaryLine || 'Endereco'}
237
+ </Text>
238
+ {secondaryLine ? (
239
+ <Text style={customStyles.navigationAddressSubtitle}>
240
+ {secondaryLine}
241
+ </Text>
242
+ ) : null}
243
+ </View>
244
+ <View style={customStyles.navigationOptions}>
245
+ <TouchableOpacity
246
+ style={[
247
+ customStyles.navigationOptionButton,
248
+ customStyles.navigationOptionButtonPrimary,
249
+ ]}
250
+ onPress={() => handleOpenNavigation('google')}>
251
+ <Text
252
+ style={[
253
+ customStyles.navigationOptionButtonText,
254
+ customStyles.navigationOptionButtonTextPrimary,
255
+ ]}>
256
+ Google Maps
257
+ </Text>
258
+ </TouchableOpacity>
259
+ <TouchableOpacity
260
+ style={customStyles.navigationOptionButton}
261
+ onPress={() => handleOpenNavigation('waze')}>
262
+ <Text style={customStyles.navigationOptionButtonText}>
263
+ Waze
264
+ </Text>
265
+ </TouchableOpacity>
266
+ </View>
267
+ <TouchableOpacity
268
+ style={customStyles.modalCancelButton}
269
+ onPress={closeNavigationModal}>
270
+ <Text style={customStyles.modalCancelText}>Cancelar</Text>
271
+ </TouchableOpacity>
272
+ </View>
273
+ </View>
274
+ </Modal>
275
+ );
276
+ };
277
+
278
+ return (
279
+ <>
280
+ <View style={customStyles.tabContent}>
281
+ <View style={customStyles.section}>
282
+ <View style={customStyles.sectionHeader}>
283
+ <Text style={customStyles.sectionTitle}>
284
+ {global.t?.t('address', 'title', 'addresses')}
285
+ </Text>
367
286
  {isEditing && (
368
287
  <TouchableOpacity
369
288
  onPress={() => openModal(null)}
@@ -376,46 +295,32 @@ const AddressesTab = ({client, customStyles, isEditing, onUpdateClient}) => {
376
295
  </TouchableOpacity>
377
296
  )}
378
297
  </View>
379
- {addresses.length === 0 ? (
380
- <Text style={customStyles.emptyText}>{global.t?.t('address', 'label', 'noAddressesFound')}</Text>
381
- ) : (
382
- addresses.map(address => (
383
- <View
384
- key={address.id}
385
- style={[
386
- customStyles.listItem,
387
- customStyles.listItemWithActions,
388
- ]}>
298
+ {addresses.length === 0 ? (
299
+ <Text style={customStyles.emptyText}>
300
+ {global.t?.t('address', 'empty', 'noAddresses') ||
301
+ 'Nenhum endereco cadastrado.'}
302
+ </Text>
303
+ ) : (
304
+ addresses.map(address => (
305
+ <View key={address.id || address['@id']} style={customStyles.itemCard}>
389
306
  <View style={customStyles.itemContent}>
390
- <TouchableOpacity
391
- onPress={() => openNavigationModal(address)}
392
- style={[
393
- customStyles.iconButtonLocation,
394
- customStyles.locationButton,
395
- ]}>
396
- <Icon
397
- name="location-on"
398
- size={20}
399
- color={customStyles.iconButtonPrimaryIcon.color}
400
- />
401
- </TouchableOpacity>
402
- <View>
403
- <Text style={customStyles.itemText}>
404
- {address.street}
405
- {address.number ? `, ${address.number}` : ''}
406
- </Text>
407
- <Text style={customStyles.itemSubtext}>
408
- {address.district ? `${address.district}, ` : ''}
409
- {address.city}
410
- {address.state ? ` - ${address.state}` : ''}
411
- {address.country ? ` (${address.country})` : ''}
412
- {address.complement ? `\n${address.complement}` : ''}
413
- {address.nickname && address.nickname !== 'DEFAULT'
414
- ? `\n${address.nickname}`
415
- : ''}
416
- </Text>
417
- </View>
418
- </View>
307
+ <View style={{flex: 1}}>
308
+ <Text style={customStyles.itemTitle}>
309
+ {[address.street, address.number].filter(Boolean).join(', ') ||
310
+ 'Endereco'}
311
+ </Text>
312
+ <Text style={customStyles.itemSubtitle}>
313
+ {address.district ? `${address.district}\n` : ''}
314
+ {address.city}
315
+ {address.state ? ` - ${address.state}` : ''}
316
+ {address.country ? ` (${address.country})` : ''}
317
+ {address.complement ? `\n${address.complement}` : ''}
318
+ {address.nickname && address.nickname !== 'DEFAULT'
319
+ ? `\n${address.nickname}`
320
+ : ''}
321
+ </Text>
322
+ </View>
323
+ </View>
419
324
  {isEditing && (
420
325
  <View
421
326
  style={[
@@ -440,16 +345,16 @@ const AddressesTab = ({client, customStyles, isEditing, onUpdateClient}) => {
440
345
  color={customStyles.iconButtonGhostIcon.color}
441
346
  />
442
347
  </TouchableOpacity>
443
- </View>
444
- )}
445
- </View>
446
- ))
447
- )}
448
- </View>
449
- </View>
450
- {renderModal()}
451
- {renderNavigationModal()}
452
- </>
348
+ </View>
349
+ )}
350
+ </View>
351
+ ))
352
+ )}
353
+ </View>
354
+ </View>
355
+ {renderModal()}
356
+ {renderNavigationModal()}
357
+ </>
453
358
  );
454
359
  };
455
360