@controleonline/ui-customers 1.0.81 → 1.0.82

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 (62) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/publish-npm.yml +91 -0
  3. package/AGENTS.md +12 -6
  4. package/docs/technical/Client-Details-Criar-Usuario.md +113 -0
  5. package/package.json +5 -4
  6. package/src/react/components/tabs/AddressesTab.js +240 -335
  7. package/src/react/components/tabs/DocumentAttachments.js +270 -0
  8. package/src/react/components/tabs/DocumentsTab.js +355 -463
  9. package/src/react/components/tabs/DocumentsTab.styles.js +123 -0
  10. package/src/react/components/tabs/EmployeeCreateFormFields.js +203 -0
  11. package/src/react/components/tabs/EmployeesTab.js +17 -171
  12. package/src/react/components/tabs/EmployeesTab.styles.js +220 -172
  13. package/src/react/components/tabs/GeneralTab.js +84 -46
  14. package/src/react/components/tabs/GeneralTab.styles.js +81 -77
  15. package/src/react/components/tabs/MediaTab.js +50 -5
  16. package/src/react/components/tabs/PeopleCategoriesPanel.js +161 -78
  17. package/src/react/components/tabs/SalesmanTab.js +1 -1
  18. package/src/react/components/tabs/UserFormModal.js +26 -2
  19. package/src/react/components/tabs/UsersTab.js +30 -19
  20. package/src/react/components/tabs/UsersTab.styles.js +257 -257
  21. package/src/react/components/tabs/UsersTabApiKeyModal.js +115 -0
  22. package/src/react/components/tabs/UsersTabUserModal.js +222 -0
  23. package/src/react/components/tabs/addressesTabHelpers.js +190 -0
  24. package/src/react/components/tabs/documentsTabHelpers.js +142 -0
  25. package/src/react/components/tabs/employeeContacts.js +3 -23
  26. package/src/react/components/tabs/employeeUnlink.js +7 -4
  27. package/src/react/components/tabs/employeesTabHelpers.js +33 -9
  28. package/src/react/components/tabs/generalTabHelpers.js +30 -8
  29. package/src/react/components/tabs/useGeneralTabPeopleLink.js +68 -0
  30. package/src/react/components/tabs/usersTabHelpers.js +24 -23
  31. package/src/react/pages/details.page.styles.js +114 -100
  32. package/src/react/utils/humanCompanyLinkTypes.js +28 -0
  33. package/src/tests/browser/manager/client-details-add-user-modal.spec.js +281 -0
  34. package/src/tests/browser/manager/client-details-address-create-coords.spec.js +414 -0
  35. package/src/tests/browser/manager/client-details-address-edit.spec.js +331 -0
  36. package/src/tests/browser/manager/client-details-contact-edit.spec.js +198 -0
  37. package/src/tests/browser/manager/client-details-create-contact.spec.js +306 -0
  38. package/src/tests/browser/manager/client-details-documents.spec.js +346 -0
  39. package/src/tests/browser/manager/client-details-identity-case.spec.js +227 -0
  40. package/src/tests/browser/manager/client-details-people-type.manifest.md +18 -0
  41. package/src/tests/browser/manager/client-details-people-type.spec.js +345 -0
  42. package/src/tests/browser/manager/client-details-supplier-product-create.spec.js +334 -0
  43. package/src/tests/browser/manager/client-details-users-tab-load.spec.js +194 -0
  44. package/src/tests/browser/manager/employees-salesman-after-sales.spec.js +441 -0
  45. package/src/tests/react/components/SalesmanTab.company.test.js +40 -0
  46. package/src/tests/react/components/tabs/UsersTab.styles.test.js +39 -0
  47. package/src/tests/react/components/tabs/addressesTabHelpers.test.js +78 -0
  48. package/src/tests/react/components/tabs/documentsTabContract.test.mjs +37 -0
  49. package/src/tests/react/components/tabs/documentsTabHelpers.test.mjs +93 -0
  50. package/src/tests/react/components/tabs/employeeContacts.test.js +46 -4
  51. package/src/tests/react/components/tabs/employeeLinkTypes.contract.test.mjs +52 -0
  52. package/src/tests/react/components/tabs/employeeUnlink.test.js +11 -11
  53. package/src/tests/react/components/tabs/employeesTabHelpers.media.test.js +2 -23
  54. package/src/tests/react/components/tabs/employeesTabHelpers.test.js +59 -0
  55. package/src/tests/react/components/tabs/generalTabHelpers.test.js +35 -0
  56. package/src/tests/react/components/tabs/peopleMediaTabs.test.js +217 -217
  57. package/src/tests/react/components/tabs/peopleType.contract.test.mjs +47 -0
  58. package/src/tests/react/components/tabs/resolveSeededLinkType.test.mjs +20 -0
  59. package/src/tests/react/components/tabs/salesmanTabMedia.no-redeclare.test.mjs +33 -0
  60. package/src/tests/react/components/tabs/usersTabHelpers.test.js +20 -0
  61. package/src/tests/react/components/tabs/usersTabHelpers.test.mjs +63 -0
  62. package/src/tests/react/utils/humanCompanyLinkTypes.test.js +50 -0
