@controleonline/ui-customers 1.0.81

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 (102) hide show
  1. package/.github/agents/developer.agent.md +30 -0
  2. package/.github/agents/devops.agent.md +30 -0
  3. package/.github/agents/qa.agent.md +30 -0
  4. package/.github/agents/security.agent.md +30 -0
  5. package/.scrutinizer.yml +20 -0
  6. package/AGENTS.md +50 -0
  7. package/FUNDING.yml +1 -0
  8. package/README.md +23 -0
  9. package/docs/technical/Client-Details-Franquia-Filial.md +18 -0
  10. package/docs/technical/Client-Details-People-Name-Case.md +103 -0
  11. package/docs/technical/Cliente-Vendedor-Vinculo-e-Permissoes.md +152 -0
  12. package/docs/wiki.md +1 -0
  13. package/package.json +32 -0
  14. package/scripts/validate-wiki-pointer.cjs +13 -0
  15. package/src/react/components/tabs/AddressesTab.js +494 -0
  16. package/src/react/components/tabs/CategoriesTab.js +196 -0
  17. package/src/react/components/tabs/ClassificationChips.js +494 -0
  18. package/src/react/components/tabs/ContactTab.js +621 -0
  19. package/src/react/components/tabs/ContactTab.styles.js +119 -0
  20. package/src/react/components/tabs/ContractsTab.js +13 -0
  21. package/src/react/components/tabs/DocumentsTab.js +463 -0
  22. package/src/react/components/tabs/DocumentsTab.styles.js +133 -0
  23. package/src/react/components/tabs/EmployeeContactRow.js +83 -0
  24. package/src/react/components/tabs/EmployeesTab.js +454 -0
  25. package/src/react/components/tabs/EmployeesTab.styles.js +172 -0
  26. package/src/react/components/tabs/FiscalTab.js +6 -0
  27. package/src/react/components/tabs/FranchiseCommissionSection.js +307 -0
  28. package/src/react/components/tabs/FranchiseLinksManageModal.js +215 -0
  29. package/src/react/components/tabs/FranchiseLinksTab.js +282 -0
  30. package/src/react/components/tabs/GeneralTab.js +466 -0
  31. package/src/react/components/tabs/GeneralTab.styles.js +101 -0
  32. package/src/react/components/tabs/MediaTab.js +356 -0
  33. package/src/react/components/tabs/PeopleCategoriesPanel.js +601 -0
  34. package/src/react/components/tabs/ProductsTab.js +236 -0
  35. package/src/react/components/tabs/ProductsTab.styles.js +143 -0
  36. package/src/react/components/tabs/SalesmanCommissionBlock.js +120 -0
  37. package/src/react/components/tabs/SalesmanManageModal.js +208 -0
  38. package/src/react/components/tabs/SalesmanTab.js +436 -0
  39. package/src/react/components/tabs/SalesmanTab.styles.js +4 -0
  40. package/src/react/components/tabs/UserApiKeyModal.js +90 -0
  41. package/src/react/components/tabs/UserFormModal.js +301 -0
  42. package/src/react/components/tabs/UsersTab.js +426 -0
  43. package/src/react/components/tabs/UsersTab.styles.js +283 -0
  44. package/src/react/components/tabs/UsersTab.styles.test.js +42 -0
  45. package/src/react/components/tabs/contactMutationMessage.js +18 -0
  46. package/src/react/components/tabs/employeeContacts.js +214 -0
  47. package/src/react/components/tabs/employeeUnlink.js +68 -0
  48. package/src/react/components/tabs/employeesTabHelpers.js +227 -0
  49. package/src/react/components/tabs/franchiseCommissionHelpers.js +134 -0
  50. package/src/react/components/tabs/franchiseLinksTab.helpers.js +247 -0
  51. package/src/react/components/tabs/generalTabHelpers.js +120 -0
  52. package/src/react/components/tabs/salesmanTab.helpers.js +114 -0
  53. package/src/react/components/tabs/salesmanTabHelpers.js +173 -0
  54. package/src/react/components/tabs/salesmanTabMedia.js +55 -0
  55. package/src/react/components/tabs/salesmanTabSession.js +39 -0
  56. package/src/react/components/tabs/useEmployeeUnlink.js +58 -0
  57. package/src/react/components/tabs/useFranchiseLinksManage.js +215 -0
  58. package/src/react/components/tabs/useSalesmanManage.js +222 -0
  59. package/src/react/components/tabs/usersTabHelpers.js +221 -0
  60. package/src/react/domain/productSupplierFlow.js +37 -0
  61. package/src/react/index.js +4 -0
  62. package/src/react/pages/ClientDetailsSkeleton.js +84 -0
  63. package/src/react/pages/clientDetailsHelpers.js +17 -0
  64. package/src/react/pages/clients.js +27 -0
  65. package/src/react/pages/details.js +529 -0
  66. package/src/react/pages/details.page.styles.js +105 -0
  67. package/src/react/pages/details.styles.js +15 -0
  68. package/src/react/pages/employees.js +69 -0
  69. package/src/react/pages/franchisees.js +30 -0
  70. package/src/react/pages/prospects.js +21 -0
  71. package/src/react/pages/provider.js +21 -0
  72. package/src/react/router/routes.js +104 -0
  73. package/src/react/styles/details.js +571 -0
  74. package/src/store/people_categories/index.js +72 -0
  75. package/src/tests/browser/manager/client-details-create-user.spec.js +321 -0
  76. package/src/tests/browser/manager/client-details-employee-unlink.spec.js +215 -0
  77. package/src/tests/browser/manager/client-details-employees-refresh.spec.js +271 -0
  78. package/src/tests/browser/manager/client-details-enable.spec.js +241 -0
  79. package/src/tests/browser/manager/client-details-fiscal-tabs.spec.js +410 -0
  80. package/src/tests/browser/manager/client-details-franchise-commission.spec.js +318 -0
  81. package/src/tests/browser/manager/client-details-franchise-links.spec.js +365 -0
  82. package/src/tests/browser/manager/client-details-media.spec.js +405 -0
  83. package/src/tests/browser/manager/client-details-relationships.spec.js +313 -0
  84. package/src/tests/browser/manager/client-details-salesman-commission.spec.js +291 -0
  85. package/src/tests/browser/manager/client-details-salesman-manage.spec.js +448 -0
  86. package/src/tests/react/components/tabs/contactMutationMessage.test.js +25 -0
  87. package/src/tests/react/components/tabs/employeeContacts.test.js +266 -0
  88. package/src/tests/react/components/tabs/employeeUnlink.test.js +67 -0
  89. package/src/tests/react/components/tabs/employeesTabHelpers.media.test.js +78 -0
  90. package/src/tests/react/components/tabs/employeesTabHelpers.test.js +64 -0
  91. package/src/tests/react/components/tabs/fiscalTabContract.test.mjs +29 -0
  92. package/src/tests/react/components/tabs/franchiseCommissionHelpers.test.js +128 -0
  93. package/src/tests/react/components/tabs/franchiseLinksTab.helpers.test.mjs +166 -0
  94. package/src/tests/react/components/tabs/generalTabHelpers.test.js +130 -0
  95. package/src/tests/react/components/tabs/peopleMediaTabs.test.js +322 -0
  96. package/src/tests/react/components/tabs/salesmanTab.helpers.test.mjs +75 -0
  97. package/src/tests/react/components/tabs/salesmanTabHelpers.test.js +142 -0
  98. package/src/tests/react/components/tabs/salesmanTabHelpers.test.mjs +92 -0
  99. package/src/tests/react/components/tabs/usersTabHelpers.test.js +59 -0
  100. package/src/tests/react/domain/productSupplierFlow.test.mjs +55 -0
  101. package/src/tests/react/pages/clientDetailsHelpers.test.js +98 -0
  102. package/src/tests/react/pages/employees.test.js +44 -0
