@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,405 @@
1
+ const {expect, test} = require('playwright/test');
2
+ const {API_ORIGIN} = require('../../../../../../../src/tests/browser/apiOrigin');
3
+ const {version: appVersion} = require('../../../../../../../package.json');
4
+
5
+ const CORS_HEADERS = {
6
+ 'access-control-allow-origin': '*',
7
+ 'access-control-allow-headers':
8
+ 'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
9
+ 'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
10
+ };
11
+
12
+ const collection = (member = [], summary = {}) => ({
13
+ member,
14
+ 'hydra:member': member,
15
+ totalItems: member.length,
16
+ 'hydra:totalItems': member.length,
17
+ summary,
18
+ });
19
+
20
+ const jsonHeaders = () => ({
21
+ ...CORS_HEADERS,
22
+ 'content-type': 'application/ld+json; charset=utf-8',
23
+ });
24
+
25
+ const textHeaders = () => ({
26
+ ...CORS_HEADERS,
27
+ 'content-type': 'text/css; charset=utf-8',
28
+ });
29
+
30
+ const createCompany = () => ({
31
+ id: 3,
32
+ name: 'Controle Online',
33
+ alias: 'CONTROLE ONLINE',
34
+ panel_enabled: true,
35
+ enabled: true,
36
+ commercial_enabled: true,
37
+ theme: {
38
+ colors: {
39
+ primary: '#0EA5E9',
40
+ secondary: '#F97316',
41
+ },
42
+ },
43
+ configs: {},
44
+ });
45
+
46
+ const buildMediaType = ({id, type, peopleType}) => ({
47
+ '@id': `/media_types/${id}`,
48
+ id,
49
+ type,
50
+ peopleType,
51
+ });
52
+
53
+ const buildPeopleMedia = ({id, peopleId, mediaType, fileName}) => ({
54
+ '@id': `/people_media/${id}`,
55
+ id,
56
+ people: `/people/${peopleId}`,
57
+ mediaType,
58
+ file: {
59
+ '@id': `/files/${id}`,
60
+ id,
61
+ name: fileName,
62
+ extension: fileName.split('.').pop(),
63
+ url: `https://cdn.example/${fileName}`,
64
+ },
65
+ });
66
+
67
+ const buildPeopleLink = ({id, company, people, linkType = 'employee'}) => ({
68
+ '@id': `/people_links/${id}`,
69
+ id,
70
+ company,
71
+ people,
72
+ linkType,
73
+ });
74
+
75
+ const mockClientDetailsApi = async page => {
76
+ const company = createCompany();
77
+ const personAvatar = buildPeopleMedia({
78
+ id: 102,
79
+ peopleId: 30,
80
+ mediaType: buildMediaType({id: 11, type: 'avatar', peopleType: 'F'}),
81
+ fileName: 'person-avatar.png',
82
+ });
83
+ const peopleById = {
84
+ 9: {
85
+ '@id': '/people/9',
86
+ id: 9,
87
+ name: 'CLIENT WITHOUT MEDIA',
88
+ alias: 'CLIENT WITHOUT MEDIA',
89
+ peopleType: 'F',
90
+ enable: true,
91
+ email: 'client-without-media@example.com',
92
+ image: 9,
93
+ },
94
+ 29: {
95
+ '@id': '/people/29',
96
+ id: 29,
97
+ name: 'LAVE GO FRANQUIAS LTDA',
98
+ alias: 'LAVE GO',
99
+ peopleType: 'J',
100
+ enable: true,
101
+ },
102
+ 30: {
103
+ '@id': '/people/30',
104
+ id: 30,
105
+ name: 'JOAO SILVA',
106
+ alias: 'JOAO SILVA',
107
+ peopleType: 'F',
108
+ enable: true,
109
+ peopleMedia: [personAvatar],
110
+ },
111
+ };
112
+
113
+ await page.route(`${API_ORIGIN}/**`, async route => {
114
+ const request = route.request();
115
+ const url = new URL(request.url());
116
+ const pathname = url.pathname.replace(/^\/+/, '');
117
+ const method = request.method().toUpperCase();
118
+
119
+ if (method === 'OPTIONS') {
120
+ return route.fulfill({
121
+ status: 204,
122
+ headers: CORS_HEADERS,
123
+ body: '',
124
+ });
125
+ }
126
+
127
+ if (pathname === 'themes-colors.css') {
128
+ return route.fulfill({
129
+ status: 200,
130
+ headers: textHeaders(),
131
+ body: ':root { --primary: #0ea5e9; --secondary: #f97316; }',
132
+ });
133
+ }
134
+
135
+ if (pathname === 'runtime/ip') {
136
+ return route.fulfill({
137
+ status: 200,
138
+ headers: jsonHeaders(),
139
+ body: JSON.stringify({ip: '127.0.0.1'}),
140
+ });
141
+ }
142
+
143
+ if (pathname === 'people/company/default') {
144
+ return route.fulfill({
145
+ status: 200,
146
+ headers: jsonHeaders(),
147
+ body: JSON.stringify(company),
148
+ });
149
+ }
150
+
151
+ if (pathname === 'people/companies/my') {
152
+ return route.fulfill({
153
+ status: 200,
154
+ headers: jsonHeaders(),
155
+ body: JSON.stringify(collection([company])),
156
+ });
157
+ }
158
+
159
+ if (pathname === 'people/9' || pathname === 'people/29' || pathname === 'people/30') {
160
+ const client = peopleById[Number(pathname.split('/')[1])];
161
+ return route.fulfill({
162
+ status: 200,
163
+ headers: jsonHeaders(),
164
+ body: JSON.stringify(client),
165
+ });
166
+ }
167
+
168
+ if (pathname === 'media_types') {
169
+ const peopleType = String(url.searchParams.get('peopleType') || '').toUpperCase();
170
+ const type = url.searchParams.get('type');
171
+
172
+ const mediaTypes =
173
+ peopleType === 'F'
174
+ ? [buildMediaType({id: 11, type: 'avatar', peopleType: 'F'})]
175
+ : [buildMediaType({id: 12, type: 'icon', peopleType: 'J'})];
176
+
177
+ return route.fulfill({
178
+ status: 200,
179
+ headers: jsonHeaders(),
180
+ body: JSON.stringify(
181
+ collection(mediaTypes.filter(item => !type || item.type === type)),
182
+ ),
183
+ });
184
+ }
185
+
186
+ if (pathname === 'people_links') {
187
+ const companyId = String(url.searchParams.get('company') || '').replace(/\D/g, '');
188
+ const links =
189
+ companyId === '9'
190
+ ? [
191
+ buildPeopleLink({
192
+ id: 201,
193
+ company: peopleById[9],
194
+ people: peopleById[30],
195
+ }),
196
+ ]
197
+ : [];
198
+
199
+ return route.fulfill({
200
+ status: 200,
201
+ headers: jsonHeaders(),
202
+ body: JSON.stringify(collection(links)),
203
+ });
204
+ }
205
+
206
+ if (pathname === 'people_media') {
207
+ if (method === 'POST') {
208
+ const body = await request.postDataJSON().catch(() => ({}));
209
+ const peopleId = String(body?.people || '').replace(/\D/g, '') || '30';
210
+ const mediaTypeId = String(body?.mediaType || '').replace(/\D/g, '') || '11';
211
+ const mediaType = buildMediaType({
212
+ id: Number(mediaTypeId),
213
+ type: mediaTypeId === '12' ? 'icon' : 'avatar',
214
+ peopleType: mediaTypeId === '12' ? 'J' : 'F',
215
+ });
216
+
217
+ return route.fulfill({
218
+ status: 201,
219
+ headers: jsonHeaders(),
220
+ body: JSON.stringify(
221
+ buildPeopleMedia({
222
+ id: 130,
223
+ peopleId: Number(peopleId),
224
+ mediaType,
225
+ fileName: 'selected-library-image.png',
226
+ }),
227
+ ),
228
+ });
229
+ }
230
+
231
+ const people = String(url.searchParams.get('people') || '');
232
+ const mediaType = String(url.searchParams.get('mediaType.type') || '');
233
+ const media =
234
+ people.endsWith('/29') && mediaType === 'icon'
235
+ ? buildPeopleMedia({
236
+ id: 101,
237
+ peopleId: 29,
238
+ mediaType: buildMediaType({id: 12, type: 'icon', peopleType: 'J'}),
239
+ fileName: 'company-icon.png',
240
+ })
241
+ : people.endsWith('/30') && mediaType === 'avatar'
242
+ ? buildPeopleMedia({
243
+ id: 102,
244
+ peopleId: 30,
245
+ mediaType: buildMediaType({id: 11, type: 'avatar', peopleType: 'F'}),
246
+ fileName: 'person-avatar.png',
247
+ })
248
+ : null;
249
+
250
+ return route.fulfill({
251
+ status: 200,
252
+ headers: jsonHeaders(),
253
+ body: JSON.stringify(collection(media ? [media] : [])),
254
+ });
255
+ }
256
+
257
+ if (pathname === 'menus-people') {
258
+ return route.fulfill({
259
+ status: 200,
260
+ headers: jsonHeaders(),
261
+ body: JSON.stringify({modules: {}}),
262
+ });
263
+ }
264
+
265
+ if (pathname === 'configs/discovery-configs') {
266
+ return route.fulfill({
267
+ status: 200,
268
+ headers: jsonHeaders(),
269
+ body: JSON.stringify({configs: {}}),
270
+ });
271
+ }
272
+
273
+ return route.fulfill({
274
+ status: 200,
275
+ headers: jsonHeaders(),
276
+ body: JSON.stringify(collection([])),
277
+ });
278
+ });
279
+
280
+ await page.addInitScript(
281
+ ({appVersion}) => {
282
+ const setLocalStorageItem = (key, value) => {
283
+ try {
284
+ localStorage.setItem(key, value);
285
+ } catch {
286
+ // Initial documents such as about:blank do not expose storage.
287
+ }
288
+ };
289
+
290
+ setLocalStorageItem(
291
+ 'session',
292
+ JSON.stringify({
293
+ id: 7,
294
+ people: '/people/7',
295
+ api_key: 'test-api-key',
296
+ active: 1,
297
+ mycompany: 3,
298
+ roles: ['ROLE_SUPER'],
299
+ }),
300
+ );
301
+ setLocalStorageItem('config', JSON.stringify({language: 'pt-br'}));
302
+ setLocalStorageItem('app-type', 'MANAGER');
303
+ setLocalStorageItem(
304
+ 'device',
305
+ JSON.stringify({
306
+ id: 'web-manager',
307
+ device: 'web-manager',
308
+ type: 'WEB',
309
+ appName: 'Browser Manager',
310
+ appVersion,
311
+ buildNumber: appVersion,
312
+ systemName: 'web',
313
+ systemVersion: 'web',
314
+ deviceType: 'web',
315
+ metadata: {},
316
+ }),
317
+ );
318
+ },
319
+ {appVersion},
320
+ );
321
+ };
322
+
323
+ test.describe('client details media browser smoke', () => {
324
+ test('loads contact avatars from people_links without contact people_media requests', async ({page}) => {
325
+ const contactMediaRequests = [];
326
+
327
+ page.on('request', request => {
328
+ const url = new URL(request.url());
329
+
330
+ if (
331
+ url.pathname.replace(/^\/+/, '') === 'people_media' &&
332
+ decodeURIComponent(url.search).includes('/people/30')
333
+ ) {
334
+ contactMediaRequests.push(request.url());
335
+ }
336
+ });
337
+
338
+ await mockClientDetailsApi(page);
339
+
340
+ await page.goto('/client-details?clientId=9&contextKey=client&initialTab=contacts');
341
+
342
+ await expect(page.getByText('Contatos', {exact: true})).toBeVisible({timeout: 15000});
343
+ await expect(page.getByText('JOAO SILVA', {exact: true})).toBeVisible();
344
+ expect(contactMediaRequests).toEqual([]);
345
+ });
346
+
347
+ test('loads valid client details without media as initials only', async ({page}) => {
348
+ const avatarRequests = [];
349
+
350
+ page.on('request', request => {
351
+ const url = request.url();
352
+
353
+ if (/gravatar\.com\/avatar|\/files\/9\/download/i.test(url)) {
354
+ avatarRequests.push(url);
355
+ }
356
+ });
357
+
358
+ await mockClientDetailsApi(page);
359
+
360
+ await page.goto('/client-details?clientId=9&contextKey=client');
361
+
362
+ await expect(page.getByText('CLIENT WITHOUT MEDIA', {exact: true})).toBeVisible({timeout: 15000});
363
+ await expect(page.getByText('Documentos', {exact: true})).toBeVisible();
364
+ await expect(page.getByText('Not Found', {exact: true})).toHaveCount(0);
365
+ expect(avatarRequests).toEqual([]);
366
+ });
367
+
368
+ test('shows company icon upload controls for pessoa juridica', async ({page}) => {
369
+ await mockClientDetailsApi(page);
370
+
371
+ await page.goto('/client-details?clientId=29&contextKey=client');
372
+
373
+ await expect(page.getByText('Documentos', {exact: true})).toBeVisible({timeout: 15000});
374
+ await page.getByText('Media', {exact: true}).click();
375
+ const managerButton = page.getByText('Gerenciar icon', {exact: true});
376
+ await expect(managerButton).toBeVisible();
377
+ await managerButton.click();
378
+
379
+ const uploadButton = page.getByText('Enviar nova', {exact: true});
380
+ await expect(uploadButton).toBeVisible();
381
+
382
+ const fileChooserPromise = page.waitForEvent('filechooser');
383
+ await uploadButton.click();
384
+ await expect(fileChooserPromise).resolves.toBeTruthy();
385
+ });
386
+
387
+ test('shows personal avatar upload controls for pessoa fisica', async ({page}) => {
388
+ await mockClientDetailsApi(page);
389
+
390
+ await page.goto('/client-details?clientId=30&contextKey=client');
391
+
392
+ await expect(page.getByText('Documentos', {exact: true})).toBeVisible({timeout: 15000});
393
+ await page.getByText('Media', {exact: true}).click();
394
+ const managerButton = page.getByText('Gerenciar avatar', {exact: true});
395
+ await expect(managerButton).toBeVisible();
396
+ await managerButton.click();
397
+
398
+ const uploadButton = page.getByText('Enviar nova', {exact: true});
399
+ await expect(uploadButton).toBeVisible();
400
+
401
+ const fileChooserPromise = page.waitForEvent('filechooser');
402
+ await uploadButton.click();
403
+ await expect(fileChooserPromise).resolves.toBeTruthy();
404
+ });
405
+ });
@@ -0,0 +1,313 @@
1
+ const {expect, test} = require('playwright/test');
2
+ const {API_ORIGIN} = require('../../../../../../../src/tests/browser/apiOrigin');
3
+ const {version: appVersion} = require('../../../../../../../package.json');
4
+
5
+ const CORS_HEADERS = {
6
+ 'access-control-allow-origin': '*',
7
+ 'access-control-allow-headers':
8
+ 'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
9
+ 'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
10
+ };
11
+
12
+ const jsonHeaders = () => ({
13
+ ...CORS_HEADERS,
14
+ 'content-type': 'application/ld+json; charset=utf-8',
15
+ });
16
+
17
+ const textHeaders = () => ({
18
+ ...CORS_HEADERS,
19
+ 'content-type': 'text/css; charset=utf-8',
20
+ });
21
+
22
+ const collection = member => ({
23
+ member,
24
+ 'hydra:member': member,
25
+ totalItems: member.length,
26
+ 'hydra:totalItems': member.length,
27
+ });
28
+
29
+ const themeColors = {
30
+ primary: '#1C8FBD',
31
+ secondary: '#0169D9',
32
+ background: '#F3F7FB',
33
+ text: '#0F172A',
34
+ textSecondary: '#6C7787',
35
+ border: '#D7E1EC',
36
+ headerBackground: '#F3F7FB',
37
+ headerBorder: '#D7E1EC',
38
+ headerText: '#0F172A',
39
+ cardBackground: '#FFFFFF',
40
+ cardBorder: '#D7E1EC',
41
+ cardText: '#0F172A',
42
+ cardIcon: '#1C8FBD',
43
+ cardShadow: '#1C8FBD',
44
+ listItemBackground: '#F1F8FB',
45
+ listItemText: '#0F172A',
46
+ listItemSubtitleText: '#6C7787',
47
+ loadingSpinner: '#1C8FBD',
48
+ buttonBackground: '#1C8FBD',
49
+ buttonBorder: '#1C8FBD',
50
+ buttonIcon: '#FFFFFF',
51
+ buttonText: '#FFFFFF',
52
+ buttonShadow: '#1C8FBD',
53
+ buttonBackgroundSecondary: '#FFFFFF',
54
+ buttonBorderSecondary: '#D7E1EC',
55
+ buttonIconSecondary: '#0169D9',
56
+ buttonTextSecondary: '#0169D9',
57
+ buttonDisabledBackground: '#CBD5E1',
58
+ tabBarBackground: '#FFFFFF',
59
+ tabBarBorder: '#D7E1EC',
60
+ menuSelectedBorder: '#1C8FBD',
61
+ menuSelectedText: '#1C8FBD',
62
+ pageBackground: '#F3F7FB',
63
+ modalOverlay: 'rgba(15,23,42,0.45)',
64
+ modalBackground: '#FFFFFF',
65
+ modalText: '#0F172A',
66
+ modalShadow: '#1C8FBD',
67
+ inputBackground: '#FFFFFF',
68
+ inputBorder: '#D7E1EC',
69
+ };
70
+
71
+ const themeCss = `:root { ${Object.entries(themeColors)
72
+ .map(([key, value]) => `--${key}: ${value};`)
73
+ .join(' ')} }`;
74
+
75
+ const company = {
76
+ '@id': '/people/2',
77
+ id: 2,
78
+ name: 'CONTROLE ONLINE',
79
+ alias: 'CONTROLE ONLINE',
80
+ panel_enabled: true,
81
+ enabled: true,
82
+ commercial_enabled: true,
83
+ theme: {colors: themeColors},
84
+ configs: {},
85
+ };
86
+
87
+ const client = {
88
+ '@id': '/people/29',
89
+ id: 29,
90
+ name: 'LAVE GO FRANQUIAS LTDA',
91
+ alias: 'LAVE-GO',
92
+ peopleType: 'J',
93
+ enable: true,
94
+ };
95
+
96
+ const seller = {
97
+ '@id': '/people/6',
98
+ id: 6,
99
+ name: 'VENDEDOR CORRETO',
100
+ alias: 'VENDEDOR',
101
+ peopleType: 'F',
102
+ };
103
+
104
+ const contact = {
105
+ '@id': '/people/25',
106
+ id: 25,
107
+ name: 'CONTATO CORRETO',
108
+ alias: 'CONTATO',
109
+ peopleType: 'F',
110
+ };
111
+
112
+ const mockManagerApi = async page => {
113
+ const relationshipQueries = [];
114
+
115
+ await page.route(`${API_ORIGIN}/**`, async route => {
116
+ const request = route.request();
117
+ const url = new URL(request.url());
118
+ const pathname = url.pathname.replace(/^\/+/, '');
119
+
120
+ if (request.method().toUpperCase() === 'OPTIONS') {
121
+ return route.fulfill({status: 204, headers: CORS_HEADERS, body: ''});
122
+ }
123
+
124
+ if (pathname === 'themes-colors.css') {
125
+ return route.fulfill({
126
+ status: 200,
127
+ headers: textHeaders(),
128
+ body: themeCss,
129
+ });
130
+ }
131
+
132
+ if (pathname === 'runtime/ip') {
133
+ return route.fulfill({
134
+ status: 200,
135
+ headers: jsonHeaders(),
136
+ body: JSON.stringify({ip: '127.0.0.1'}),
137
+ });
138
+ }
139
+
140
+ if (pathname === 'people/companies/my') {
141
+ return route.fulfill({
142
+ status: 200,
143
+ headers: jsonHeaders(),
144
+ body: JSON.stringify(collection([company])),
145
+ });
146
+ }
147
+
148
+ if (pathname === 'people/company/default') {
149
+ return route.fulfill({
150
+ status: 200,
151
+ headers: jsonHeaders(),
152
+ body: JSON.stringify(company),
153
+ });
154
+ }
155
+
156
+ if (pathname === 'people/29') {
157
+ return route.fulfill({
158
+ status: 200,
159
+ headers: jsonHeaders(),
160
+ body: JSON.stringify(client),
161
+ });
162
+ }
163
+
164
+ if (pathname === 'people_links') {
165
+ const query = Object.fromEntries(url.searchParams);
166
+ relationshipQueries.push(query);
167
+
168
+ const links = query.linkType === 'sellers-client'
169
+ ? [
170
+ {
171
+ '@id': '/people_links/101',
172
+ id: 101,
173
+ company: seller,
174
+ people: client,
175
+ linkType: 'sellers-client',
176
+ },
177
+ {
178
+ '@id': '/people_links/102',
179
+ id: 102,
180
+ company: seller,
181
+ people: {...client, '@id': '/people/11', id: 11},
182
+ linkType: 'sellers-client',
183
+ },
184
+ ]
185
+ : [
186
+ {
187
+ '@id': '/people_links/201',
188
+ id: 201,
189
+ company: client,
190
+ people: contact,
191
+ linkType: 'employee',
192
+ },
193
+ {
194
+ '@id': '/people_links/202',
195
+ id: 202,
196
+ company: {...client, '@id': '/people/31', id: 31},
197
+ people: contact,
198
+ linkType: 'employee',
199
+ },
200
+ ];
201
+
202
+ return route.fulfill({
203
+ status: 200,
204
+ headers: jsonHeaders(),
205
+ body: JSON.stringify(collection(links)),
206
+ });
207
+ }
208
+
209
+ if (pathname === 'menus-people') {
210
+ return route.fulfill({
211
+ status: 200,
212
+ headers: jsonHeaders(),
213
+ body: JSON.stringify({modules: {}}),
214
+ });
215
+ }
216
+
217
+ if (pathname === 'configs/discovery-configs') {
218
+ return route.fulfill({
219
+ status: 200,
220
+ headers: jsonHeaders(),
221
+ body: JSON.stringify({configs: {}}),
222
+ });
223
+ }
224
+
225
+ return route.fulfill({
226
+ status: 200,
227
+ headers: jsonHeaders(),
228
+ body: JSON.stringify(collection([])),
229
+ });
230
+ });
231
+
232
+ await page.addInitScript(
233
+ ({version}) => {
234
+ localStorage.setItem(
235
+ 'session',
236
+ JSON.stringify({
237
+ id: 19,
238
+ people: '/people/19',
239
+ api_key: 'test-api-key',
240
+ active: 1,
241
+ mycompany: 2,
242
+ roles: ['ROLE_SUPER'],
243
+ }),
244
+ );
245
+ localStorage.setItem('app-type', 'MANAGER');
246
+ localStorage.setItem('config', JSON.stringify({language: 'pt-br'}));
247
+ localStorage.setItem(
248
+ 'device',
249
+ JSON.stringify({
250
+ id: 'web-manager',
251
+ device: 'web-manager',
252
+ type: 'WEB',
253
+ appName: 'Browser Manager',
254
+ appVersion: version,
255
+ buildNumber: version,
256
+ systemName: 'web',
257
+ systemVersion: 'web',
258
+ deviceType: 'web',
259
+ metadata: {},
260
+ }),
261
+ );
262
+ },
263
+ {version: appVersion},
264
+ );
265
+
266
+ return relationshipQueries;
267
+ };
268
+
269
+ test.describe('manager client relationship details', () => {
270
+ test('scopes sellers and contacts with numeric people-link filters', async ({page}) => {
271
+ const duplicateKeyWarnings = [];
272
+ page.on('console', message => {
273
+ if (message.text().includes('same key')) {
274
+ duplicateKeyWarnings.push(message.text());
275
+ }
276
+ });
277
+
278
+ const relationshipQueries = await mockManagerApi(page);
279
+
280
+ await page.goto('/client-details?clientId=29&contextKey=client');
281
+
282
+ await expect(
283
+ page.getByText('LAVE GO FRANQUIAS LTDA', {exact: true}).first(),
284
+ ).toBeVisible();
285
+
286
+ await expect
287
+ .poll(() => relationshipQueries.length)
288
+ .toBeGreaterThanOrEqual(2);
289
+
290
+ expect(relationshipQueries).toEqual(
291
+ expect.arrayContaining([
292
+ expect.objectContaining({
293
+ people: '29',
294
+ linkType: 'sellers-client',
295
+ }),
296
+ expect.objectContaining({
297
+ company: '29',
298
+ }),
299
+ ]),
300
+ );
301
+ expect(
302
+ relationshipQueries.some(
303
+ query =>
304
+ String(query.people || '').startsWith('/people/') ||
305
+ String(query.company || '').startsWith('/people/'),
306
+ ),
307
+ ).toBe(false);
308
+
309
+ await expect(page.getByText('VENDEDOR CORRETO', {exact: true})).toHaveCount(1);
310
+ await expect(page.getByText('CONTATO CORRETO', {exact: true})).toHaveCount(1);
311
+ expect(duplicateKeyWarnings).toEqual([]);
312
+ });
313
+ });