@@ -3,6 +3,11 @@
3
3
  * Extracted to keep GeneralTab.js under the absolute 500-line limit.
4
4
  */
5
5
 
6
+ import {
7
+ HUMAN_COMPANY_LINK_TYPES,
8
+ HUMAN_COMPANY_LINK_TYPE_OPTIONS,
9
+ } from '../../utils/humanCompanyLinkTypes';
10
+
6
11
  const normalizeText = value => String(value || '').replace(/\s+/g, ' ').trim();
7
12
  const normalizeIdentityValue = value => normalizeText(value);
8
13
  const extractId = value => String(value || '').replace(/\D/g, '');
@@ -81,21 +86,31 @@ const parseBrDateToYmd = value => {
81
86
  .padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
82
87
  };
83
88
 
84
- const LINK_TYPE_OPTIONS = [
85
- { value: 'employee', translationKey: 'employee' },
86
- { value: 'owner', translationKey: 'owner' },
87
- { value: 'director', translationKey: 'director' },
88
- { value: 'manager', translationKey: 'manager' },
89
- { value: 'courier', translationKey: 'courier' },
90
- ];
89
+ const LINK_TYPE_OPTIONS = HUMAN_COMPANY_LINK_TYPE_OPTIONS;
91
90
 
92
91
  const normalizeLinkType = value => {
93
92
  const normalized = String(value || '').trim().toLowerCase();
94
- return ['employee', 'owner', 'director', 'manager', 'courier'].includes(normalized)
93
+ return HUMAN_COMPANY_LINK_TYPES.includes(normalized)
95
94
  ? normalized
96
95
  : 'employee';
97
96
  };
98
97
 