@@ -0,0 +1,119 @@
1
+ export const inlineStyle_363_18 = {
2
+ fontSize: 16,
3
+ fontWeight: '600',
4
+ color: '#212529',
5
+ marginBottom: 8,
6
+ };
7
+
8
+ export const inlineStyle_365_14 = {
9
+ borderWidth: 1,
10
+ borderColor: '#e9ecef',
11
+ borderRadius: 12,
12
+ paddingHorizontal: 16,
13
+ paddingVertical: 12,
14
+ fontSize: 16,
15
+ backgroundColor: '#f8f9fa',
16
+ };
17
+
18
+ export const inlineStyle_382_18 = {
19
+ fontSize: 16,
20
+ fontWeight: '600',
21
+ color: '#212529',
22
+ marginBottom: 8,
23
+ };
24
+
25
+ export const inlineStyle_384_14 = {
26
+ borderWidth: 1,
27
+ borderColor: '#e9ecef',
28
+ borderRadius: 12,
29
+ paddingHorizontal: 16,
30
+ paddingVertical: 12,
31
+ fontSize: 16,
32
+ backgroundColor: '#f8f9fa',
33
+ };
34
+
35
+ export const inlineStyle_404_8 = {
36
+ justifyContent: 'flex-end',
37
+ };
38
+
39
+ export const inlineStyle_405_14 = {
40
+ backgroundColor: '#fff',
41
+ borderTopLeftRadius: 24,
42
+ borderTopRightRadius: 24,
43
+ maxHeight: '80%',
44
+ width: '100%',
45
+ shadowColor: '#000',
46
+
47
+ shadowOffset: {
48
+ width: 0,
49
+ height: -4,
50
+ },
51
+
52
+ shadowOpacity: 0.1,
53
+ shadowRadius: 12,
54
+ elevation: 10,
55
+ };
56
+
57
+ export const inlineStyle_418_16 = {
58
+ flexDirection: 'row',
59
+ alignItems: 'center',
60
+ justifyContent: 'space-between',
61
+ paddingHorizontal: 24,
62
+ paddingVertical: 20,
63
+ borderBottomWidth: 1,
64
+ borderBottomColor: '#F1F5F9',
65
+ };
66
+
67
+ export const inlineStyle_427_18 = {
68
+ fontSize: 20,
69
+ fontWeight: '700',
70
+ color: '#0F172A',
71
+ };
72
+
73
+ export const inlineStyle_430_51 = {
74
+ width: 32,
75
+ height: 32,
76
+ borderRadius: 16,
77
+ backgroundColor: '#F1F5F9',
78
+ alignItems: 'center',
79
+ justifyContent: 'center',
80
+ };
81
+
82
+ export const inlineStyle_437_16 = {
83
+ padding: 24,
84
+ };
85
+
86
+ export const inlineStyle_439_18 = {
87
+ flexDirection: 'row',
88
+ gap: 12,
89
+ marginTop: 20,
90
+ };
91
+
92
+ export const inlineStyle_441_16 = {
93
+ flex: 1,
94
+ paddingVertical: 14,
95
+ borderRadius: 12,
96
+ borderWidth: 1,
97
+ borderColor: '#64748B',
98
+ alignItems: 'center',
99
+ };
100
+
101
+ export const inlineStyle_445_22 = {
102
+ fontSize: 16,
103
+ fontWeight: '600',
104
+ color: '#64748B',
105
+ };
106
+
107
+ export const inlineStyle_448_16 = {
108
+ flex: 1,
109
+ paddingVertical: 14,
110
+ borderRadius: 12,
111
+ backgroundColor: '#007bff',
112
+ alignItems: 'center',
113
+ };
114
+
115
+ export const inlineStyle_452_22 = {
116
+ fontSize: 16,
117
+ fontWeight: '600',
118
+ color: '#fff',
119
+ };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import {View} from 'react-native';
3
+ import Contracts from '@controleonline/ui-contracts/src/react/components/contracts';
4
+
5
+ const ContractsTab = ({client, customStyles, parentCompanyIri}) => {
6
+ return (
7
+ <View style={customStyles.tabContent}>
8
+ <Contracts client={client} parentCompanyIri={parentCompanyIri} />
9
+ </View>
10
+ );
11
+ };
12
+
13
+ export default ContractsTab;
@@ -0,0 +1,463 @@
1
+ import { useStores } from '@store';
2
+ import React, { useEffect, useState } from 'react';
3
+
4
+ import {
5
+ Modal,
6
+ Text,
7
+ TextInput,
8
+ TouchableOpacity,
9
+ ScrollView,
10
+ View,
11
+ Keyboard,
12
+ } from 'react-native';
13
+
14
+ import AnimatedModal from '@controleonline/ui-common/src/react/components/AnimatedModal';
15
+ import FeatherIcon from 'react-native-vector-icons/Feather';
16
+ import Icon from 'react-native-vector-icons/MaterialIcons';
17
+ import {useMessage} from '@controleonline/ui-common/src/react/components/MessageService';
18
+
19
+ import {
20
+ inlineStyle_265_6,
21
+ inlineStyle_266_12,
22
+ inlineStyle_279_14,
23
+ inlineStyle_288_16,
24
+ inlineStyle_291_49,
25
+ inlineStyle_299_10,
26
+ inlineStyle_302_16,
27
+ inlineStyle_303_18,
28
+ inlineStyle_304_18,
29
+ inlineStyle_308_18,
30
+ inlineStyle_314_24,
31
+ inlineStyle_324_16,
32
+ inlineStyle_325_18,
33
+ inlineStyle_327_14,
34
+ inlineStyle_341_16,
35
+ inlineStyle_343_14,
36
+ inlineStyle_350_20,
37
+ } from './DocumentsTab.styles';
38
+
39
+ const DocumentsTab = ({ client, customStyles, isEditing, onUpdateClient }) => {
40
+ const {showError, showSuccess, showDialog} = useMessage();
41
+ const [documents, setDocuments] = useState([]);
42
+ const [showModal, setShowModal] = useState(false);
43
+ const [editingItem, setEditingItem] = useState(null);
44
+ const [formData, setFormData] = useState({});
45
+ const peopleStore = useStores(state => state.people);
46
+ const peopleGetters = peopleStore.getters;
47
+ const { currentCompany } = peopleGetters;
48
+
49
+ const documentsStore = useStores(state => state.documents) || {};
50
+ const actionsDocuments = documentsStore.actions || {};
51
+ const documentsTypesStore = useStores(state => state.documentsTypes) || {};
52
+ const actionsDocumentsType = documentsTypesStore.actions || {};
53
+ const getters = documentsTypesStore.getters || {};
54
+ const { items = [] } = getters;
55
+
56
+ // Funções de máscara
57
+ const applyMask = (value, type) => {
58
+ if (!value) {
59
+ return '';
60
+ }
61
+ const numbers = value.replace(/\D/g, '');
62
+
63
+ const docTypeItem = items.find(item => item['@id'] === type);
64
+ const docType = docTypeItem?.documentType?.toUpperCase();
65
+
66
+ if (docType === 'CPF') {
67
+ // Máscara CPF: 000.000.000-00
68
+ return numbers
69
+ .slice(0, 11)
70
+ .replace(/(\d{3})(\d)/, '$1.$2')
71
+ .replace(/(\d{3})(\d)/, '$1.$2')
72
+ .replace(/(\d{3})(\d{1,2})$/, '$1-$2');
73
+ }
74
+
75
+ if (docType === 'CNPJ') {
76
+ // Máscara CNPJ: 00.000.000/0000-00
77
+ return numbers
78
+ .slice(0, 14)
79
+ .replace(/(\d{2})(\d)/, '$1.$2')
80
+ .replace(/(\d{3})(\d)/, '$1.$2')
81
+ .replace(/(\d{3})(\d)/, '$1/$2')
82
+ .replace(/(\d{4})(\d{1,2})$/, '$1-$2');
83
+ }
84
+
85
+ if (docType === 'RG') {
86
+ // Máscara RG: 00.000.000-0
87
+ return numbers
88
+ .slice(0, 9)
89
+ .replace(/(\d{2})(\d)/, '$1.$2')
90
+ .replace(/(\d{3})(\d)/, '$1.$2')
91
+ .replace(/(\d{3})(\d{1})$/, '$1-$2');
92
+ }
93
+
94
+ if (docType === 'IE') {
95
+ // Máscara IE: 000.000.000.000 (varia por estado, usando formato genérico)
96
+ return numbers
97
+ .slice(0, 12)
98
+ .replace(/(\d{3})(\d)/, '$1.$2')
99
+ .replace(/(\d{3})(\d)/, '$1.$2')
100
+ .replace(/(\d{3})(\d)/, '$1.$2');
101
+ }
102
+
103
+ if (docType === 'IM') {
104
+ // Máscara IM: 00.000.000-0 (varia por município, usando formato genérico)
105
+ return numbers
106
+ .slice(0, 9)
107
+ .replace(/(\d{2})(\d)/, '$1.$2')
108
+ .replace(/(\d{3})(\d)/, '$1.$2')
109
+ .replace(/(\d{3})(\d{1})$/, '$1-$2');
110
+ }
111
+
112
+ return value;
113
+ };
114
+
115
+ const removeMask = value => {
116
+ return value ? value.replace(/\D/g, '') : '';
117
+ };
118
+
119
+ useEffect(() => {
120
+ if (!client) return;
121
+ const rawDocuments = Array.isArray(client?.document)
122
+ ? client.document.map(d => ({
123
+ id: d.id || d['@id'],
124
+ type:
125
+ typeof d.documentType === 'object'
126
+ ? d.documentType?.['@id'] || d.documentType?.id || 'Documento'
127
+ : d.documentType || 'Documento',
128
+ value: d.document,
129
+ }))
130
+ : [];
131
+
132
+ // Always load the full document_types catalog so the type selector is available
133
+ // for new companies/people (no company_document links yet). Filtering by peopleType
134
+ // happens in getAvailableDocumentTypes.
135
+ actionsDocumentsType.getItems({});
136
+ setDocuments(rawDocuments);
137
+ }, [client, currentCompany]);
138
+
139
+ const openModal = (item = null) => {
140
+ setEditingItem(item);
141
+ setFormData(item || {});
142
+ setShowModal(true);
143
+ };
144
+
145
+ const closeModal = () => {
146
+ setShowModal(false);
147
+ setEditingItem(null);
148
+ setFormData({});
149
+ };
150
+
151
+ const normalizePeopleType = (value) => {
152
+ const v = String(value || '').toLowerCase();
153
+ if (v === 'f' || v === 'fisica' || v === 'pf' || v === 'pessoa_fisica') return 'F';
154
+ if (v === 'j' || v === 'juridica' || v === 'pj' || v === 'pessoa_juridica') return 'J';
155
+ return String(value || '');
156
+ };
157
+
158
+ const getAvailableDocumentTypes = () => {
159
+ const clientPeopleType = normalizePeopleType(client?.peopleType);
160
+ const availableTypes = items.filter(type => {
161
+ const typePeople = normalizePeopleType(type.peopleType);
162
+ // If type has no peopleType, keep it available (catalog may omit the field)
163
+ if (!typePeople) return true;
164
+ return typePeople === clientPeopleType;
165
+ });
166
+
167
+ // Se estiver editando, permite o tipo atual + tipos não utilizados
168
+ if (editingItem) {
169
+ return availableTypes.filter(type => {
170
+ const isCurrentType = type['@id'] === editingItem.type;
171
+ const isAlreadyUsed = documents.some(doc => doc.type === type['@id']);
172
+ return isCurrentType || !isAlreadyUsed;
173
+ });
174
+ }
175
+
176
+ // Se estiver adicionando, mostra apenas tipos não utilizados
177
+ return availableTypes.filter(type => {
178
+ return !documents.some(doc => doc.type === type['@id']);
179
+ });
180
+ };
181
+
182
+ const getFilteredDocuments = () => {
183
+ const isPessoaFisica =
184
+ client?.peopleType === 'F' || client?.peopleType === 'fisica';
185
+ const isPessoaJuridica =
186
+ client?.peopleType === 'J' || client?.peopleType === 'juridica';
187
+
188
+ return documents.filter(doc => {
189
+ const docTypeItem = items.find(item => item['@id'] === doc.type);
190
+ const docType = docTypeItem?.documentType?.toUpperCase();
191
+
192
+ if (isPessoaFisica) {
193
+ return docType === 'RG' || docType === 'CPF';
194
+ }
195
+
196
+ if (isPessoaJuridica) {
197
+ return docType === 'CNPJ' || docType === 'IE' || docType === 'IM';
198
+ }
199
+
200
+ return true;
201
+ });
202
+ };
203
+ const handleSave = async () => {
204
+ if (!formData.value || !formData.type) {
205
+ showError('Documento e tipo são obrigatórios.');
206
+ return;
207
+ }
208
+
209
+ if (!editingItem) {
210
+ const existingDoc = documents.find(doc => doc.type === formData.type);
211
+ if (existingDoc) {
212
+ const selectedType = items.find(item => item['@id'] === formData.type);
213
+ showError(`Já existe um documento do tipo ${selectedType?.documentType}.`);
214
+ return;
215
+ }
216
+ }
217
+
218
+ try {
219
+ // Remove a máscara antes de salvar
220
+ const cleanValue = removeMask(formData.value);
221
+
222
+ const documentData = {
223
+ document: cleanValue,
224
+ documentType: formData.type,
225
+ people: client['@id'],
226
+ };
227
+
228
+ if (editingItem) {
229
+ documentData.id = editingItem.id;
230
+ }
231
+
232
+ await actionsDocuments.save(documentData);
233
+
234
+ const documentItem = {
235
+ id: editingItem?.id || Date.now(),
236
+ value: cleanValue,
237
+ type: formData.type,
238
+ };
239
+
240
+ const updatedDocuments = editingItem
241
+ ? documents.map(d => (d.id === editingItem.id ? documentItem : d))
242
+ : [...documents, documentItem];
243
+
244
+ setDocuments(updatedDocuments);
245
+ if (onUpdateClient) {
246
+ const fullDocumentData = updatedDocuments.map(d => ({
247
+ id: d.id,
248
+ '@id': d.id,
249
+ document: d.value,
250
+ documentType: d.type,
251
+ }));
252
+ onUpdateClient('document', fullDocumentData);
253
+ }
254
+
255
+ showSuccess(
256
+ `Documento ${editingItem ? 'atualizado' : 'adicionado'} com sucesso!`,
257
+ );
258
+ closeModal();
259
+ } catch (error) {
260
+ showError(
261
+ `Falha ao ${editingItem ? 'atualizar' : 'adicionar'} documento. Tente novamente.`,
262
+ );
263
+ }
264
+ };
265
+
266
+ const handleDelete = id => {
267
+ showDialog({
268
+ title: 'Confirmar exclusão',
269
+ message: 'Deseja realmente remover este item?',
270
+ confirmLabel: 'Remover',
271
+ cancelLabel: 'Cancelar',
272
+ onConfirm: async () => {
273
+ try {
274
+ await actionsDocuments.remove(id);
275
+ const updatedDocuments = documents.filter(d => d.id !== id);
276
+ setDocuments(updatedDocuments);
277
+ if (onUpdateClient) {
278
+ const fullDocumentData = updatedDocuments.map(d => ({
279
+ id: d.id,
280
+ '@id': d.id,
281
+ document: d.value,
282
+ documentType: d.type,
283
+ }));
284
+ onUpdateClient('document', fullDocumentData);
285
+ }
286
+ showSuccess('Documento removido com sucesso!');
287
+ } catch (error) {
288
+ console.log('Delete error:', error);
289
+ showError('Falha ao remover documento. Tente novamente.');
290
+ }
291
+ },
292
+ });
293
+ };
294
+
295
+ const renderModal = () => (
296
+ <AnimatedModal
297
+ visible={showModal}
298
+ onRequestClose={closeModal}
299
+ style={inlineStyle_265_6}>
300
+ <View style={inlineStyle_266_12}>
301
+ {/* Header */}
302
+ <View style={inlineStyle_279_14}>
303
+ <Text style={inlineStyle_288_16}>
304
+ {editingItem ? 'Editar Documento' : 'Adicionar Documento'}
305
+ </Text>
306
+ <TouchableOpacity onPress={closeModal} style={inlineStyle_291_49}>
307
+ <Icon name="close" size={20} color="#64748B" />
308
+ </TouchableOpacity>
309
+ </View>
310
+
311
+ <ScrollView
312
+ style={inlineStyle_299_10}
313
+ keyboardShouldPersistTaps="handled"
314
+ keyboardDismissMode="on-drag">
315
+ <View style={inlineStyle_302_16}>
316
+ <Text style={inlineStyle_303_18}>Tipo</Text>
317
+ <View style={inlineStyle_304_18}>
318
+ {getAvailableDocumentTypes().map(type => (
319
+ <TouchableOpacity
320
+ key={type.documentType}
321
+ style={inlineStyle_308_18({
322
+ formData: formData,
323
+ type: type,
324
+ })}
325
+ onPress={() => setFormData({ ...formData, type: type['@id'] })}>
326
+ <Text style={inlineStyle_314_24({
327
+ formData: formData,
328
+ type: type,
329
+ })}>
330
+ {type.documentType}
331
+ </Text>
332
+ </TouchableOpacity>
333
+ ))}
334
+ </View>
335
+ </View>
336
+
337
+ <View style={inlineStyle_324_16}>
338
+ <Text style={inlineStyle_325_18}>Número do Documento</Text>
339
+ <TextInput
340
+ style={inlineStyle_327_14}
341
+ placeholder="Número do documento"
342
+ value={applyMask(formData.value || '', formData.type)}
343
+ onChangeText={text => {
344
+ const cleanText = removeMask(text);
345
+ setFormData({ ...formData, value: cleanText });
346
+ }}
347
+ keyboardType="numeric"
348
+ />
349
+ </View>
350
+
351
+ <View style={inlineStyle_341_16}>
352
+ <TouchableOpacity
353
+ style={inlineStyle_343_14}
354
+ onPress={() => {
355
+ Keyboard.dismiss();
356
+ closeModal();
357
+ }}>
358
+ <Text style={inlineStyle_350_20}>Cancelar</Text>
359
+ </TouchableOpacity>
360
+ <TouchableOpacity
361
+ style={customStyles.saveButton}
362
+ onPress={() => {
363
+ Keyboard.dismiss();
364
+ handleSave();
365
+ }}>
366
+ <FeatherIcon
367
+ name="save"
368
+ size={16}
369
+ color={customStyles.iconButtonPrimaryIcon.color}
370
+ />
371
+ <Text style={customStyles.saveButtonText}>Salvar</Text>
372
+ </TouchableOpacity>
373
+ </View>
374
+ </ScrollView>
375
+ </View>
376
+ </AnimatedModal>
377
+ );
378
+ return (
379
+ <>
380
+ <View style={customStyles.tabContent}>
381
+ <View style={customStyles.section}>
382
+ <View style={customStyles.sectionHeader}>
383
+ <Text style={customStyles.sectionTitle} numberOfLines={1}>
384
+ Documentos
385
+ </Text>
386
+ {isEditing && (
387
+ <TouchableOpacity
388
+ onPress={() => openModal()}
389
+ style={customStyles.iconButtonPrimary}>
390
+ <Icon
391
+ name="add"
392
+ size={20}
393
+ color={customStyles.iconButtonPrimaryIcon.color}
394
+ />
395
+ </TouchableOpacity>
396
+ )}
397
+ </View>
398
+ {getFilteredDocuments().length === 0 ? (
399
+ <Text style={customStyles.emptyText}>
400
+ Nenhum documento cadastrado
401
+ </Text>
402
+ ) : (
403
+ getFilteredDocuments().map(doc => (
404
+ <View
405
+ key={doc.id}
406
+ style={[
407
+ customStyles.listItem,
408
+ customStyles.listItemWithActions,
409
+ ]}>
410
+ <View style={customStyles.itemContent}>
411
+ <Icon
412
+ name="description"
413
+ size={20}
414
+ color={customStyles.cardItemIcon.color}
415
+ />
416
+ <View>
417
+ <Text style={customStyles.itemText}>
418
+ {applyMask(String(doc.value || ''), doc.type)}
419
+ </Text>
420
+ <Text style={customStyles.itemSubtext}>
421
+ {items.find(i => i['@id'] === doc.type)?.documentType ||
422
+ String(doc.type || '')}
423
+ </Text>
424
+ </View>
425
+ </View>
426
+ {isEditing && (
427
+ <View
428
+ style={[
429
+ customStyles.itemActions,
430
+ customStyles.itemActionsPinned,
431
+ ]}>
432
+ <TouchableOpacity
433
+ onPress={() => openModal(doc)}
434
+ style={customStyles.iconButtonGhost}>
435
+ <FeatherIcon
436
+ name="edit-2"
437
+ size={16}
438
+ color={customStyles.iconButtonGhostIcon.color}
439
+ />
440
+ </TouchableOpacity>
441
+ <TouchableOpacity
442
+ onPress={() => handleDelete(doc.id)}
443
+ style={customStyles.iconButtonGhost}>
444
+ <FeatherIcon
445
+ name="trash-2"
446
+ size={16}
447
+ color={customStyles.iconButtonGhostIcon.color}
448
+ />
449
+ </TouchableOpacity>
450
+ </View>
451
+ )}
452
+ </View>
453
+ ))
454
+ )}
455
+ </View>
456
+ </View>
457
+ {renderModal()}
458
+ </>
459
+ );
460
+ };
461
+
462
+ export default DocumentsTab;
463
+