@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,133 @@
|
|
|
1
|
+
import {StyleSheet} from 'react-native';
|
|
2
|
+
|
|
3
|
+
export default StyleSheet.create({
|
|
4
|
+
scroll: {flex: 1, width: '100%'},
|
|
5
|
+
container: {padding: 16, gap: 16},
|
|
6
|
+
containerDesktop: {
|
|
7
|
+
flexDirection: 'row',
|
|
8
|
+
alignItems: 'flex-start',
|
|
9
|
+
alignSelf: 'center',
|
|
10
|
+
width: '100%',
|
|
11
|
+
maxWidth: 1280,
|
|
12
|
+
paddingHorizontal: 32,
|
|
13
|
+
paddingVertical: 24,
|
|
14
|
+
gap: 24,
|
|
15
|
+
},
|
|
16
|
+
formPane: {width: '100%'},
|
|
17
|
+
formPaneDesktop: {flex: 1, maxWidth: 560},
|
|
18
|
+
field: {marginBottom: 10, position: 'relative', zIndex: 1},
|
|
19
|
+
fieldRaised: {zIndex: 30, elevation: 8},
|
|
20
|
+
label: {fontSize: 13, fontWeight: '600', marginBottom: 4, color: '#334155'},
|
|
21
|
+
input: {
|
|
22
|
+
borderWidth: 1,
|
|
23
|
+
borderColor: '#CBD5E1',
|
|
24
|
+
borderRadius: 8,
|
|
25
|
+
paddingHorizontal: 12,
|
|
26
|
+
paddingVertical: 10,
|
|
27
|
+
backgroundColor: '#fff',
|
|
28
|
+
},
|
|
29
|
+
select: {
|
|
30
|
+
borderWidth: 1,
|
|
31
|
+
borderColor: '#CBD5E1',
|
|
32
|
+
borderRadius: 8,
|
|
33
|
+
paddingHorizontal: 12,
|
|
34
|
+
paddingVertical: 12,
|
|
35
|
+
backgroundColor: '#F8FAFC',
|
|
36
|
+
},
|
|
37
|
+
dropdown: {
|
|
38
|
+
maxHeight: 180,
|
|
39
|
+
borderWidth: 1,
|
|
40
|
+
borderColor: '#E2E8F0',
|
|
41
|
+
borderRadius: 8,
|
|
42
|
+
marginTop: 4,
|
|
43
|
+
backgroundColor: '#fff',
|
|
44
|
+
overflow: 'hidden',
|
|
45
|
+
zIndex: 40,
|
|
46
|
+
elevation: 8,
|
|
47
|
+
},
|
|
48
|
+
option: {paddingHorizontal: 12, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#F1F5F9'},
|
|
49
|
+
row: {flexDirection: 'row', alignItems: 'center'},
|
|
50
|
+
flex: {flex: 1},
|
|
51
|
+
loader: {marginLeft: 8},
|
|
52
|
+
error: {color: '#B91C1C', marginBottom: 8},
|
|
53
|
+
hint: {color: '#64748B', marginBottom: 8, fontSize: 12},
|
|
54
|
+
mapPane: {
|
|
55
|
+
width: '100%',
|
|
56
|
+
borderWidth: 1,
|
|
57
|
+
borderColor: '#E2E8F0',
|
|
58
|
+
borderRadius: 12,
|
|
59
|
+
backgroundColor: '#F8FAFC',
|
|
60
|
+
padding: 16,
|
|
61
|
+
gap: 12,
|
|
62
|
+
},
|
|
63
|
+
mapPaneDesktop: {flex: 1, minHeight: 560},
|
|
64
|
+
mapPaneTitle: {fontWeight: '700', fontSize: 15, color: '#0F172A'},
|
|
65
|
+
mapImage: {
|
|
66
|
+
width: '100%',
|
|
67
|
+
height: 220,
|
|
68
|
+
borderRadius: 10,
|
|
69
|
+
backgroundColor: '#E2E8F0',
|
|
70
|
+
},
|
|
71
|
+
mapImageDesktop: {height: 360},
|
|
72
|
+
liveMap: {
|
|
73
|
+
width: '100%',
|
|
74
|
+
height: 220,
|
|
75
|
+
borderRadius: 10,
|
|
76
|
+
overflow: 'hidden',
|
|
77
|
+
backgroundColor: '#E2E8F0',
|
|
78
|
+
},
|
|
79
|
+
liveMapDesktop: {height: 360},
|
|
80
|
+
facadeImage: {
|
|
81
|
+
width: '100%',
|
|
82
|
+
height: 180,
|
|
83
|
+
borderRadius: 10,
|
|
84
|
+
backgroundColor: '#E2E8F0',
|
|
85
|
+
},
|
|
86
|
+
mapPlaceholder: {
|
|
87
|
+
height: 220,
|
|
88
|
+
borderRadius: 10,
|
|
89
|
+
backgroundColor: '#E2E8F0',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
justifyContent: 'center',
|
|
92
|
+
padding: 18,
|
|
93
|
+
},
|
|
94
|
+
facadePlaceholder: {
|
|
95
|
+
minHeight: 120,
|
|
96
|
+
borderRadius: 10,
|
|
97
|
+
backgroundColor: '#E2E8F0',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
justifyContent: 'center',
|
|
100
|
+
padding: 18,
|
|
101
|
+
},
|
|
102
|
+
mapPlaceholderTitle: {
|
|
103
|
+
fontWeight: '700',
|
|
104
|
+
color: '#334155',
|
|
105
|
+
marginBottom: 6,
|
|
106
|
+
},
|
|
107
|
+
mapPlaceholderText: {
|
|
108
|
+
color: '#64748B',
|
|
109
|
+
textAlign: 'center',
|
|
110
|
+
fontSize: 13,
|
|
111
|
+
lineHeight: 18,
|
|
112
|
+
},
|
|
113
|
+
actions: {flexDirection: 'row', justifyContent: 'flex-end', gap: 12, marginTop: 16},
|
|
114
|
+
btnSecondary: {paddingHorizontal: 16, paddingVertical: 12},
|
|
115
|
+
btnPrimary: {
|
|
116
|
+
backgroundColor: '#1D4ED8',
|
|
117
|
+
paddingHorizontal: 18,
|
|
118
|
+
paddingVertical: 12,
|
|
119
|
+
borderRadius: 8,
|
|
120
|
+
},
|
|
121
|
+
btnPrimaryText: {color: '#fff', fontWeight: '600'},
|
|
122
|
+
coordRow: {
|
|
123
|
+
flexDirection: 'row',
|
|
124
|
+
gap: 12,
|
|
125
|
+
},
|
|
126
|
+
coordField: {
|
|
127
|
+
flex: 1,
|
|
128
|
+
},
|
|
129
|
+
inputReadonly: {
|
|
130
|
+
backgroundColor: '#F1F5F9',
|
|
131
|
+
color: '#475569',
|
|
132
|
+
},
|
|
133
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Image, Text, View} from 'react-native';
|
|
3
|
+
import DefaultMap from '../map/DefaultMap';
|
|
4
|
+
import {hasCoordinates} from './defaultAddressHelpers';
|
|
5
|
+
import {styles} from './defaultAddressStyles';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Map + facade preview pane for DefaultAddress.
|
|
9
|
+
*/
|
|
10
|
+
export default function DefaultAddressMapPane({
|
|
11
|
+
form,
|
|
12
|
+
mapMarkerPayload,
|
|
13
|
+
mapUserCoordinates,
|
|
14
|
+
isDesktop,
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<View style={[styles.mapPane, isDesktop && styles.mapPaneDesktop]}>
|
|
18
|
+
<Text style={styles.mapPaneTitle}>Mapa</Text>
|
|
19
|
+
{hasCoordinates(form) || mapMarkerPayload ? (
|
|
20
|
+
<View style={[styles.liveMap, isDesktop && styles.liveMapDesktop]}>
|
|
21
|
+
<DefaultMap
|
|
22
|
+
markerPayloads={mapMarkerPayload ? [mapMarkerPayload] : []}
|
|
23
|
+
userCoordinates={mapUserCoordinates}
|
|
24
|
+
/>
|
|
25
|
+
</View>
|
|
26
|
+
) : form.mapStaticUrl ? (
|
|
27
|
+
<Image
|
|
28
|
+
source={{uri: form.mapStaticUrl}}
|
|
29
|
+
style={[styles.mapImage, isDesktop && styles.mapImageDesktop]}
|
|
30
|
+
resizeMode="cover"
|
|
31
|
+
/>
|
|
32
|
+
) : (
|
|
33
|
+
<View style={[styles.mapPlaceholder, isDesktop && styles.mapImageDesktop]}>
|
|
34
|
+
<Text style={styles.mapPlaceholderTitle}>Mapa indisponível</Text>
|
|
35
|
+
<Text style={styles.mapPlaceholderText}>
|
|
36
|
+
Consulte um CEP para carregar a localização quando a API retornar a
|
|
37
|
+
imagem, ou informe latitude/longitude manualmente.
|
|
38
|
+
</Text>
|
|
39
|
+
</View>
|
|
40
|
+
)}
|
|
41
|
+
|
|
42
|
+
{/* Fachada only when provider returned street-view URL (not OSM). */}
|
|
43
|
+
{form.facadeUrl ? (
|
|
44
|
+
<>
|
|
45
|
+
<Text style={styles.mapPaneTitle}>Fachada</Text>
|
|
46
|
+
<Image
|
|
47
|
+
source={{uri: form.facadeUrl}}
|
|
48
|
+
style={styles.facadeImage}
|
|
49
|
+
resizeMode="cover"
|
|
50
|
+
/>
|
|
51
|
+
</>
|
|
52
|
+
) : null}
|
|
53
|
+
</View>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {View, TextInput} from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Latitude / longitude fields.
|
|
6
|
+
* Read-only when both coordinates are present and the user has not started
|
|
7
|
+
* a manual edit; editable when coords are missing so the user can type them.
|
|
8
|
+
*/
|
|
9
|
+
export default function LatLonReadonlyFields({
|
|
10
|
+
form,
|
|
11
|
+
styles,
|
|
12
|
+
Field,
|
|
13
|
+
editable = false,
|
|
14
|
+
onChange,
|
|
15
|
+
}) {
|
|
16
|
+
const formatCoord = value =>
|
|
17
|
+
value == null || value === '' ? '' : String(value);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<View style={styles.coordRow}>
|
|
21
|
+
<View style={styles.coordField}>
|
|
22
|
+
<Field label="Latitude">
|
|
23
|
+
<TextInput
|
|
24
|
+
testID="address-latitude-input"
|
|
25
|
+
style={[styles.input, editable ? null : styles.inputReadonly]}
|
|
26
|
+
value={formatCoord(form?.latitude)}
|
|
27
|
+
editable={editable}
|
|
28
|
+
selectTextOnFocus={editable}
|
|
29
|
+
onChangeText={v => onChange?.('latitude', v)}
|
|
30
|
+
placeholder="—"
|
|
31
|
+
keyboardType="decimal-pad"
|
|
32
|
+
/>
|
|
33
|
+
</Field>
|
|
34
|
+
</View>
|
|
35
|
+
<View style={styles.coordField}>
|
|
36
|
+
<Field label="Longitude">
|
|
37
|
+
<TextInput
|
|
38
|
+
testID="address-longitude-input"
|
|
39
|
+
style={[styles.input, editable ? null : styles.inputReadonly]}
|
|
40
|
+
value={formatCoord(form?.longitude)}
|
|
41
|
+
editable={editable}
|
|
42
|
+
selectTextOnFocus={editable}
|
|
43
|
+
onChangeText={v => onChange?.('longitude', v)}
|
|
44
|
+
placeholder="—"
|
|
45
|
+
keyboardType="decimal-pad"
|
|
46
|
+
/>
|
|
47
|
+
</Field>
|
|
48
|
+
</View>
|
|
49
|
+
</View>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for DefaultAddress — keep the component under the 500-line limit.
|
|
3
|
+
* @see app-community#283 (franchise address lat/long + manual fields)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const emptyForm = {
|
|
7
|
+
nickname: '',
|
|
8
|
+
cep: '',
|
|
9
|
+
street: '',
|
|
10
|
+
number: '',
|
|
11
|
+
complement: '',
|
|
12
|
+
district: '',
|
|
13
|
+
city: '',
|
|
14
|
+
uf: '',
|
|
15
|
+
stateName: '',
|
|
16
|
+
countryCode: 'BR',
|
|
17
|
+
countryName: 'Brazil',
|
|
18
|
+
latitude: null,
|
|
19
|
+
longitude: null,
|
|
20
|
+
mapStaticUrl: null,
|
|
21
|
+
facadeUrl: null,
|
|
22
|
+
provider: null,
|
|
23
|
+
categories: [],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const onlyDigits = value => String(value || '').replace(/\D+/g, '');
|
|
27
|
+
|
|
28
|
+
export const hydrateFromRow = row => {
|
|
29
|
+
if (!row || typeof row !== 'object') {
|
|
30
|
+
return {...emptyForm};
|
|
31
|
+
}
|
|
32
|
+
const street = row?.street?.street || row?.street || '';
|
|
33
|
+
const district = row?.street?.district?.district || row?.district || '';
|
|
34
|
+
const city = row?.street?.district?.city?.city || row?.city || '';
|
|
35
|
+
const stateEntity = row?.street?.district?.city?.state || row?.state;
|
|
36
|
+
const countryEntity = stateEntity?.country || row?.country;
|
|
37
|
+
return {
|
|
38
|
+
...emptyForm,
|
|
39
|
+
nickname: row?.nickname || '',
|
|
40
|
+
cep: String(row?.street?.cep?.cep || row?.cep || row?.postal_code || ''),
|
|
41
|
+
street: typeof street === 'string' ? street : '',
|
|
42
|
+
number: row?.number != null ? String(row.number) : '',
|
|
43
|
+
complement: row?.complement || '',
|
|
44
|
+
district,
|
|
45
|
+
city,
|
|
46
|
+
uf: stateEntity?.uf || row?.uf || '',
|
|
47
|
+
stateName: stateEntity?.state || '',
|
|
48
|
+
countryCode: countryEntity?.countrycode || countryEntity?.code || 'BR',
|
|
49
|
+
countryName: countryEntity?.countryname || countryEntity?.name || 'Brazil',
|
|
50
|
+
latitude: row?.latitude ?? null,
|
|
51
|
+
longitude: row?.longitude ?? null,
|
|
52
|
+
categories: Array.isArray(row?.categories)
|
|
53
|
+
? row.categories
|
|
54
|
+
: Array.isArray(row?.category)
|
|
55
|
+
? row.category
|
|
56
|
+
: [],
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const hasAddressText = form =>
|
|
61
|
+
[form?.street, form?.number, form?.district, form?.city, form?.uf, form?.cep].some(
|
|
62
|
+
value => String(value || '').trim().length > 0,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
export const hasCoordinates = form =>
|
|
66
|
+
Number.isFinite(Number(form?.latitude)) && Number.isFinite(Number(form?.longitude));
|
|
67
|
+
|
|
68
|
+
export const parseOptionalCoordinate = value => {
|
|
69
|
+
if (value === null || value === undefined || value === '') {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const n = Number(String(value).trim().replace(',', '.'));
|
|
73
|
+
return Number.isFinite(n) ? n : null;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const mergePostalCodeData = (prev, data, {preserveFilledFields = false} = {}) => {
|
|
77
|
+
const keep = (key, nextValue) =>
|
|
78
|
+
preserveFilledFields && String(prev[key] || '').trim()
|
|
79
|
+
? prev[key]
|
|
80
|
+
: nextValue || prev[key];
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
...prev,
|
|
84
|
+
cep: onlyDigits(data.cep || prev.cep),
|
|
85
|
+
street: keep('street', data.street),
|
|
86
|
+
district: keep('district', data.district),
|
|
87
|
+
city: keep('city', data.city),
|
|
88
|
+
uf: keep('uf', data.uf || data.state),
|
|
89
|
+
stateName: keep('stateName', data.state),
|
|
90
|
+
countryCode:
|
|
91
|
+
data.country === 'Brasil' || data.country === 'Brazil'
|
|
92
|
+
? 'BR'
|
|
93
|
+
: data.country || prev.countryCode,
|
|
94
|
+
countryName:
|
|
95
|
+
data.country === 'Brasil' ? 'Brazil' : data.country || prev.countryName,
|
|
96
|
+
latitude: data.latitude ?? data.map?.latitude ?? prev.latitude,
|
|
97
|
+
longitude: data.longitude ?? data.map?.longitude ?? prev.longitude,
|
|
98
|
+
mapStaticUrl: data.map?.staticUrl || prev.mapStaticUrl || null,
|
|
99
|
+
facadeUrl: data.facade?.streetViewUrl || prev.facadeUrl || null,
|
|
100
|
+
provider: data.provider || prev.provider || null,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const getCurrentCoordinates = () =>
|
|
105
|
+
new Promise(resolve => {
|
|
106
|
+
const geolocation =
|
|
107
|
+
typeof navigator !== 'undefined' ? navigator.geolocation : null;
|
|
108
|
+
|
|
109
|
+
if (!geolocation?.getCurrentPosition) {
|
|
110
|
+
resolve(null);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
geolocation.getCurrentPosition(
|
|
115
|
+
position => {
|
|
116
|
+
const latitude = Number(position?.coords?.latitude);
|
|
117
|
+
const longitude = Number(position?.coords?.longitude);
|
|
118
|
+
|
|
119
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
120
|
+
resolve(null);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
resolve({latitude, longitude});
|
|
125
|
+
},
|
|
126
|
+
() => resolve(null),
|
|
127
|
+
{enableHighAccuracy: true, timeout: 8000, maximumAge: 60000},
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export const buildMapMarkerPayload = form => {
|
|
132
|
+
if (!hasAddressText(form) && !hasCoordinates(form)) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const addressLine = [form.street, form.number].filter(Boolean).join(', ');
|
|
137
|
+
const addressExtra = [
|
|
138
|
+
form.district,
|
|
139
|
+
[form.city, form.uf].filter(Boolean).join(' - '),
|
|
140
|
+
form.countryName || form.countryCode,
|
|
141
|
+
form.cep,
|
|
142
|
+
]
|
|
143
|
+
.filter(Boolean)
|
|
144
|
+
.join(' • ');
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
id: 'default-address-preview',
|
|
148
|
+
title: form.nickname || 'Endereco',
|
|
149
|
+
addressLine: addressLine || addressExtra || 'Endereco',
|
|
150
|
+
addressExtra,
|
|
151
|
+
latitude: form.latitude,
|
|
152
|
+
longitude: form.longitude,
|
|
153
|
+
geocodeQuery: [
|
|
154
|
+
addressLine,
|
|
155
|
+
form.district,
|
|
156
|
+
[form.city, form.uf].filter(Boolean).join(' - '),
|
|
157
|
+
form.countryName || form.countryCode,
|
|
158
|
+
form.cep,
|
|
159
|
+
]
|
|
160
|
+
.filter(Boolean)
|
|
161
|
+
.join(', '),
|
|
162
|
+
};
|
|
163
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {StyleSheet} from 'react-native';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Styles for DefaultAddress — extracted to keep the component under 500 lines.
|
|
5
|
+
*/
|
|
6
|
+
export const styles = StyleSheet.create({
|
|
7
|
+
scroll: {flex: 1, backgroundColor: '#fff'},
|
|
8
|
+
container: {padding: 16, paddingBottom: 32},
|
|
9
|
+
containerDesktop: {
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
alignItems: 'flex-start',
|
|
12
|
+
gap: 24,
|
|
13
|
+
maxWidth: 1200,
|
|
14
|
+
alignSelf: 'center',
|
|
15
|
+
width: '100%',
|
|
16
|
+
},
|
|
17
|
+
formPane: {width: '100%'},
|
|
18
|
+
formPaneDesktop: {flex: 1, maxWidth: 560},
|
|
19
|
+
field: {marginBottom: 10, position: 'relative', zIndex: 1},
|
|
20
|
+
fieldRaised: {zIndex: 30, elevation: 8},
|
|
21
|
+
label: {fontSize: 13, fontWeight: '600', marginBottom: 4, color: '#334155'},
|
|
22
|
+
input: {
|
|
23
|
+
borderWidth: 1,
|
|
24
|
+
borderColor: '#CBD5E1',
|
|
25
|
+
borderRadius: 8,
|
|
26
|
+
paddingHorizontal: 12,
|
|
27
|
+
paddingVertical: 10,
|
|
28
|
+
backgroundColor: '#fff',
|
|
29
|
+
},
|
|
30
|
+
select: {
|
|
31
|
+
borderWidth: 1,
|
|
32
|
+
borderColor: '#CBD5E1',
|
|
33
|
+
borderRadius: 8,
|
|
34
|
+
paddingHorizontal: 12,
|
|
35
|
+
paddingVertical: 12,
|
|
36
|
+
backgroundColor: '#F8FAFC',
|
|
37
|
+
},
|
|
38
|
+
dropdown: {
|
|
39
|
+
maxHeight: 220,
|
|
40
|
+
borderWidth: 1,
|
|
41
|
+
borderColor: '#E2E8F0',
|
|
42
|
+
borderRadius: 8,
|
|
43
|
+
marginTop: 4,
|
|
44
|
+
backgroundColor: '#fff',
|
|
45
|
+
zIndex: 40,
|
|
46
|
+
elevation: 8,
|
|
47
|
+
},
|
|
48
|
+
option: {
|
|
49
|
+
paddingHorizontal: 12,
|
|
50
|
+
paddingVertical: 10,
|
|
51
|
+
borderBottomWidth: 1,
|
|
52
|
+
borderBottomColor: '#F1F5F9',
|
|
53
|
+
},
|
|
54
|
+
row: {flexDirection: 'row', alignItems: 'center'},
|
|
55
|
+
flex: {flex: 1},
|
|
56
|
+
half: {flex: 1},
|
|
57
|
+
gap8: {gap: 8},
|
|
58
|
+
loader: {marginLeft: 8},
|
|
59
|
+
error: {color: '#B91C1C', marginBottom: 8},
|
|
60
|
+
hint: {color: '#64748B', marginBottom: 8, fontSize: 12},
|
|
61
|
+
mapPane: {
|
|
62
|
+
width: '100%',
|
|
63
|
+
borderWidth: 1,
|
|
64
|
+
borderColor: '#E2E8F0',
|
|
65
|
+
borderRadius: 12,
|
|
66
|
+
backgroundColor: '#F8FAFC',
|
|
67
|
+
padding: 16,
|
|
68
|
+
gap: 12,
|
|
69
|
+
},
|
|
70
|
+
mapPaneDesktop: {flex: 1, minHeight: 560},
|
|
71
|
+
mapPaneTitle: {fontWeight: '700', fontSize: 15, color: '#0F172A'},
|
|
72
|
+
mapImage: {
|
|
73
|
+
width: '100%',
|
|
74
|
+
height: 220,
|
|
75
|
+
borderRadius: 10,
|
|
76
|
+
backgroundColor: '#E2E8F0',
|
|
77
|
+
},
|
|
78
|
+
mapImageDesktop: {height: 360},
|
|
79
|
+
liveMap: {
|
|
80
|
+
width: '100%',
|
|
81
|
+
height: 220,
|
|
82
|
+
borderRadius: 10,
|
|
83
|
+
overflow: 'hidden',
|
|
84
|
+
backgroundColor: '#E2E8F0',
|
|
85
|
+
},
|
|
86
|
+
liveMapDesktop: {height: 360},
|
|
87
|
+
facadeImage: {
|
|
88
|
+
width: '100%',
|
|
89
|
+
height: 180,
|
|
90
|
+
borderRadius: 10,
|
|
91
|
+
backgroundColor: '#E2E8F0',
|
|
92
|
+
},
|
|
93
|
+
mapPlaceholder: {
|
|
94
|
+
height: 220,
|
|
95
|
+
borderRadius: 10,
|
|
96
|
+
backgroundColor: '#E2E8F0',
|
|
97
|
+
alignItems: 'center',
|
|
98
|
+
justifyContent: 'center',
|
|
99
|
+
padding: 18,
|
|
100
|
+
},
|
|
101
|
+
facadePlaceholder: {
|
|
102
|
+
minHeight: 120,
|
|
103
|
+
borderRadius: 10,
|
|
104
|
+
backgroundColor: '#E2E8F0',
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
justifyContent: 'center',
|
|
107
|
+
padding: 18,
|
|
108
|
+
},
|
|
109
|
+
mapPlaceholderTitle: {
|
|
110
|
+
fontWeight: '700',
|
|
111
|
+
color: '#334155',
|
|
112
|
+
marginBottom: 6,
|
|
113
|
+
},
|
|
114
|
+
mapPlaceholderText: {
|
|
115
|
+
color: '#64748B',
|
|
116
|
+
textAlign: 'center',
|
|
117
|
+
fontSize: 13,
|
|
118
|
+
lineHeight: 18,
|
|
119
|
+
},
|
|
120
|
+
actions: {
|
|
121
|
+
flexDirection: 'row',
|
|
122
|
+
justifyContent: 'flex-end',
|
|
123
|
+
gap: 12,
|
|
124
|
+
marginTop: 16,
|
|
125
|
+
},
|
|
126
|
+
btnSecondary: {paddingHorizontal: 16, paddingVertical: 12},
|
|
127
|
+
btnPrimary: {
|
|
128
|
+
backgroundColor: '#2563EB',
|
|
129
|
+
borderRadius: 8,
|
|
130
|
+
paddingHorizontal: 18,
|
|
131
|
+
paddingVertical: 12,
|
|
132
|
+
},
|
|
133
|
+
btnPrimaryText: {color: '#fff', fontWeight: '600'},
|
|
134
|
+
btnSecondaryText: {color: '#475569', fontWeight: '600'},
|
|
135
|
+
btnDisabled: {opacity: 0.6},
|
|
136
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for DefaultAddress form (hydration, CEP merge, geolocation).
|
|
3
|
+
*/
|
|
4
|
+
const emptyForm = {
|
|
5
|
+
nickname: '',
|
|
6
|
+
cep: '',
|
|
7
|
+
street: '',
|
|
8
|
+
number: '',
|
|
9
|
+
complement: '',
|
|
10
|
+
district: '',
|
|
11
|
+
city: '',
|
|
12
|
+
uf: '',
|
|
13
|
+
stateName: '',
|
|
14
|
+
countryCode: 'BR',
|
|
15
|
+
countryName: 'Brazil',
|
|
16
|
+
latitude: null,
|
|
17
|
+
longitude: null,
|
|
18
|
+
mapStaticUrl: null,
|
|
19
|
+
facadeUrl: null,
|
|
20
|
+
provider: null,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const hydrateFromRow = row => {
|
|
24
|
+
if (!row || typeof row !== 'object') {
|
|
25
|
+
return {...emptyForm};
|
|
26
|
+
}
|
|
27
|
+
const street = row?.street?.street || row?.street || '';
|
|
28
|
+
const district = row?.street?.district?.district || row?.district || '';
|
|
29
|
+
const city = row?.street?.district?.city?.city || row?.city || '';
|
|
30
|
+
const stateEntity = row?.street?.district?.city?.state || row?.state;
|
|
31
|
+
const countryEntity = stateEntity?.country || row?.country;
|
|
32
|
+
return {
|
|
33
|
+
...emptyForm,
|
|
34
|
+
nickname: row?.nickname || '',
|
|
35
|
+
cep: String(row?.street?.cep?.cep || row?.cep || row?.postal_code || ''),
|
|
36
|
+
street: typeof street === 'string' ? street : '',
|
|
37
|
+
number: row?.number != null ? String(row.number) : '',
|
|
38
|
+
complement: row?.complement || '',
|
|
39
|
+
district,
|
|
40
|
+
city,
|
|
41
|
+
uf: stateEntity?.uf || row?.uf || '',
|
|
42
|
+
stateName: stateEntity?.state || '',
|
|
43
|
+
countryCode: countryEntity?.countrycode || countryEntity?.code || 'BR',
|
|
44
|
+
countryName: countryEntity?.countryname || countryEntity?.name || 'Brazil',
|
|
45
|
+
latitude: row?.latitude ?? null,
|
|
46
|
+
longitude: row?.longitude ?? null,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const onlyDigits = value => String(value || '').replace(/\D+/g, '');
|
|
51
|
+
|
|
52
|
+
const hasAddressText = form =>
|
|
53
|
+
[
|
|
54
|
+
form?.street,
|
|
55
|
+
form?.number,
|
|
56
|
+
form?.district,
|
|
57
|
+
form?.city,
|
|
58
|
+
form?.uf,
|
|
59
|
+
form?.cep,
|
|
60
|
+
].some(value => String(value || '').trim().length > 0);
|
|
61
|
+
|
|
62
|
+
const hasCoordinates = form =>
|
|
63
|
+
Number.isFinite(Number(form?.latitude)) && Number.isFinite(Number(form?.longitude));
|
|
64
|
+
|
|
65
|
+
const mergePostalCodeData = (prev, data, {preserveFilledFields = false} = {}) => {
|
|
66
|
+
const keep = (key, nextValue) =>
|
|
67
|
+
preserveFilledFields && String(prev[key] || '').trim()
|
|
68
|
+
? prev[key]
|
|
69
|
+
: nextValue || prev[key];
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
...prev,
|
|
73
|
+
cep: onlyDigits(data.cep || prev.cep),
|
|
74
|
+
street: keep('street', data.street),
|
|
75
|
+
district: keep('district', data.district),
|
|
76
|
+
city: keep('city', data.city),
|
|
77
|
+
uf: keep('uf', data.uf || data.state),
|
|
78
|
+
stateName: keep('stateName', data.state),
|
|
79
|
+
countryCode:
|
|
80
|
+
data.country === 'Brasil' || data.country === 'Brazil'
|
|
81
|
+
? 'BR'
|
|
82
|
+
: data.country || prev.countryCode,
|
|
83
|
+
countryName:
|
|
84
|
+
data.country === 'Brasil' ? 'Brazil' : data.country || prev.countryName,
|
|
85
|
+
latitude: data.latitude ?? data.map?.latitude ?? prev.latitude,
|
|
86
|
+
longitude: data.longitude ?? data.map?.longitude ?? prev.longitude,
|
|
87
|
+
mapStaticUrl: data.map?.staticUrl || prev.mapStaticUrl || null,
|
|
88
|
+
facadeUrl: data.facade?.streetViewUrl || prev.facadeUrl || null,
|
|
89
|
+
provider: data.provider || prev.provider || null,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getCurrentCoordinates = () =>
|
|
94
|
+
new Promise(resolve => {
|
|
95
|
+
const geolocation =
|
|
96
|
+
typeof navigator !== 'undefined' ? navigator.geolocation : null;
|
|
97
|
+
|
|
98
|
+
if (!geolocation?.getCurrentPosition) {
|
|
99
|
+
resolve(null);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
geolocation.getCurrentPosition(
|
|
104
|
+
position => {
|
|
105
|
+
const latitude = Number(position?.coords?.latitude);
|
|
106
|
+
const longitude = Number(position?.coords?.longitude);
|
|
107
|
+
|
|
108
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
109
|
+
resolve(null);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
resolve({latitude, longitude});
|
|
114
|
+
},
|
|
115
|
+
() => resolve(null),
|
|
116
|
+
{enableHighAccuracy: true, timeout: 8000, maximumAge: 60000},
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
emptyForm,
|
|
123
|
+
hydrateFromRow,
|
|
124
|
+
onlyDigits,
|
|
125
|
+
hasAddressText,
|
|
126
|
+
hasCoordinates,
|
|
127
|
+
mergePostalCodeData,
|
|
128
|
+
getCurrentCoordinates,
|
|
129
|
+
};
|