@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,39 @@
1
+ import { app_type } from '@appType';
2
+
3
+ const resolveAppType = () => {
4
+ // MANAGER is normally defined by the build. localStorage is only populated
5
+ // when an ADMIN build lets the user switch views, so it cannot be the source
6
+ // of truth for regular MANAGER deployments.
7
+ if (app_type) {
8
+ return String(app_type).trim();
9
+ }
10
+ try {
11
+ if (typeof localStorage !== 'undefined') {
12
+ return String(localStorage.getItem('app-type') || '').trim();
13
+ }
14
+ } catch (_) {
15
+ /* noop */
16
+ }
17
+ return '';
18
+ };
19
+
20
+ const resolveSessionUser = authStore => {
21
+ const fromStore = authStore?.getters?.user || authStore?.state?.user;
22
+ if (fromStore && typeof fromStore === 'object') {
23
+ return fromStore;
24
+ }
25
+ try {
26
+ if (typeof localStorage !== 'undefined') {
27
+ const raw = localStorage.getItem('session');
28
+ if (raw) {
29
+ return JSON.parse(raw);
30
+ }
31
+ }
32
+ } catch (_) {
33
+ /* noop */
34
+ }
35
+ return null;
36
+ };
37
+
38
+
39
+ export { resolveAppType, resolveSessionUser };
@@ -0,0 +1,58 @@
1
+ import {useCallback} from 'react';
2
+ import {
3
+ confirmEmployeeRemoval,
4
+ extractEmployeeUnlinkIds,
5
+ filterEmployeesAfterUnlink,
6
+ removeEmployeeFromCompany,
7
+ } from './employeeUnlink';
8
+
9
+ export const useEmployeeUnlink = ({
10
+ removePeople,
11
+ removePeopleLink,
12
+ showDialog,
13
+ showError,
14
+ showSuccess,
15
+ setEmployees,
16
+ }) => {
17
+ const handleRemoveEmployee = useCallback(
18
+ employee => {
19
+ const ids = extractEmployeeUnlinkIds(employee);
20
+ if (!ids.peopleId && !ids.linkId) {
21
+ showError?.('Não foi possível identificar o colaborador para remover.');
22
+ return;
23
+ }
24
+
25
+ confirmEmployeeRemoval({
26
+ showDialog,
27
+ onConfirm: async () => {
28
+ try {
29
+ await removeEmployeeFromCompany({
30
+ ...ids,
31
+ removePeople,
32
+ removePeopleLink,
33
+ });
34
+ showSuccess?.('Colaborador removido com sucesso.');
35
+ setEmployees?.(prev => filterEmployeesAfterUnlink(prev, ids));
36
+ } catch (error) {
37
+ showError?.(
38
+ error?.message ||
39
+ 'Não foi possível remover o colaborador. Verifique vínculos e tente novamente.',
40
+ );
41
+ }
42
+ },
43
+ });
44
+ },
45
+ [
46
+ removePeople,
47
+ removePeopleLink,
48
+ showDialog,
49
+ showError,
50
+ showSuccess,
51
+ setEmployees,
52
+ ],
53
+ );
54
+
55
+ return {handleRemoveEmployee};
56
+ };
57
+
58
+ export default useEmployeeUnlink;
@@ -0,0 +1,215 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ buildAvailableFranchiseOptions,
4
+ buildFranchiseLinkReadParams,
5
+ buildFranchiseSavePayload,
6
+ canManageFranchiseLinks,
7
+ normalizeFranchiseLink,
8
+ toPeopleIri,
9
+ } from './franchiseLinksTab.helpers';
10
+ import { extractId } from './salesmanTabHelpers';
11
+ import { buildFranchiseLinksFromPeopleLinks } from './franchiseLinksTab.helpers';
12
+ import { normalizeCollection } from './salesmanTabMedia';
13
+
14
+ /**
15
+ * Administrative manage (link/edit/remove) for FranchiseLinksTab when APP_TYPE=MANAGER.
16
+ */
17
+ export function useFranchiseLinksManage({
18
+ appType,
19
+ client,
20
+ clientId,
21
+ currentCompanyId,
22
+ links,
23
+ setLinks,
24
+ setIsLoading,
25
+ peopleActions,
26
+ getPeopleLinks,
27
+ savePeopleLink,
28
+ removePeopleLink,
29
+ showDialog,
30
+ showError,
31
+ showSuccess,
32
+ }) {
33
+ const canManage = useMemo(() => canManageFranchiseLinks(appType), [appType]);
34
+ const companyIri = useMemo(() => toPeopleIri(client), [client]);
35
+ const [availableCandidates, setAvailableCandidates] = useState([]);
36
+ const [linkedNormalized, setLinkedNormalized] = useState([]);
37
+ const [showManageModal, setShowManageModal] = useState(false);
38
+ const [editingManageLink, setEditingManageLink] = useState(null);
39
+ const [manageForm, setManageForm] = useState({
40
+ linkedIri: '',
41
+ linkType: 'franchisee',
42
+ comission: '',
43
+ minimumComission: '',
44
+ });
45
+ const [isManageSaving, setIsManageSaving] = useState(false);
46
+
47
+ useEffect(() => {
48
+ setLinkedNormalized(
49
+ (Array.isArray(links) ? links : [])
50
+ .map(item => normalizeFranchiseLink(item))
51
+ .filter(Boolean),
52
+ );
53
+ }, [links]);
54
+
55
+ useEffect(() => {
56
+ let cancelled = false;
57
+ if (!canManage || !peopleActions?.getItems) {
58
+ setAvailableCandidates([]);
59
+ return undefined;
60
+ }
61
+ // PJ candidates only
62
+ peopleActions
63
+ .getItems({
64
+ peopleType: 'J',
65
+ itemsPerPage: 100,
66
+ })
67
+ .then(response => {
68
+ if (!cancelled) {
69
+ const items = Array.isArray(response)
70
+ ? response
71
+ : normalizeCollection(response);
72
+ // Exclude current client itself
73
+ setAvailableCandidates(
74
+ items.filter(
75
+ item => extractId(item?.id || item?.['@id']) !== String(clientId),
76
+ ),
77
+ );
78
+ }
79
+ })
80
+ .catch(() => {
81
+ if (!cancelled) setAvailableCandidates([]);
82
+ });
83
+ return () => {
84
+ cancelled = true;
85
+ };
86
+ }, [canManage, peopleActions, clientId]);
87
+
88
+ const franchiseOptions = useMemo(
89
+ () =>
90
+ buildAvailableFranchiseOptions({
91
+ candidates: availableCandidates,
92
+ linkedItems: linkedNormalized,
93
+ editingLink: editingManageLink,
94
+ }),
95
+ [availableCandidates, linkedNormalized, editingManageLink],
96
+ );
97
+
98
+ const openManageModal = useCallback(link => {
99
+ setEditingManageLink(link || null);
100
+ setManageForm({
101
+ linkedIri: link?.linkedIri || '',
102
+ linkType: link?.linkType || 'franchisee',
103
+ comission: String(link?.comission ?? ''),
104
+ minimumComission: String(link?.minimumComission ?? ''),
105
+ });
106
+ setShowManageModal(true);
107
+ }, []);
108
+
109
+ const closeManageModal = useCallback(() => {
110
+ setShowManageModal(false);
111
+ setEditingManageLink(null);
112
+ }, []);
113
+
114
+ const reloadLinks = useCallback(async () => {
115
+ if (!getPeopleLinks || !clientId) {
116
+ return;
117
+ }
118
+ const response = await getPeopleLinks(buildFranchiseLinkReadParams(clientId));
119
+ const next = buildFranchiseLinksFromPeopleLinks(response, {
120
+ companyId: clientId,
121
+ });
122
+ setLinks(next);
123
+ }, [getPeopleLinks, clientId, setLinks]);
124
+
125
+ const handleManageSave = useCallback(async () => {
126
+ if (!savePeopleLink || !companyIri) {
127
+ showError?.('Não foi possível salvar o vínculo neste momento.');
128
+ return;
129
+ }
130
+ const payload = buildFranchiseSavePayload({
131
+ editingLink: editingManageLink,
132
+ formData: manageForm,
133
+ companyIri,
134
+ });
135
+ if (!payload) {
136
+ showError?.('Selecione uma pessoa jurídica e o tipo de vínculo.');
137
+ return;
138
+ }
139
+ setIsManageSaving(true);
140
+ try {
141
+ await savePeopleLink(payload);
142
+ showSuccess?.(
143
+ editingManageLink
144
+ ? 'Vínculo atualizado com sucesso!'
145
+ : 'Vínculo criado com sucesso!',
146
+ );
147
+ closeManageModal();
148
+ setIsLoading?.(true);
149
+ await reloadLinks();
150
+ } catch {
151
+ showError?.(
152
+ editingManageLink
153
+ ? 'Não foi possível atualizar o vínculo.'
154
+ : 'Não foi possível criar o vínculo.',
155
+ );
156
+ } finally {
157
+ setIsManageSaving(false);
158
+ setIsLoading?.(false);
159
+ }
160
+ }, [
161
+ savePeopleLink,
162
+ companyIri,
163
+ editingManageLink,
164
+ manageForm,
165
+ showError,
166
+ showSuccess,
167
+ closeManageModal,
168
+ setIsLoading,
169
+ reloadLinks,
170
+ ]);
171
+
172
+ const handleManageDelete = useCallback(
173
+ link => {
174
+ if (!link?.id || !removePeopleLink) {
175
+ showError?.('Não foi possível identificar o vínculo para remover.');
176
+ return;
177
+ }
178
+ showDialog?.({
179
+ title: 'Remover vínculo',
180
+ message: 'Deseja realmente remover este vínculo de franquia/filial?',
181
+ confirmLabel: 'Remover',
182
+ cancelLabel: 'Cancelar',
183
+ onConfirm: async () => {
184
+ try {
185
+ await removePeopleLink(link.id);
186
+ showSuccess?.('Vínculo removido com sucesso!');
187
+ setLinks(prev =>
188
+ (prev || []).filter(
189
+ item => extractId(item?.id) !== extractId(link.id),
190
+ ),
191
+ );
192
+ } catch {
193
+ showError?.('Não foi possível remover o vínculo.');
194
+ }
195
+ },
196
+ });
197
+ },
198
+ [removePeopleLink, showDialog, showError, showSuccess, setLinks],
199
+ );
200
+
201
+ return {
202
+ canManage,
203
+ linkedNormalized,
204
+ showManageModal,
205
+ editingManageLink,
206
+ manageForm,
207
+ setManageForm,
208
+ isManageSaving,
209
+ franchiseOptions,
210
+ openManageModal,
211
+ closeManageModal,
212
+ handleManageSave,
213
+ handleManageDelete,
214
+ };
215
+ }
@@ -0,0 +1,222 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ buildAvailableSalesmanOptions,
4
+ buildSalesmanSavePayload,
5
+ canManageSalesmen,
6
+ normalizeSalesmanLink,
7
+ toPeopleIri,
8
+ } from './salesmanTab.helpers';
9
+ import { extractId } from './salesmanTabHelpers';
10
+ import {
11
+ buildPeopleLinkReadParams,
12
+ buildSalesmanLinksFromPeopleLinks,
13
+ } from './employeeContacts';
14
+ import { normalizeCollection } from './salesmanTabMedia';
15
+
16
+ /**
17
+ * Administrative manage (link/edit/remove) for SalesmanTab when APP_TYPE=MANAGER.
18
+ */
19
+ export function useSalesmanManage({
20
+ appType,
21
+ client,
22
+ clientId,
23
+ currentCompanyId,
24
+ linkType,
25
+ clients,
26
+ setClients,
27
+ setIsLoading,
28
+ peopleActions,
29
+ getPeopleLinks,
30
+ savePeopleLink,
31
+ removePeopleLink,
32
+ loadDefaultSalesmanLinks,
33
+ showDialog,
34
+ showError,
35
+ showSuccess,
36
+ }) {
37
+ const canManage = useMemo(() => canManageSalesmen(appType), [appType]);
38
+ const clientIri = useMemo(() => toPeopleIri(client), [client]);
39
+ const [availableSalesmen, setAvailableSalesmen] = useState([]);
40
+ const [linkedNormalized, setLinkedNormalized] = useState([]);
41
+ const [showManageModal, setShowManageModal] = useState(false);
42
+ const [editingManageLink, setEditingManageLink] = useState(null);
43
+ const [manageForm, setManageForm] = useState({
44
+ sellerIri: '',
45
+ commission: '0',
46
+ minimumCommission: '0',
47
+ });
48
+ const [isManageSaving, setIsManageSaving] = useState(false);
49
+
50
+ useEffect(() => {
51
+ setLinkedNormalized(
52
+ (Array.isArray(clients) ? clients : [])
53
+ .map(item => normalizeSalesmanLink(item))
54
+ .filter(Boolean),
55
+ );
56
+ }, [clients]);
57
+
58
+ useEffect(() => {
59
+ let cancelled = false;
60
+ if (!canManage || !currentCompanyId || !peopleActions?.getItems) {
61
+ setAvailableSalesmen([]);
62
+ return undefined;
63
+ }
64
+ peopleActions
65
+ .getItems({
66
+ 'link.company': `/people/${currentCompanyId}`,
67
+ 'link.linkType': 'salesman',
68
+ peopleType: 'F',
69
+ itemsPerPage: 100,
70
+ })
71
+ .then(response => {
72
+ if (!cancelled) {
73
+ setAvailableSalesmen(
74
+ Array.isArray(response) ? response : normalizeCollection(response),
75
+ );
76
+ }
77
+ })
78
+ .catch(() => {
79
+ if (!cancelled) setAvailableSalesmen([]);
80
+ });
81
+ return () => {
82
+ cancelled = true;
83
+ };
84
+ }, [canManage, currentCompanyId, peopleActions]);
85
+
86
+ const salesmanOptions = useMemo(
87
+ () =>
88
+ buildAvailableSalesmanOptions({
89
+ salesmen: availableSalesmen,
90
+ linkedSalesmen: linkedNormalized,
91
+ editingLink: editingManageLink,
92
+ }),
93
+ [availableSalesmen, linkedNormalized, editingManageLink],
94
+ );
95
+
96
+ const openManageModal = useCallback(link => {
97
+ setEditingManageLink(link || null);
98
+ setManageForm({
99
+ sellerIri: link?.sellerIri || '',
100
+ commission: String(link?.commission ?? 0),
101
+ minimumCommission: String(link?.minimumCommission ?? 0),
102
+ });
103
+ setShowManageModal(true);
104
+ }, []);
105
+
106
+ const closeManageModal = useCallback(() => {
107
+ setShowManageModal(false);
108
+ setEditingManageLink(null);
109
+ }, []);
110
+
111
+ const handleManageSave = useCallback(async () => {
112
+ if (!savePeopleLink || !clientIri) {
113
+ showError('Nao foi possivel salvar o vendedor neste momento.');
114
+ return;
115
+ }
116
+ const payload = buildSalesmanSavePayload({
117
+ editingLink: editingManageLink,
118
+ formData: manageForm,
119
+ clientIri,
120
+ linkType: linkType || 'sellers-client',
121
+ });
122
+ if (!payload) {
123
+ showError('Selecione um vendedor valido.');
124
+ return;
125
+ }
126
+ setIsManageSaving(true);
127
+ try {
128
+ await savePeopleLink(payload);
129
+ showSuccess(
130
+ editingManageLink
131
+ ? 'Vendedor atualizado com sucesso!'
132
+ : 'Vendedor vinculado com sucesso!',
133
+ );
134
+ closeManageModal();
135
+ setIsLoading(true);
136
+ if (getPeopleLinks && clientId) {
137
+ const params = buildPeopleLinkReadParams({
138
+ clientId,
139
+ linkType: linkType || 'sellers-client',
140
+ });
141
+ const items = await getPeopleLinks(params);
142
+ const nextClients = buildSalesmanLinksFromPeopleLinks(items, {
143
+ clientId,
144
+ linkType: linkType || 'sellers-client',
145
+ });
146
+ setClients(nextClients);
147
+ if (loadDefaultSalesmanLinks) {
148
+ await loadDefaultSalesmanLinks(
149
+ nextClients.map(item => item?.company?.id || item?.company?.['@id']),
150
+ );
151
+ }
152
+ }
153
+ } catch {
154
+ showError(
155
+ editingManageLink
156
+ ? 'Nao foi possivel atualizar o vendedor.'
157
+ : 'Nao foi possivel vincular o vendedor.',
158
+ );
159
+ } finally {
160
+ setIsManageSaving(false);
161
+ setIsLoading(false);
162
+ }
163
+ }, [
164
+ savePeopleLink,
165
+ clientIri,
166
+ editingManageLink,
167
+ manageForm,
168
+ linkType,
169
+ showError,
170
+ showSuccess,
171
+ closeManageModal,
172
+ setIsLoading,
173
+ getPeopleLinks,
174
+ clientId,
175
+ setClients,
176
+ loadDefaultSalesmanLinks,
177
+ ]);
178
+
179
+ const handleManageDelete = useCallback(
180
+ link => {
181
+ if (!link?.id || !removePeopleLink) {
182
+ showError('Nao foi possivel identificar o vinculo para remover.');
183
+ return;
184
+ }
185
+ showDialog({
186
+ title: 'Remover vendedor',
187
+ message: 'Deseja realmente remover este vendedor do cliente?',
188
+ confirmLabel: 'Remover',
189
+ cancelLabel: 'Cancelar',
190
+ onConfirm: async () => {
191
+ try {
192
+ await removePeopleLink(link.id);
193
+ showSuccess('Vendedor removido com sucesso!');
194
+ setClients(prev =>
195
+ (prev || []).filter(
196
+ item => extractId(item?.id) !== extractId(link.id),
197
+ ),
198
+ );
199
+ } catch {
200
+ showError('Nao foi possivel remover o vendedor.');
201
+ }
202
+ },
203
+ });
204
+ },
205
+ [removePeopleLink, showDialog, showError, showSuccess, setClients],
206
+ );
207
+
208
+ return {
209
+ canManage,
210
+ linkedNormalized,
211
+ showManageModal,
212
+ editingManageLink,
213
+ manageForm,
214
+ setManageForm,
215
+ isManageSaving,
216
+ salesmanOptions,
217
+ openManageModal,
218
+ closeManageModal,
219
+ handleManageSave,
220
+ handleManageDelete,
221
+ };
222
+ }