@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.
- package/.github/agents/developer.agent.md +30 -0
- package/.github/agents/devops.agent.md +30 -0
- package/.github/agents/qa.agent.md +30 -0
- package/.github/agents/security.agent.md +30 -0
- package/.scrutinizer.yml +20 -0
- package/AGENTS.md +50 -0
- package/FUNDING.yml +1 -0
- package/README.md +23 -0
- package/docs/technical/Client-Details-Franquia-Filial.md +18 -0
- package/docs/technical/Client-Details-People-Name-Case.md +103 -0
- package/docs/technical/Cliente-Vendedor-Vinculo-e-Permissoes.md +152 -0
- package/docs/wiki.md +1 -0
- package/package.json +32 -0
- package/scripts/validate-wiki-pointer.cjs +13 -0
- package/src/react/components/tabs/AddressesTab.js +494 -0
- package/src/react/components/tabs/CategoriesTab.js +196 -0
- package/src/react/components/tabs/ClassificationChips.js +494 -0
- package/src/react/components/tabs/ContactTab.js +621 -0
- package/src/react/components/tabs/ContactTab.styles.js +119 -0
- package/src/react/components/tabs/ContractsTab.js +13 -0
- package/src/react/components/tabs/DocumentsTab.js +463 -0
- package/src/react/components/tabs/DocumentsTab.styles.js +133 -0
- package/src/react/components/tabs/EmployeeContactRow.js +83 -0
- package/src/react/components/tabs/EmployeesTab.js +454 -0
- package/src/react/components/tabs/EmployeesTab.styles.js +172 -0
- package/src/react/components/tabs/FiscalTab.js +6 -0
- package/src/react/components/tabs/FranchiseCommissionSection.js +307 -0
- package/src/react/components/tabs/FranchiseLinksManageModal.js +215 -0
- package/src/react/components/tabs/FranchiseLinksTab.js +282 -0
- package/src/react/components/tabs/GeneralTab.js +466 -0
- package/src/react/components/tabs/GeneralTab.styles.js +101 -0
- package/src/react/components/tabs/MediaTab.js +356 -0
- package/src/react/components/tabs/PeopleCategoriesPanel.js +601 -0
- package/src/react/components/tabs/ProductsTab.js +236 -0
- package/src/react/components/tabs/ProductsTab.styles.js +143 -0
- package/src/react/components/tabs/SalesmanCommissionBlock.js +120 -0
- package/src/react/components/tabs/SalesmanManageModal.js +208 -0
- package/src/react/components/tabs/SalesmanTab.js +436 -0
- package/src/react/components/tabs/SalesmanTab.styles.js +4 -0
- package/src/react/components/tabs/UserApiKeyModal.js +90 -0
- package/src/react/components/tabs/UserFormModal.js +301 -0
- package/src/react/components/tabs/UsersTab.js +426 -0
- package/src/react/components/tabs/UsersTab.styles.js +283 -0
- package/src/react/components/tabs/UsersTab.styles.test.js +42 -0
- package/src/react/components/tabs/contactMutationMessage.js +18 -0
- package/src/react/components/tabs/employeeContacts.js +214 -0
- package/src/react/components/tabs/employeeUnlink.js +68 -0
- package/src/react/components/tabs/employeesTabHelpers.js +227 -0
- package/src/react/components/tabs/franchiseCommissionHelpers.js +134 -0
- package/src/react/components/tabs/franchiseLinksTab.helpers.js +247 -0
- package/src/react/components/tabs/generalTabHelpers.js +120 -0
- package/src/react/components/tabs/salesmanTab.helpers.js +114 -0
- package/src/react/components/tabs/salesmanTabHelpers.js +173 -0
- package/src/react/components/tabs/salesmanTabMedia.js +55 -0
- package/src/react/components/tabs/salesmanTabSession.js +39 -0
- package/src/react/components/tabs/useEmployeeUnlink.js +58 -0
- package/src/react/components/tabs/useFranchiseLinksManage.js +215 -0
- package/src/react/components/tabs/useSalesmanManage.js +222 -0
- package/src/react/components/tabs/usersTabHelpers.js +221 -0
- package/src/react/domain/productSupplierFlow.js +37 -0
- package/src/react/index.js +4 -0
- package/src/react/pages/ClientDetailsSkeleton.js +84 -0
- package/src/react/pages/clientDetailsHelpers.js +17 -0
- package/src/react/pages/clients.js +27 -0
- package/src/react/pages/details.js +529 -0
- package/src/react/pages/details.page.styles.js +105 -0
- package/src/react/pages/details.styles.js +15 -0
- package/src/react/pages/employees.js +69 -0
- package/src/react/pages/franchisees.js +30 -0
- package/src/react/pages/prospects.js +21 -0
- package/src/react/pages/provider.js +21 -0
- package/src/react/router/routes.js +104 -0
- package/src/react/styles/details.js +571 -0
- package/src/store/people_categories/index.js +72 -0
- package/src/tests/browser/manager/client-details-create-user.spec.js +321 -0
- package/src/tests/browser/manager/client-details-employee-unlink.spec.js +215 -0
- package/src/tests/browser/manager/client-details-employees-refresh.spec.js +271 -0
- package/src/tests/browser/manager/client-details-enable.spec.js +241 -0
- package/src/tests/browser/manager/client-details-fiscal-tabs.spec.js +410 -0
- package/src/tests/browser/manager/client-details-franchise-commission.spec.js +318 -0
- package/src/tests/browser/manager/client-details-franchise-links.spec.js +365 -0
- package/src/tests/browser/manager/client-details-media.spec.js +405 -0
- package/src/tests/browser/manager/client-details-relationships.spec.js +313 -0
- package/src/tests/browser/manager/client-details-salesman-commission.spec.js +291 -0
- package/src/tests/browser/manager/client-details-salesman-manage.spec.js +448 -0
- package/src/tests/react/components/tabs/contactMutationMessage.test.js +25 -0
- package/src/tests/react/components/tabs/employeeContacts.test.js +266 -0
- package/src/tests/react/components/tabs/employeeUnlink.test.js +67 -0
- package/src/tests/react/components/tabs/employeesTabHelpers.media.test.js +78 -0
- package/src/tests/react/components/tabs/employeesTabHelpers.test.js +64 -0
- package/src/tests/react/components/tabs/fiscalTabContract.test.mjs +29 -0
- package/src/tests/react/components/tabs/franchiseCommissionHelpers.test.js +128 -0
- package/src/tests/react/components/tabs/franchiseLinksTab.helpers.test.mjs +166 -0
- package/src/tests/react/components/tabs/generalTabHelpers.test.js +130 -0
- package/src/tests/react/components/tabs/peopleMediaTabs.test.js +322 -0
- package/src/tests/react/components/tabs/salesmanTab.helpers.test.mjs +75 -0
- package/src/tests/react/components/tabs/salesmanTabHelpers.test.js +142 -0
- package/src/tests/react/components/tabs/salesmanTabHelpers.test.mjs +92 -0
- package/src/tests/react/components/tabs/usersTabHelpers.test.js +59 -0
- package/src/tests/react/domain/productSupplierFlow.test.mjs +55 -0
- package/src/tests/react/pages/clientDetailsHelpers.test.js +98 -0
- 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
|
+
|