98
+ /** Canonical People.peopleType: F (PF) | J (PJ). API enum — do not invent values. */
99
+ const PEOPLE_TYPE_OPTIONS = [
100
+ { value: 'F', label: 'Pessoa Física' },
101
+ { value: 'J', label: 'Pessoa Jurídica' },
102
+ ];
103
+
104
+ const normalizePeopleType = value => {
105
+ const normalized = String(value || '')
106
+ .trim()
107
+ .toUpperCase();
108
+ if (normalized === 'J' || normalized === 'JURIDICA' || normalized === 'PJ') {
109
+ return 'J';
110
+ }
111
+ return 'F';
112
+ };
113
+
99
114
  const toPeopleIri = value => {
100
115
  const directIri = String(value?.['@id'] || '').trim();
101
116
  if (directIri.startsWith('/people/')) {
@@ -106,6 +121,10 @@ const toPeopleIri = value => {
106
121
  return id ? `/people/${id}` : '';
107
122
  };
108
123
 
124
+ /** Prefer client.linkType (post-save) over static route seed (#446). */
125
+ const resolveSeededLinkType = (clientLinkType, routeLinkType) =>
126
+ normalizeLinkType(clientLinkType || routeLinkType);
127
+
109
128
  export {
110
129
  normalizeText,
111
130
  normalizeIdentityValue,
@@ -116,5 +135,8 @@ export {
116
135
  parseBrDateToYmd,
117
136
  LINK_TYPE_OPTIONS,
118
137
  normalizeLinkType,
138
+ resolveSeededLinkType,
139
+ PEOPLE_TYPE_OPTIONS,
140
+ normalizePeopleType,
119
141
  toPeopleIri,
120
142
  };
@@ -0,0 +1,68 @@
1
+ import {useEffect, useState} from 'react';
2
+ import {extractId, resolveSeededLinkType} from './generalTabHelpers';
3
+
4
+ /**
5
+ * Load people_link id + type for PF contact under a company.
6
+ * Prefers clientLinkType after save so UI does not snap back (#446).
7
+ */
8
+ export default function useGeneralTabPeopleLink({
9
+ canEditLinkType,
10
+ getItems,
11
+ contactPeopleIri,
12
+ parentCompanyIri,
13
+ clientLinkType,
14
+ setRegistrationForm,
15
+ setOriginalRegistrationForm,
16
+ }) {
17
+ const [peopleLinkId, setPeopleLinkId] = useState('');
18
+
19
+ useEffect(() => {
20
+ if (!canEditLinkType || typeof getItems !== 'function') {
21
+ return undefined;
22
+ }
23
+
24
+ let cancelled = false;
25
+
26
+ getItems({
27
+ people: extractId(contactPeopleIri),
28
+ company: extractId(parentCompanyIri),
29
+ })
30
+ .then(items => {
31
+ if (cancelled || !Array.isArray(items) || items.length === 0) {
32
+ return;
33
+ }
34
+
35
+ const link = items[0];
36
+ const nextLinkType = resolveSeededLinkType(
37
+ clientLinkType,
38
+ link?.linkType,
39
+ );
40
+ const nextLinkId = String(link?.id || link?.['@id'] || '').replace(
41
+ /\D/g,
42
+ '',
43
+ );
44
+
45
+ setPeopleLinkId(nextLinkId);
46
+ setRegistrationForm(prev => ({...prev, linkType: nextLinkType}));
47
+ setOriginalRegistrationForm(prev => ({
48
+ ...prev,
49
+ linkType: nextLinkType,
50
+ }));
51
+ })
52
+ .catch(() => {});
53
+
54
+ return () => {
55
+ cancelled = true;
56
+ };
57
+ }, [
58
+ canEditLinkType,
59
+ getItems,
60
+ contactPeopleIri,
61
+ parentCompanyIri,
62
+ clientLinkType,
63
+ setRegistrationForm,
64
+ setOriginalRegistrationForm,
65
+ ]);
66
+
67
+ return {peopleLinkId, setPeopleLinkId};
68
+ }
@@ -1,3 +1,4 @@
1
+ const { mapPasswordErrorMessage } = require('@controleonline/ui-common/src/react/utils/passwordPolicy');
1
2
  const extractId = value => String(value || '').replace(/\D/g, '');
2
3
 
3
4
  /** API Platform expects people as IRI (e.g. /people/106218), not a bare id. */
@@ -22,7 +23,6 @@ const buildUsersListQuery = client => {
22
23
  };
23
24
  };
24
25
 
25
-
26
26
  const normalizeUserItem = entry => {
27
27
  if (!entry) {
28
28
  return null;
@@ -109,35 +109,36 @@ const copyTextToClipboard = async text => {
109
109
  };
110
110
 
111
111
  const extractErrorMessage = error => {
112
+ let message = '';
112
113
  if (Array.isArray(error?.violations) && error.violations.length) {
113
- return error.violations
114
+ message = error.violations
114
115
  .map(item => item?.message || item)
115
116
  .filter(Boolean)
116
117
  .join('\n');
117
- }
118
-
119
- if (Array.isArray(error?.message)) {
120
- return error.message
118
+ } else if (Array.isArray(error?.message)) {
119
+ message = error.message
121
120
  .map(item => item?.message || item)
122
121
  .filter(Boolean)
123
122
  .join(', ');
124
- }
125
-
126
- const status = error?.response?.status || error?.status;
127
- if (status === 401) {
128
- return 'Autenticação necessária. Faça login novamente e tente criar o usuário.';
129
- }
130
-
131
- if (error?.response?.data?.message) {
132
- return String(error.response.data.message);
133
- }
134
-
135
- const msg = error?.message || error?.error || (typeof error === 'string' ? error : '');
136
- if (/authentication required/i.test(String(msg))) {
137
- return 'Autenticação necessária. Faça login novamente e tente criar o usuário.';
138
- }
139
-
140
- return msg;
123
+ } else {
124
+ const status = error?.response?.status || error?.status;
125
+ if (status === 401) {
126
+ message =
127
+ 'Autenticação necessária. Faça login novamente e tente criar o usuário.';
128
+ } else if (error?.response?.data?.message) {
129
+ message = String(error.response.data.message);
130
+ } else {
131
+ message =
132
+ error?.message ||
133
+ error?.error ||
134
+ (typeof error === 'string' ? error : '');
135
+ if (/authentication required/i.test(String(message))) {
136
+ message =
137
+ 'Autenticação necessária. Faça login novamente e tente criar o usuário.';
138
+ }
139
+ }
140
+ }
141
+ return mapPasswordErrorMessage(message || '');
141
142
  };
142
143
 
143
144
  const apiKeyModalStyles = {
@@ -1,105 +1,119 @@
1
- import { StyleSheet } from 'react-native';
2
- import { colors } from '@controleonline/../../src/styles/colors';
3
-
4
- const styles = StyleSheet.create({
5
- container: {
6
- flex: 1,
7
- backgroundColor: '#F8FAFC',
8
- },
9
- headerProfile: {
10
- alignItems: 'center',
11
- paddingVertical: 20,
12
- backgroundColor: '#F8FAFC',
13
- },
1
+ import { StyleSheet } from 'react-native';
2
+ import { colors } from '@controleonline/../../src/styles/colors';
3
+
4
+ const styles = StyleSheet.create({
5
+ container: {
6
+ flex: 1,
7
+ backgroundColor: '#F8FAFC',
8
+ },
9
+ headerProfile: {
10
+ alignItems: 'center',
11
+ paddingVertical: 20,
12
+ backgroundColor: '#F8FAFC',
13
+ },
14
14
  avatarContainer: {
15
15
  position: 'relative',
16
16
  marginBottom: 12,
17
17
  },
18
18
  profileName: {
19
- fontSize: 20,
20
- fontWeight: '700',
21
- color: '#0F172A',
22
- marginBottom: 4,
23
- textAlign: 'center',
24
- maxWidth: '86%',
25
- },
26
- profileId: {
27
- fontSize: 14,
28
- color: '#64748B',
29
- fontWeight: '500',
30
- },
31
- tabsHeader: {
32
- flexDirection: 'row',
33
- backgroundColor: '#fff',
34
- borderBottomWidth: 1,
35
- borderBottomColor: '#F1F5F9',
36
- },
37
- tabButton: {
38
- flex: 1,
39
- alignItems: 'center',
40
- paddingVertical: 16,
41
- position: 'relative',
42
- },
43
- tabButtonText: {
44
- fontSize: 14,
45
- fontWeight: '600',
46
- color: '#64748B',
47
- },
48
- tabButtonTextActive: {
49
- color: colors.primary,
50
- fontWeight: '700',
51
- },
52
- activeIndicator: {
53
- position: 'absolute',
54
- bottom: 0,
55
- width: '60%',
56
- height: 3,
57
- backgroundColor: colors.primary,
58
- borderTopLeftRadius: 3,
59
- borderTopRightRadius: 3,
60
- },
61
- contentContainer: {
62
- flex: 1,
63
- },
64
- scrollContent: {
65
- flexGrow: 1,
66
- },
67
- tabPane: {
68
- height: '100%',
69
- },
70
- tabScroll: {
71
- flex: 1,
72
- },
73
- skeletonContent: {
74
- paddingHorizontal: 16,
75
- paddingTop: 16,
76
- },
77
- skeletonCircle: {
78
- backgroundColor: '#E2E8F0',
79
- },
80
- skeletonLine: {
81
- backgroundColor: '#E2E8F0',
82
- borderRadius: 8,
83
- },
84
- skeletonTab: {
85
- flex: 1,
86
- height: 36,
87
- marginHorizontal: 8,
88
- marginVertical: 10,
89
- borderRadius: 10,
90
- backgroundColor: '#E2E8F0',
91
- },
92
- skeletonCard: {
93
- backgroundColor: '#FFFFFF',
94
- borderRadius: 14,
95
- padding: 16,
96
- marginBottom: 12,
97
- shadowColor: '#000',
98
- shadowOffset: { width: 0, height: 2 },
99
- shadowOpacity: 0.06,
100
- shadowRadius: 6,
101
- elevation: 2,
102
- },
103
- });
104
-
105
- export default styles;
19
+ fontSize: 20,
20
+ fontWeight: '700',
21
+ color: '#0F172A',
22
+ marginBottom: 4,
23
+ textAlign: 'center',
24
+ maxWidth: '86%',
25
+ },
26
+ profileId: {
27
+ fontSize: 14,
28
+ color: '#64748B',
29
+ fontWeight: '500',
30
+ },
31
+ removeButton: {
32
+ marginTop: 12,
33
+ paddingHorizontal: 16,
34
+ paddingVertical: 8,
35
+ borderRadius: 8,
36
+ borderWidth: 1,
37
+ borderColor: '#FECACA',
38
+ backgroundColor: '#FEF2F2',
39
+ },
40
+ removeButtonText: {
41
+ fontSize: 14,
42
+ fontWeight: '600',
43
+ color: '#B91C1C',
44
+ },
45
+ tabsHeader: {
46
+ flexDirection: 'row',
47
+ backgroundColor: '#fff',
48
+ borderBottomWidth: 1,
49
+ borderBottomColor: '#F1F5F9',
50
+ },
51
+ tabButton: {
52
+ flex: 1,
53
+ alignItems: 'center',
54
+ paddingVertical: 16,
55
+ position: 'relative',
56
+ },
57
+ tabButtonText: {
58
+ fontSize: 14,
59
+ fontWeight: '600',
60
+ color: '#64748B',
61
+ },
62
+ tabButtonTextActive: {
63
+ color: colors.primary,
64
+ fontWeight: '700',
65
+ },
66
+ activeIndicator: {
67
+ position: 'absolute',
68
+ bottom: 0,
69
+ width: '60%',
70
+ height: 3,
71
+ backgroundColor: colors.primary,
72
+ borderTopLeftRadius: 3,
73
+ borderTopRightRadius: 3,
74
+ },
75
+ contentContainer: {
76
+ flex: 1,
77
+ },
78
+ scrollContent: {
79
+ flexGrow: 1,
80
+ },
81
+ tabPane: {
82
+ height: '100%',
83
+ },
84
+ tabScroll: {
85
+ flex: 1,
86
+ },
87
+ skeletonContent: {
88
+ paddingHorizontal: 16,
89
+ paddingTop: 16,
90
+ },
91
+ skeletonCircle: {
92
+ backgroundColor: '#E2E8F0',
93
+ },
94
+ skeletonLine: {
95
+ backgroundColor: '#E2E8F0',
96
+ borderRadius: 8,
97
+ },
98
+ skeletonTab: {
99
+ flex: 1,
100
+ height: 36,
101
+ marginHorizontal: 8,
102
+ marginVertical: 10,
103
+ borderRadius: 10,
104
+ backgroundColor: '#E2E8F0',
105
+ },
106
+ skeletonCard: {
107
+ backgroundColor: '#FFFFFF',
108
+ borderRadius: 14,
109
+ padding: 16,
110
+ marginBottom: 12,
111
+ shadowColor: '#000',
112
+ shadowOffset: { width: 0, height: 2 },
113
+ shadowOpacity: 0.06,
114
+ shadowRadius: 6,
115
+ elevation: 2,
116
+ },
117
+ });
118
+
119
+ export default styles;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Canonical human↔company link-type catalog for Contatos/Colaboradores.
3
+ * Values stay aligned with ui-people HUMAN_COMPANY_LINK_TYPES.
4
+ * Single source for create/edit/list/filter UI options in ui-customers.
5
+ */
6
+ export const HUMAN_COMPANY_LINK_TYPES = [
7
+ 'employee',
8
+ 'owner',
9
+ 'director',
10
+ 'manager',
11
+ 'salesman',
12
+ 'after-sales',
13
+ 'courier',
14
+ ];
15
+
16
+ export const HUMAN_COMPANY_LINK_TYPE_OPTIONS = HUMAN_COMPANY_LINK_TYPES.map(
17
+ value => ({
18
+ value,
19
+ translationKey: value,
20
+ }),
21
+ );
22
+
23
+ export const isHumanCompanyLinkType = value =>
24
+ HUMAN_COMPANY_LINK_TYPES.includes(
25
+ String(value || '')
26
+ .trim()
27
+ .toLowerCase(),
28
+ );