@controleonline/ui-default 1.0.270 → 1.0.272
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/workflows/integration-source-gate.yml +68 -0
- package/.github/workflows/manager-worker.yml +19 -0
- package/AGENTS.md +46 -50
- package/docs/technical/Contrato-de-colunas.md +67 -0
- package/docs/technical/Default-Components.md +76 -0
- package/docs/technical/DefaultForm.md +80 -0
- package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
- package/docs/technical/DefaultTable.md +203 -0
- package/docs/wiki.md +1 -0
- package/package.json +22 -21
- package/scripts/validate-wiki-pointer.cjs +13 -0
- package/src/react/components/address/DefaultAddress.js +435 -0
- package/src/react/components/address/DefaultAddress.styles.js +133 -0
- package/src/react/components/address/DefaultAddressMapPane.js +55 -0
- package/src/react/components/address/LatLonReadonlyFields.js +51 -0
- package/src/react/components/address/defaultAddressHelpers.js +163 -0
- package/src/react/components/address/defaultAddressStyles.js +136 -0
- package/src/react/components/address/defaultAddressUtils.js +129 -0
- package/src/react/components/extra-data/DefaultExtraData.js +152 -0
- package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
- package/src/react/components/files/DefaultFile.js +231 -46
- package/src/react/components/files/DefaultFileColumn.js +123 -0
- package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
- package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
- package/src/react/components/filters/CompactFilterSelector.js +51 -35
- package/src/react/components/filters/DefaultColumnFilter.js +58 -41
- package/src/react/components/filters/DefaultExternalFilters.js +331 -265
- package/src/react/components/form/DefaultForm.js +99 -79
- package/src/react/components/help/DefaultTooltip.js +59 -33
- package/src/react/components/help/DefaultTooltip.styles.js +23 -3
- package/src/react/components/inputs/DefaultColorInput.js +153 -0
- package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
- package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
- package/src/react/components/inputs/DefaultInput.js +69 -5
- package/src/react/components/inputs/DefaultInput.styles.js +26 -0
- package/src/react/components/inputs/DefaultSelect.js +134 -123
- package/src/react/components/inputs/defaultInputUtils.js +69 -43
- package/src/react/components/map/DefaultMap.native.js +73 -73
- package/src/react/components/map/DefaultMap.shared.js +911 -762
- package/src/react/components/map/DefaultMap.web.js +124 -68
- package/src/react/components/table/DefaultColumnMenu.js +109 -108
- package/src/react/components/table/DefaultDebug.js +44 -37
- package/src/react/components/table/DefaultSearchModal.js +9 -1
- package/src/react/components/table/DefaultTable.grouping.js +98 -0
- package/src/react/components/table/DefaultTable.js +213 -107
- package/src/react/components/table/DefaultTable.styles.js +367 -314
- package/src/react/components/table/DefaultTableBody.js +13 -8
- package/src/react/components/table/DefaultTableCards.js +20 -1
- package/src/react/components/table/DefaultTableControls.js +180 -205
- package/src/react/components/table/DefaultTableImportModal.js +108 -0
- package/src/react/components/table/DefaultTableInput.js +1 -0
- package/src/react/components/table/DefaultTableRowActions.js +1 -1
- package/src/react/components/table/DefaultTableRows.js +224 -363
- package/src/react/components/table/DefaultTableSearch.js +28 -5
- package/src/react/components/table/DefaultTableToolbar.js +92 -3
- package/src/react/components/table/useDefaultTablePagination.js +56 -7
- package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
- package/src/react/components/upload/DefaultUpload.js +436 -0
- package/src/react/components/upload/DefaultUpload.styles.js +153 -0
- package/src/react/components/upload/DefaultUploadAttachmentsList.js +138 -0
- package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
- package/src/react/components/upload/defaultUploadHelpers.js +108 -0
- package/src/react/components/upload/defaultUploadLibrary.js +248 -0
- package/src/react/components/upload/fileUpload.js +93 -0
- package/src/react/hooks/usePostalCodeLookup.js +145 -0
- package/src/react/index.js +6 -0
- package/src/react/services/addressFormUtils.js +196 -0
- package/src/react/services/addressGeo.js +227 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +29 -12
- package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
- package/src/tests/react/components/files/DefaultFile.test.js +6 -6
- package/src/tests/react/components/form/DefaultForm.test.js +123 -0
- package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
- package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
- package/src/tests/react/components/table/DefaultTable.test.js +53 -2
- package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
- package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
- package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
- package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
- package/src/tests/react/components/upload/defaultUploadLibrary.test.js +82 -0
- package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
- package/src/tests/react/hooks/usePostalCodeLookup.test.js +40 -0
- package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
- package/src/tests/react/services/addressFormUtils.test.js +176 -0
- package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
- package/src/tests/react/services/addressGeoLookup.test.js +105 -0
- package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
- package/src/tests/react/services/smoke-637/index.html +132 -0
- package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +84 -48
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {api} from '@controleonline/ui-common/src/api';
|
|
2
|
+
|
|
3
|
+
const onlyDigits = value => String(value || '').replace(/\D+/g, '');
|
|
4
|
+
|
|
5
|
+
const hasText = value => String(value || '').trim().length > 0;
|
|
6
|
+
|
|
7
|
+
export function isValidCoord(value) {
|
|
8
|
+
if (value === null || value === undefined || value === '') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const n = Number(value);
|
|
12
|
+
return Number.isFinite(n) && Math.abs(n) > 0.000001;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isPostalLookupIncomplete(data) {
|
|
16
|
+
if (!data || typeof data !== 'object') {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
const missingText = !hasText(data.street) || !hasText(data.city);
|
|
20
|
+
const missingCoords =
|
|
21
|
+
!isValidCoord(data.latitude ?? data?.map?.latitude) ||
|
|
22
|
+
!isValidCoord(data.longitude ?? data?.map?.longitude);
|
|
23
|
+
return missingText || missingCoords;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function mergePostalLookupPayload(base, extra, providerSuffix) {
|
|
27
|
+
const src = extra && typeof extra === 'object' ? extra : {};
|
|
28
|
+
const next = {...(base && typeof base === 'object' ? base : {})};
|
|
29
|
+
const fill = (key, value) => {
|
|
30
|
+
if (!hasText(next[key]) && hasText(value)) {
|
|
31
|
+
next[key] = value;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
fill('street', src.street);
|
|
35
|
+
fill('district', src.district);
|
|
36
|
+
fill('city', src.city);
|
|
37
|
+
fill('state', src.state || src.uf);
|
|
38
|
+
fill('uf', src.uf || src.state);
|
|
39
|
+
fill('country', src.country);
|
|
40
|
+
if (!isValidCoord(next.latitude) && isValidCoord(src.latitude)) {
|
|
41
|
+
next.latitude = Number(src.latitude);
|
|
42
|
+
}
|
|
43
|
+
if (!isValidCoord(next.longitude) && isValidCoord(src.longitude)) {
|
|
44
|
+
next.longitude = Number(src.longitude);
|
|
45
|
+
}
|
|
46
|
+
if (providerSuffix) {
|
|
47
|
+
const current = String(next.provider || '').trim();
|
|
48
|
+
next.provider = current
|
|
49
|
+
? `${current}+${providerSuffix}`
|
|
50
|
+
: providerSuffix;
|
|
51
|
+
}
|
|
52
|
+
return next;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function mapViaCepPayload(json, digits) {
|
|
56
|
+
if (!json || json.erro) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
cep: digits,
|
|
61
|
+
street: json.logradouro || '',
|
|
62
|
+
district: json.bairro || '',
|
|
63
|
+
city: json.localidade || '',
|
|
64
|
+
state: json.uf || '',
|
|
65
|
+
uf: json.uf || '',
|
|
66
|
+
country: 'Brasil',
|
|
67
|
+
provider: 'viacep-client',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function lookupViaCep(cep) {
|
|
72
|
+
const digits = onlyDigits(cep);
|
|
73
|
+
if (digits.length !== 8) {
|
|
74
|
+
throw new Error('CEP deve ter 8 dígitos');
|
|
75
|
+
}
|
|
76
|
+
const response = await fetch(`https://viacep.com.br/ws/${digits}/json/`);
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new Error(`ViaCEP HTTP ${response.status}`);
|
|
79
|
+
}
|
|
80
|
+
const json = await response.json();
|
|
81
|
+
const mapped = mapViaCepPayload(json, digits);
|
|
82
|
+
if (!mapped) {
|
|
83
|
+
throw new Error('CEP não encontrado');
|
|
84
|
+
}
|
|
85
|
+
return mapped;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildNominatimQuery(data, digits) {
|
|
89
|
+
const cepFormatted =
|
|
90
|
+
digits.length === 8 ? `${digits.slice(0, 5)}-${digits.slice(5)}` : digits;
|
|
91
|
+
return [
|
|
92
|
+
data?.street,
|
|
93
|
+
data?.district,
|
|
94
|
+
data?.city,
|
|
95
|
+
data?.uf || data?.state,
|
|
96
|
+
cepFormatted,
|
|
97
|
+
'Brasil',
|
|
98
|
+
]
|
|
99
|
+
.map(part => String(part || '').trim())
|
|
100
|
+
.filter(Boolean)
|
|
101
|
+
.join(', ');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function lookupNominatimCoords(data, digits) {
|
|
105
|
+
const q = buildNominatimQuery(data, digits);
|
|
106
|
+
if (!q) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const url = `https://nominatim.openstreetmap.org/search?format=json&limit=1&q=${encodeURIComponent(q)}`;
|
|
110
|
+
const response = await fetch(url, {
|
|
111
|
+
headers: {
|
|
112
|
+
Accept: 'application/json',
|
|
113
|
+
'User-Agent': 'ControleOnline-ui-default/task-637',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const rows = await response.json();
|
|
120
|
+
if (!Array.isArray(rows) || !rows[0]?.lat || !rows[0]?.lon) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
latitude: Number(rows[0].lat),
|
|
125
|
+
longitude: Number(rows[0].lon),
|
|
126
|
+
formatted: rows[0].display_name || null,
|
|
127
|
+
provider: 'nominatim-client',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function lookupPostalCode(cep) {
|
|
132
|
+
const digits = onlyDigits(cep);
|
|
133
|
+
if (digits.length !== 8) {
|
|
134
|
+
throw new Error('CEP deve ter 8 dígitos');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let payload = {cep: digits};
|
|
138
|
+
let apiError = null;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const apiData = await api.fetch(`postal-codes/${digits}`, {method: 'GET'});
|
|
142
|
+
if (apiData && typeof apiData === 'object') {
|
|
143
|
+
payload = {...payload, ...apiData, cep: digits};
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {
|
|
146
|
+
apiError = e;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!hasText(payload.street) || !hasText(payload.city)) {
|
|
150
|
+
try {
|
|
151
|
+
const viaCep = await lookupViaCep(digits);
|
|
152
|
+
payload = mergePostalLookupPayload(payload, viaCep, viaCep.provider);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
if (apiError && isPostalLookupIncomplete(payload)) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
e?.message ||
|
|
157
|
+
apiError?.message ||
|
|
158
|
+
apiError?.response?.data?.detail ||
|
|
159
|
+
'CEP inválido ou serviço indisponível',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
!isValidCoord(payload.latitude ?? payload?.map?.latitude) ||
|
|
167
|
+
!isValidCoord(payload.longitude ?? payload?.map?.longitude)
|
|
168
|
+
) {
|
|
169
|
+
try {
|
|
170
|
+
const geo = await lookupNominatimCoords(payload, digits);
|
|
171
|
+
if (geo) {
|
|
172
|
+
payload = mergePostalLookupPayload(payload, geo, geo.provider);
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
// coords optional if text fields already filled
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (isPostalLookupIncomplete(payload) && apiError && !hasText(payload.street)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
apiError?.message ||
|
|
182
|
+
apiError?.response?.data?.detail ||
|
|
183
|
+
'CEP inválido ou serviço indisponível',
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return payload;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function listCountries(q = '') {
|
|
191
|
+
const params = q ? {q} : {};
|
|
192
|
+
const data = await api.fetch('address-geo/countries', {method: 'GET', params});
|
|
193
|
+
return Array.isArray(data?.member) ? data.member : [];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function listStates(country = 'BR') {
|
|
197
|
+
const data = await api.fetch('address-geo/states', {
|
|
198
|
+
method: 'GET',
|
|
199
|
+
params: {country},
|
|
200
|
+
});
|
|
201
|
+
return Array.isArray(data?.member) ? data.member : [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function buildAddressSavePayload(form, peopleIri) {
|
|
205
|
+
const payload = {
|
|
206
|
+
street: form.street || '',
|
|
207
|
+
city: form.city || '',
|
|
208
|
+
district: form.district || '',
|
|
209
|
+
state: form.uf || form.state || '',
|
|
210
|
+
country: form.countryCode || form.country || 'BR',
|
|
211
|
+
people: peopleIri,
|
|
212
|
+
number: form.number ?? '',
|
|
213
|
+
complement: form.complement || '',
|
|
214
|
+
nickname: form.nickname || '',
|
|
215
|
+
cep: onlyDigits(form.cep),
|
|
216
|
+
latitude: form.latitude ?? 0,
|
|
217
|
+
longitude: form.longitude ?? 0,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
if (Array.isArray(form.categories)) {
|
|
221
|
+
payload.categories = form.categories;
|
|
222
|
+
} else if (Array.isArray(form.category)) {
|
|
223
|
+
payload.category = form.category;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return payload;
|
|
227
|
+
}
|
|
@@ -18,6 +18,7 @@ const normalizePreferenceSegment = value =>
|
|
|
18
18
|
.replace(/-+/g, '-');
|
|
19
19
|
|
|
20
20
|
export const resolveDefaultTablePreferenceScope = ({
|
|
21
|
+
companyId = '',
|
|
21
22
|
preferenceKey = '',
|
|
22
23
|
route = null,
|
|
23
24
|
storeName = '',
|
|
@@ -30,11 +31,13 @@ export const resolveDefaultTablePreferenceScope = ({
|
|
|
30
31
|
'unknown-route';
|
|
31
32
|
|
|
32
33
|
/*
|
|
33
|
-
* @agents DefaultTable preferences are saved as [store][route].
|
|
34
|
-
* The same store can back different screens,
|
|
35
|
-
*
|
|
34
|
+
* @agents DefaultTable preferences are saved as [company][store][route].
|
|
35
|
+
* The same store can back different screens, and the same user can switch
|
|
36
|
+
* companies, so filters, sort, view mode, and column visibility must be
|
|
37
|
+
* keyed by company, store and route together.
|
|
36
38
|
*/
|
|
37
39
|
return {
|
|
40
|
+
companyKey: normalizePreferenceSegment(companyId),
|
|
38
41
|
routeKey: routeName,
|
|
39
42
|
storeKey: normalizePreferenceSegment(storeName) || 'unknown-store',
|
|
40
43
|
};
|
|
@@ -45,10 +48,13 @@ const normalizePreferenceScope = scope => {
|
|
|
45
48
|
return null;
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
const companyKey = normalizePreferenceSegment(scope.companyKey);
|
|
48
52
|
const storeKey = normalizePreferenceSegment(scope.storeKey);
|
|
49
53
|
const routeKey = normalizePreferenceSegment(scope.routeKey);
|
|
50
54
|
|
|
51
|
-
return
|
|
55
|
+
return companyKey && storeKey && routeKey
|
|
56
|
+
? {companyKey, storeKey, routeKey}
|
|
57
|
+
: null;
|
|
52
58
|
};
|
|
53
59
|
|
|
54
60
|
const readStoredPreferences = () => {
|
|
@@ -86,7 +92,10 @@ const readScopedPreference = scope => {
|
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
const storedPreferences = readStoredPreferences();
|
|
89
|
-
const
|
|
95
|
+
const companyPreferences = storedPreferences[resolvedScope.companyKey];
|
|
96
|
+
const storePreferences = isPlainObject(companyPreferences)
|
|
97
|
+
? companyPreferences[resolvedScope.storeKey]
|
|
98
|
+
: null;
|
|
90
99
|
const routePreferences = isPlainObject(storePreferences)
|
|
91
100
|
? storePreferences[resolvedScope.routeKey]
|
|
92
101
|
: null;
|
|
@@ -101,8 +110,13 @@ const writeScopedPreference = (scope, nextValues = {}) => {
|
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
const storedPreferences = readStoredPreferences();
|
|
104
|
-
const
|
|
105
|
-
|
|
113
|
+
const companyPreferences = isPlainObject(
|
|
114
|
+
storedPreferences[resolvedScope.companyKey],
|
|
115
|
+
)
|
|
116
|
+
? storedPreferences[resolvedScope.companyKey]
|
|
117
|
+
: {};
|
|
118
|
+
const storePreferences = isPlainObject(companyPreferences[resolvedScope.storeKey])
|
|
119
|
+
? companyPreferences[resolvedScope.storeKey]
|
|
106
120
|
: {};
|
|
107
121
|
const routePreferences = isPlainObject(storePreferences[resolvedScope.routeKey])
|
|
108
122
|
? storePreferences[resolvedScope.routeKey]
|
|
@@ -110,11 +124,14 @@ const writeScopedPreference = (scope, nextValues = {}) => {
|
|
|
110
124
|
|
|
111
125
|
writeStoredPreferences({
|
|
112
126
|
...storedPreferences,
|
|
113
|
-
[resolvedScope.
|
|
114
|
-
...
|
|
115
|
-
[resolvedScope.
|
|
116
|
-
...
|
|
117
|
-
|
|
127
|
+
[resolvedScope.companyKey]: {
|
|
128
|
+
...companyPreferences,
|
|
129
|
+
[resolvedScope.storeKey]: {
|
|
130
|
+
...storePreferences,
|
|
131
|
+
[resolvedScope.routeKey]: {
|
|
132
|
+
...routePreferences,
|
|
133
|
+
...nextValues,
|
|
134
|
+
},
|
|
118
135
|
},
|
|
119
136
|
},
|
|
120
137
|
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for defaultAddressHelpers (app-community#283).
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
emptyForm,
|
|
6
|
+
hasCoordinates,
|
|
7
|
+
hydrateFromRow,
|
|
8
|
+
mergePostalCodeData,
|
|
9
|
+
onlyDigits,
|
|
10
|
+
parseOptionalCoordinate,
|
|
11
|
+
buildMapMarkerPayload,
|
|
12
|
+
} from '../../../../react/components/address/defaultAddressHelpers';
|
|
13
|
+
|
|
14
|
+
describe('defaultAddressHelpers', () => {
|
|
15
|
+
test('onlyDigits strips non-digits', () => {
|
|
16
|
+
expect(onlyDigits('12.345-678')).toBe('12345678');
|
|
17
|
+
expect(onlyDigits(null)).toBe('');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('parseOptionalCoordinate accepts number and comma decimal', () => {
|
|
21
|
+
expect(parseOptionalCoordinate('-23.5505')).toBe(-23.5505);
|
|
22
|
+
expect(parseOptionalCoordinate('-46,6333')).toBeCloseTo(-46.6333);
|
|
23
|
+
expect(parseOptionalCoordinate('')).toBeNull();
|
|
24
|
+
expect(parseOptionalCoordinate('abc')).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('hasCoordinates requires both finite values', () => {
|
|
28
|
+
expect(hasCoordinates({latitude: -23.5, longitude: -46.6})).toBe(true);
|
|
29
|
+
expect(hasCoordinates({latitude: null, longitude: -46.6})).toBe(false);
|
|
30
|
+
expect(hasCoordinates(emptyForm)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('hydrateFromRow maps nested street/district/city/state', () => {
|
|
34
|
+
const row = {
|
|
35
|
+
nickname: 'Matriz',
|
|
36
|
+
number: 100,
|
|
37
|
+
latitude: -23.5,
|
|
38
|
+
longitude: -46.6,
|
|
39
|
+
street: {
|
|
40
|
+
street: 'Av Paulista',
|
|
41
|
+
cep: {cep: '01310100'},
|
|
42
|
+
district: {
|
|
43
|
+
district: 'Bela Vista',
|
|
44
|
+
city: {
|
|
45
|
+
city: 'São Paulo',
|
|
46
|
+
state: {
|
|
47
|
+
uf: 'SP',
|
|
48
|
+
state: 'São Paulo',
|
|
49
|
+
country: {countrycode: 'BR', countryname: 'Brazil'},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const form = hydrateFromRow(row);
|
|
56
|
+
expect(form.street).toBe('Av Paulista');
|
|
57
|
+
expect(form.district).toBe('Bela Vista');
|
|
58
|
+
expect(form.city).toBe('São Paulo');
|
|
59
|
+
expect(form.uf).toBe('SP');
|
|
60
|
+
expect(form.cep).toBe('01310100');
|
|
61
|
+
expect(form.latitude).toBe(-23.5);
|
|
62
|
+
expect(form.longitude).toBe(-46.6);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('mergePostalCodeData fills lat/long from provider payload', () => {
|
|
66
|
+
const prev = {...emptyForm, street: 'Rua A'};
|
|
67
|
+
const data = {
|
|
68
|
+
cep: '01310100',
|
|
69
|
+
street: 'Av Paulista',
|
|
70
|
+
city: 'São Paulo',
|
|
71
|
+
uf: 'SP',
|
|
72
|
+
country: 'Brasil',
|
|
73
|
+
latitude: -23.561,
|
|
74
|
+
longitude: -46.656,
|
|
75
|
+
map: {staticUrl: 'https://example.com/map'},
|
|
76
|
+
provider: 'googlemaps',
|
|
77
|
+
};
|
|
78
|
+
const next = mergePostalCodeData(prev, data);
|
|
79
|
+
expect(next.latitude).toBe(-23.561);
|
|
80
|
+
expect(next.longitude).toBe(-46.656);
|
|
81
|
+
expect(next.provider).toBe('googlemaps');
|
|
82
|
+
expect(next.mapStaticUrl).toBe('https://example.com/map');
|
|
83
|
+
expect(next.countryCode).toBe('BR');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('buildMapMarkerPayload returns null without address or coords', () => {
|
|
87
|
+
expect(buildMapMarkerPayload(emptyForm)).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('buildMapMarkerPayload includes coords when present', () => {
|
|
91
|
+
const payload = buildMapMarkerPayload({
|
|
92
|
+
...emptyForm,
|
|
93
|
+
street: 'Av Paulista',
|
|
94
|
+
number: '1000',
|
|
95
|
+
city: 'São Paulo',
|
|
96
|
+
uf: 'SP',
|
|
97
|
+
latitude: -23.5,
|
|
98
|
+
longitude: -46.6,
|
|
99
|
+
});
|
|
100
|
+
expect(payload).not.toBeNull();
|
|
101
|
+
expect(payload.latitude).toBe(-23.5);
|
|
102
|
+
expect(payload.longitude).toBe(-46.6);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -35,7 +35,7 @@ describe('DefaultFile', () => {
|
|
|
35
35
|
jest.clearAllTimers();
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
it('renders backend download files with app-domain
|
|
38
|
+
it('renders backend download files with path app-domain and headers', () => {
|
|
39
39
|
let tree;
|
|
40
40
|
|
|
41
41
|
renderer.act(() => {
|
|
@@ -58,11 +58,11 @@ describe('DefaultFile', () => {
|
|
|
58
58
|
|
|
59
59
|
const image = tree.root.findByType('image');
|
|
60
60
|
|
|
61
|
-
expect(image.props.source).toEqual({
|
|
62
|
-
uri: 'https://api.controleonline.com/files/3/download
|
|
63
|
-
headers: {
|
|
64
|
-
Authorization: 'Bearer token',
|
|
65
|
-
'app-domain': 'maincompany.controleonline.com',
|
|
61
|
+
expect(image.props.source).toEqual({
|
|
62
|
+
uri: 'https://api.controleonline.com/maincompany.controleonline.com/files/3/download',
|
|
63
|
+
headers: {
|
|
64
|
+
Authorization: 'Bearer token',
|
|
65
|
+
'app-domain': 'maincompany.controleonline.com',
|
|
66
66
|
},
|
|
67
67
|
});
|
|
68
68
|
expect(image.props.resizeMode).toBe('contain');
|
|
@@ -78,4 +78,127 @@ describe('DefaultForm', () => {
|
|
|
78
78
|
const inputAfterTyping = capturedInputs[capturedInputs.length - 1];
|
|
79
79
|
expect(inputAfterTyping.value).toBe('Cliente desistiu');
|
|
80
80
|
});
|
|
81
|
+
|
|
82
|
+
it('saves hidden create payload columns with row and default values', async () => {
|
|
83
|
+
const save = jest.fn(() => Promise.resolve({id: 10}));
|
|
84
|
+
const columns = [
|
|
85
|
+
{
|
|
86
|
+
name: 'price',
|
|
87
|
+
label: 'value',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'receiver',
|
|
91
|
+
createPayload: true,
|
|
92
|
+
visibleForm: false,
|
|
93
|
+
saveFormat: value => `/people/${value}`,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'invoiceType',
|
|
97
|
+
createPayload: true,
|
|
98
|
+
defaultValue: 'invoice',
|
|
99
|
+
visibleForm: false,
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
let tree;
|
|
103
|
+
|
|
104
|
+
await renderer.act(async () => {
|
|
105
|
+
tree = renderer.create(
|
|
106
|
+
React.createElement(DefaultForm, {
|
|
107
|
+
actions: {save},
|
|
108
|
+
columns,
|
|
109
|
+
mode: 'create',
|
|
110
|
+
row: {receiver: 21},
|
|
111
|
+
storeName: 'invoice',
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await renderer.act(async () => {
|
|
117
|
+
capturedInputs[capturedInputs.length - 1].onChangeValue('123.45');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const saveButton = tree.root.findAllByType('TouchableOpacity')[1];
|
|
121
|
+
|
|
122
|
+
await renderer.act(async () => {
|
|
123
|
+
await saveButton.props.onPress();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(save).toHaveBeenCalledWith({
|
|
127
|
+
price: 123.45,
|
|
128
|
+
receiver: '/people/21',
|
|
129
|
+
invoiceType: 'invoice',
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('preserves primitive ids for hidden create payload list columns', async () => {
|
|
134
|
+
const save = jest.fn(() => Promise.resolve({id: 11}));
|
|
135
|
+
const columns = [
|
|
136
|
+
{
|
|
137
|
+
name: 'price',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'receiver',
|
|
141
|
+
createPayload: true,
|
|
142
|
+
list: 'people/getItems',
|
|
143
|
+
visibleForm: false,
|
|
144
|
+
formatList: value => {
|
|
145
|
+
if (!value || typeof value !== 'object') {
|
|
146
|
+
return {value, label: String(value || '')};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {value: value.id, label: value.name};
|
|
150
|
+
},
|
|
151
|
+
saveFormat: value => `/people/${value?.value || value}`,
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
let tree;
|
|
155
|
+
|
|
156
|
+
await renderer.act(async () => {
|
|
157
|
+
tree = renderer.create(
|
|
158
|
+
React.createElement(DefaultForm, {
|
|
159
|
+
actions: {save},
|
|
160
|
+
columns,
|
|
161
|
+
mode: 'create',
|
|
162
|
+
row: {receiver: 21},
|
|
163
|
+
storeName: 'invoice',
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await renderer.act(async () => {
|
|
169
|
+
capturedInputs[capturedInputs.length - 1].onChangeValue('1.23');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await renderer.act(async () => {
|
|
173
|
+
await tree.root.findAllByType('TouchableOpacity')[1].props.onPress();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(save).toHaveBeenCalledWith({
|
|
177
|
+
price: 1.23,
|
|
178
|
+
receiver: '/people/21',
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('uses explicit form labels before translated column fallbacks', async () => {
|
|
183
|
+
const columns = [
|
|
184
|
+
{
|
|
185
|
+
name: 'paymentType',
|
|
186
|
+
label: 'paymentType.paymentType',
|
|
187
|
+
formLabel: 'Tipo de pagamento',
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
await renderer.act(async () => {
|
|
192
|
+
renderer.create(
|
|
193
|
+
React.createElement(DefaultForm, {
|
|
194
|
+
actions: {save: jest.fn()},
|
|
195
|
+
columns,
|
|
196
|
+
mode: 'create',
|
|
197
|
+
storeName: 'invoice',
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(capturedInputs[capturedInputs.length - 1].label).toBe('Tipo de pagamento');
|
|
203
|
+
});
|
|
81
204
|
});
|