@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,282 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+
3
+ import {
4
+ ActivityIndicator,
5
+ Text,
6
+ TouchableOpacity,
7
+ View,
8
+ } from 'react-native';
9
+
10
+ import FeatherIcon from 'react-native-vector-icons/Feather';
11
+ import { useNavigation } from '@react-navigation/native';
12
+ import { useStore } from '@store';
13
+ import PeopleAvatar from '@controleonline/ui-people/src/react/components/PeopleAvatar';
14
+ import { useMessage } from '@controleonline/ui-common/src/react/components/MessageService';
15
+ import Icon from 'react-native-vector-icons/MaterialIcons';
16
+ import { colors } from '@controleonline/../../src/styles/colors';
17
+
18
+ import {
19
+ buildFranchiseLinkReadParams,
20
+ buildFranchiseLinksFromPeopleLinks,
21
+ franchiseLinkTypeLabel,
22
+ normalizeFranchiseLink,
23
+ } from './franchiseLinksTab.helpers';
24
+ import { extractId } from './salesmanTabHelpers';
25
+ import { resolveAppType } from './salesmanTabSession';
26
+ import FranchiseLinksManageModal from './FranchiseLinksManageModal';
27
+ import { useFranchiseLinksManage } from './useFranchiseLinksManage';
28
+
29
+ const FranchiseLinksTab = ({
30
+ client,
31
+ customStyles,
32
+ emptyText,
33
+ errorText,
34
+ }) => {
35
+ const navigation = useNavigation();
36
+ const { showDialog, showError, showSuccess } = useMessage();
37
+
38
+ const peopleStore = useStore('people');
39
+ const peopleLinkStore = useStore('people_link');
40
+ const peopleActions = peopleStore?.actions || {};
41
+ const getPeopleLinks = peopleLinkStore?.actions?.getItems;
42
+ const savePeopleLink = peopleLinkStore?.actions?.save;
43
+ const removePeopleLink = peopleLinkStore?.actions?.remove;
44
+
45
+ const [links, setLinks] = useState([]);
46
+ const [isLoading, setIsLoading] = useState(false);
47
+ const [error, setError] = useState('');
48
+
49
+ const clientId = extractId(client?.id || client?.['@id']);
50
+ const appType = useMemo(() => resolveAppType(), []);
51
+
52
+ useEffect(() => {
53
+ let cancelled = false;
54
+
55
+ if (!clientId || !getPeopleLinks) {
56
+ setLinks([]);
57
+ setError('');
58
+ return () => {
59
+ cancelled = true;
60
+ };
61
+ }
62
+
63
+ setIsLoading(true);
64
+ setError('');
65
+
66
+ // company only — API zeros the collection when linkType[]/enable are sent
67
+ // (task-641). Filter franchisee client-side via buildFranchiseLinksFromPeopleLinks.
68
+ getPeopleLinks(buildFranchiseLinkReadParams(clientId))
69
+ .then(items => {
70
+ if (cancelled) {
71
+ return;
72
+ }
73
+ const next = buildFranchiseLinksFromPeopleLinks(items, {
74
+ companyId: clientId,
75
+ });
76
+ setLinks(next);
77
+ })
78
+ .catch(() => {
79
+ if (!cancelled) {
80
+ setLinks([]);
81
+ setError(errorText || 'Não foi possível carregar os vínculos.');
82
+ }
83
+ })
84
+ .finally(() => {
85
+ if (!cancelled) {
86
+ setIsLoading(false);
87
+ }
88
+ });
89
+
90
+ return () => {
91
+ cancelled = true;
92
+ };
93
+ }, [clientId, errorText, getPeopleLinks]);
94
+
95
+ const {
96
+ canManage,
97
+ linkedNormalized,
98
+ showManageModal,
99
+ editingManageLink,
100
+ manageForm,
101
+ setManageForm,
102
+ isManageSaving,
103
+ franchiseOptions,
104
+ openManageModal,
105
+ closeManageModal,
106
+ handleManageSave,
107
+ handleManageDelete,
108
+ } = useFranchiseLinksManage({
109
+ appType,
110
+ client,
111
+ clientId,
112
+ links,
113
+ setLinks,
114
+ setIsLoading,
115
+ peopleActions,
116
+ getPeopleLinks,
117
+ savePeopleLink,
118
+ removePeopleLink,
119
+ showDialog,
120
+ showError,
121
+ showSuccess,
122
+ });
123
+
124
+ return (
125
+ <>
126
+ <View style={customStyles.tabContent}>
127
+ <View style={customStyles.section}>
128
+ {canManage ? (
129
+ <View
130
+ style={{
131
+ flexDirection: 'row',
132
+ justifyContent: 'flex-end',
133
+ marginBottom: 8,
134
+ }}>
135
+ <TouchableOpacity
136
+ onPress={() => openManageModal(null)}
137
+ accessibilityLabel="Vincular franquia ou filial"
138
+ testID="franchise-links-add-btn">
139
+ <Icon name="add" size={24} color={colors.primary} />
140
+ </TouchableOpacity>
141
+ </View>
142
+ ) : null}
143
+ {isLoading ? (
144
+ <View style={{ paddingVertical: 16, alignItems: 'center' }}>
145
+ <ActivityIndicator
146
+ size="small"
147
+ color={customStyles.loadingIndicator?.color}
148
+ />
149
+ </View>
150
+ ) : error ? (
151
+ <Text style={customStyles.emptyText}>{error}</Text>
152
+ ) : !links || links.length === 0 ? (
153
+ <Text style={customStyles.emptyText}>
154
+ {emptyText || 'Nenhuma franquia ou filial vinculada'}
155
+ </Text>
156
+ ) : (
157
+ links.map(item => {
158
+ const linkId = extractId(item?.id || item?.['@id']);
159
+ const linkedPeople = item?.people || {};
160
+ const typeLabel = franchiseLinkTypeLabel(
161
+ item?.linkType,
162
+ global.t,
163
+ );
164
+
165
+ return (
166
+ <TouchableOpacity
167
+ key={String(item?.id || item?.['@id'])}
168
+ style={[
169
+ customStyles.listItem,
170
+ customStyles.listItemWithEndAction,
171
+ ]}
172
+ activeOpacity={0.8}
173
+ onPress={() => {
174
+ const targetId = extractId(
175
+ linkedPeople?.id || linkedPeople?.['@id'],
176
+ );
177
+ if (!targetId) {
178
+ return;
179
+ }
180
+ peopleActions?.setItem?.(linkedPeople);
181
+ navigation.push('ClientDetails', { clientId: targetId });
182
+ }}
183
+ testID={`franchise-link-row-${linkId}`}>
184
+ <View style={customStyles.itemContent}>
185
+ <PeopleAvatar
186
+ people={linkedPeople}
187
+ size={40}
188
+ backgroundColor={
189
+ customStyles.listAvatarBrand?.backgroundColor
190
+ }
191
+ borderColor={customStyles.listAvatarBrand?.borderColor}
192
+ borderWidth={2}
193
+ textColor={customStyles.listAvatarText?.color}
194
+ style={customStyles.listAvatar}
195
+ />
196
+ <View style={{ flex: 1 }}>
197
+ <Text style={customStyles.itemText}>
198
+ {String(linkedPeople?.name || '-')}
199
+ </Text>
200
+ <Text style={customStyles.itemSubtext}>
201
+ {`ID: ${extractId(linkedPeople?.id || linkedPeople?.['@id']) || '-'}`}
202
+ {linkedPeople?.alias
203
+ ? ` - ${String(linkedPeople.alias)}`
204
+ : ''}
205
+ </Text>
206
+ <Text
207
+ style={[
208
+ customStyles.itemSubtext,
209
+ { marginTop: 2, fontWeight: '600' },
210
+ ]}>
211
+ {typeLabel}
212
+ </Text>
213
+ <Text style={customStyles.itemSubtext}>
214
+ {`Comissão: ${Number(item?.comission || 0)}% · Mínima: ${Number(item?.minimum_comission ?? item?.minimumComission ?? 0)}`}
215
+ </Text>
216
+ </View>
217
+ </View>
218
+ {canManage ? (
219
+ <View style={{ flexDirection: 'row' }}>
220
+ <TouchableOpacity
221
+ onPress={() => {
222
+ const link = linkedNormalized.find(
223
+ entry =>
224
+ entry.id === linkId ||
225
+ entry.linkedId ===
226
+ extractId(linkedPeople?.id || linkedPeople?.['@id']),
227
+ );
228
+ openManageModal(
229
+ link || normalizeFranchiseLink(item),
230
+ );
231
+ }}
232
+ style={{ padding: 6 }}
233
+ testID={`franchise-links-edit-${linkId}`}>
234
+ <Icon name="edit" size={18} color={colors.primary} />
235
+ </TouchableOpacity>
236
+ <TouchableOpacity
237
+ onPress={() => {
238
+ const link = linkedNormalized.find(
239
+ entry =>
240
+ entry.id === linkId ||
241
+ entry.linkedId ===
242
+ extractId(linkedPeople?.id || linkedPeople?.['@id']),
243
+ );
244
+ handleManageDelete(link || { id: linkId });
245
+ }}
246
+ style={{ padding: 6 }}
247
+ testID={`franchise-links-delete-${linkId}`}>
248
+ <Icon name="delete-outline" size={18} color="#DC2626" />
249
+ </TouchableOpacity>
250
+ </View>
251
+ ) : (
252
+ <View style={customStyles.iconButtonGhost}>
253
+ <FeatherIcon
254
+ name="chevron-right"
255
+ size={16}
256
+ color={customStyles.iconButtonGhostIcon?.color}
257
+ />
258
+ </View>
259
+ )}
260
+ </TouchableOpacity>
261
+ );
262
+ })
263
+ )}
264
+ </View>
265
+ </View>
266
+ {canManage ? (
267
+ <FranchiseLinksManageModal
268
+ visible={showManageModal}
269
+ onClose={closeManageModal}
270
+ editingLink={editingManageLink}
271
+ formData={manageForm}
272
+ setFormData={setManageForm}
273
+ franchiseOptions={franchiseOptions}
274
+ isSaving={isManageSaving}
275
+ onSave={handleManageSave}
276
+ />
277
+ ) : null}
278
+ </>
279
+ );
280
+ };
281
+
282
+ export default FranchiseLinksTab;