@controleonline/ui-default 1.0.263
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/agents/developer.agent.md +30 -0
- package/.github/agents/devops.agent.md +30 -0
- package/.github/agents/qa.agent.md +30 -0
- package/.github/agents/security.agent.md +30 -0
- package/.scrutinizer.yml +20 -0
- package/AGENTS.md +47 -0
- package/FUNDING.yml +1 -0
- package/package.json +21 -0
- package/src/react/components/errors/DefaultErrors.js +360 -0
- package/src/react/components/files/DefaultFile.js +46 -0
- package/src/react/components/filters/CompactFilterSelector.js +262 -0
- package/src/react/components/filters/CompactFilterSelector.styles.js +124 -0
- package/src/react/components/filters/DateShortcutFilter.js +264 -0
- package/src/react/components/filters/DateShortcutFilter.styles.js +82 -0
- package/src/react/components/filters/DefaultColumnFilter.js +97 -0
- package/src/react/components/filters/DefaultColumnFilter.styles.js +21 -0
- package/src/react/components/filters/DefaultExternalFilters.js +441 -0
- package/src/react/components/filters/DefaultExternalFilters.styles.js +177 -0
- package/src/react/components/filters/DefaultSearch.js +103 -0
- package/src/react/components/filters/DefaultSearch.styles.js +70 -0
- package/src/react/components/filters/dateFilterSelection.js +29 -0
- package/src/react/components/form/DefaultForm.js +198 -0
- package/src/react/components/form/DefaultForm.styles.js +70 -0
- package/src/react/components/help/DefaultTooltip.js +87 -0
- package/src/react/components/help/DefaultTooltip.styles.js +61 -0
- package/src/react/components/inputs/DefaultInput.js +160 -0
- package/src/react/components/inputs/DefaultInput.styles.js +93 -0
- package/src/react/components/inputs/DefaultSelect.js +192 -0
- package/src/react/components/inputs/DefaultSelect.styles.js +65 -0
- package/src/react/components/inputs/defaultInputUtils.js +230 -0
- package/src/react/components/map/DefaultGoogleMap.styles.js +9 -0
- package/src/react/components/map/DefaultGoogleMap.web.js +698 -0
- package/src/react/components/map/DefaultMap.native.js +71 -0
- package/src/react/components/map/DefaultMap.shared.js +762 -0
- package/src/react/components/map/DefaultMap.styles.js +16 -0
- package/src/react/components/map/DefaultMap.web.js +66 -0
- package/src/react/components/map/DefaultNativeMap.native.js +615 -0
- package/src/react/components/map/DefaultNativeMap.shared.js +532 -0
- package/src/react/components/map/DefaultNativeMap.styles.js +122 -0
- package/src/react/components/table/DefaultTable.js +1897 -0
- package/src/react/components/table/DefaultTable.styles.js +610 -0
- package/src/react/index.js +10 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +264 -0
- package/src/store/default/actions.js +444 -0
- package/src/store/default/getters.js +28 -0
- package/src/store/default/mutation_types.js +26 -0
- package/src/store/default/mutations.js +138 -0
- package/src/tests/react/components/DateShortcutFilter.test.js +96 -0
- package/src/tests/react/components/errors/DefaultErrors.test.js +162 -0
- package/src/tests/react/components/files/DefaultFile.test.js +80 -0
- package/src/tests/react/components/map/DefaultMap.shared.test.js +162 -0
- package/src/tests/react/filters/dateFilterSelection.test.js +46 -0
- package/src/tests/react/inputs/defaultInputUtils.test.js +45 -0
- package/src/tests/react/store/defaultActions.test.js +112 -0
- package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +223 -0
- package/src/utils/filters.js +56 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
import {resolveAddressDisplayParts} from '@controleonline/ui-common/src/react/utils/entityDisplay';
|
|
2
|
+
|
|
3
|
+
const normalizeText = value => String(value ?? '').trim();
|
|
4
|
+
|
|
5
|
+
const isMapConfigObject = value =>
|
|
6
|
+
Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
|
|
8
|
+
const normalizeCoordinate = value => {
|
|
9
|
+
const numeric = Number(String(value ?? '').replace(',', '.'));
|
|
10
|
+
|
|
11
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const serializeForHtml = value =>
|
|
15
|
+
JSON.stringify(value ?? null).replace(/</g, '\\u003c');
|
|
16
|
+
|
|
17
|
+
export const extractMapCoordinates = address => {
|
|
18
|
+
if (!address || typeof address !== 'object') {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const latitude = normalizeCoordinate(address.latitude);
|
|
23
|
+
const longitude = normalizeCoordinate(address.longitude);
|
|
24
|
+
|
|
25
|
+
if (latitude === null || longitude === null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {latitude, longitude};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const normalizeMarkerId = (item, latitude, longitude) =>
|
|
33
|
+
normalizeText(item?.id) ||
|
|
34
|
+
`${latitude}:${longitude}:${normalizeText(item?.title || item?.companyName)}`;
|
|
35
|
+
|
|
36
|
+
const resolveMarkerGeocodeQuery = item => {
|
|
37
|
+
const addressParts = resolveAddressDisplayParts(item);
|
|
38
|
+
const candidates = [
|
|
39
|
+
item?.geocodeQuery,
|
|
40
|
+
item?.navigationQuery,
|
|
41
|
+
item?.searchFor,
|
|
42
|
+
item?.searchQuery,
|
|
43
|
+
item?.addressQuery,
|
|
44
|
+
item?.formattedAddress,
|
|
45
|
+
item?.formatted,
|
|
46
|
+
typeof item?.address === 'string' ? item.address : null,
|
|
47
|
+
[addressParts.streetLine, addressParts.district, addressParts.cityStateLine]
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.join(', '),
|
|
50
|
+
addressParts.primary,
|
|
51
|
+
addressParts.nickname,
|
|
52
|
+
item?.title,
|
|
53
|
+
item?.companyName,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
for (const candidate of candidates) {
|
|
57
|
+
const normalized = normalizeText(candidate);
|
|
58
|
+
if (normalized) {
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return '';
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const resolveDefaultMapApiKey = source => {
|
|
67
|
+
if (!isMapConfigObject(source)) {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return normalizeText(
|
|
72
|
+
source.apiKey ||
|
|
73
|
+
source.googleMapsApiKey ||
|
|
74
|
+
source.webGoogleMapsApiKey ||
|
|
75
|
+
source.androidGoogleMapsApiKey ||
|
|
76
|
+
source.maps?.apiKey ||
|
|
77
|
+
source.map?.apiKey ||
|
|
78
|
+
source.mapApiKey,
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const normalizeMapMarkerPayload = item => {
|
|
83
|
+
if (!item || typeof item !== 'object') {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const coordinates = extractMapCoordinates(item);
|
|
88
|
+
|
|
89
|
+
const addressParts = resolveAddressDisplayParts(item);
|
|
90
|
+
const title = normalizeText(
|
|
91
|
+
item.title ||
|
|
92
|
+
item.companyName ||
|
|
93
|
+
item.company ||
|
|
94
|
+
item.label ||
|
|
95
|
+
item.name ||
|
|
96
|
+
addressParts.primary ||
|
|
97
|
+
addressParts.nickname ||
|
|
98
|
+
item.formattedAddress ||
|
|
99
|
+
item.formatted ||
|
|
100
|
+
item.address ||
|
|
101
|
+
item.searchFor,
|
|
102
|
+
);
|
|
103
|
+
const addressLine = normalizeText(
|
|
104
|
+
item.addressLine ||
|
|
105
|
+
item.address ||
|
|
106
|
+
addressParts.streetLine ||
|
|
107
|
+
item.formattedAddress ||
|
|
108
|
+
item.formatted ||
|
|
109
|
+
item.searchFor ||
|
|
110
|
+
title,
|
|
111
|
+
);
|
|
112
|
+
const addressExtra = normalizeText(
|
|
113
|
+
item.addressExtra ||
|
|
114
|
+
[addressParts.district, addressParts.cityStateLine, addressParts.postalCode]
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.join(' • '),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...item,
|
|
121
|
+
id: normalizeMarkerId(
|
|
122
|
+
item,
|
|
123
|
+
coordinates?.latitude ?? 'na',
|
|
124
|
+
coordinates?.longitude ?? 'na',
|
|
125
|
+
),
|
|
126
|
+
...(coordinates
|
|
127
|
+
? {
|
|
128
|
+
latitude: coordinates.latitude,
|
|
129
|
+
longitude: coordinates.longitude,
|
|
130
|
+
}
|
|
131
|
+
: {}),
|
|
132
|
+
title,
|
|
133
|
+
companyName: normalizeText(item.companyName || item.company || item.groupName || title),
|
|
134
|
+
addressLine,
|
|
135
|
+
addressExtra,
|
|
136
|
+
geocodeQuery: resolveMarkerGeocodeQuery(item),
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const dedupeByKey = (items, keyResolver) => {
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
const result = [];
|
|
143
|
+
|
|
144
|
+
items.forEach(item => {
|
|
145
|
+
const key = keyResolver(item);
|
|
146
|
+
|
|
147
|
+
if (!key || seen.has(key)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
seen.add(key);
|
|
152
|
+
result.push(item);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return result;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const resolveMarkersFromSource = source => {
|
|
159
|
+
if (!isMapConfigObject(source)) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const markers = [];
|
|
164
|
+
|
|
165
|
+
if (Array.isArray(source.markers)) {
|
|
166
|
+
markers.push(...source.markers);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (Array.isArray(source.markerPayloads)) {
|
|
170
|
+
markers.push(...source.markerPayloads);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (source.origin) {
|
|
174
|
+
markers.push({...source.origin, __mapRole: 'origin'});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (source.destination) {
|
|
178
|
+
markers.push({...source.destination, __mapRole: 'destination'});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return markers;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const resolveExplicitPaths = ({config, addresses, paths}) => {
|
|
185
|
+
if (Array.isArray(paths) && paths.length > 0) {
|
|
186
|
+
return paths;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const sources = [config, addresses].filter(isMapConfigObject);
|
|
190
|
+
|
|
191
|
+
for (const source of sources) {
|
|
192
|
+
if (Array.isArray(source.paths) && source.paths.length > 0) {
|
|
193
|
+
return source.paths;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!sources.length) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const originDestinationSource = sources.find(
|
|
202
|
+
source => source.origin && source.destination,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
if (originDestinationSource) {
|
|
206
|
+
return [
|
|
207
|
+
{
|
|
208
|
+
from: originDestinationSource.origin,
|
|
209
|
+
to: originDestinationSource.destination,
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const originMarkersSource = sources.find(
|
|
215
|
+
source => source.origin && Array.isArray(source.markers) && source.markers.length > 0,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (originMarkersSource) {
|
|
219
|
+
return originMarkersSource.markers.map(target => ({
|
|
220
|
+
from: originMarkersSource.origin,
|
|
221
|
+
to: target,
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return [];
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export const resolveDefaultMapPayload = ({
|
|
229
|
+
config = null,
|
|
230
|
+
addresses = null,
|
|
231
|
+
markerPayloads = [],
|
|
232
|
+
paths = [],
|
|
233
|
+
userCoordinates = null,
|
|
234
|
+
apiKey = '',
|
|
235
|
+
} = {}) => {
|
|
236
|
+
const normalizedConfig = isMapConfigObject(config) ? config : null;
|
|
237
|
+
const nestedConfigAddresses = isMapConfigObject(normalizedConfig?.addresses)
|
|
238
|
+
? normalizedConfig.addresses
|
|
239
|
+
: null;
|
|
240
|
+
const normalizedAddresses =
|
|
241
|
+
(addresses && typeof addresses === 'object' && !Array.isArray(addresses)
|
|
242
|
+
? addresses
|
|
243
|
+
: nestedConfigAddresses) || null;
|
|
244
|
+
|
|
245
|
+
const resolvedApiKey =
|
|
246
|
+
resolveDefaultMapApiKey(normalizedConfig) ||
|
|
247
|
+
resolveDefaultMapApiKey(normalizedAddresses) ||
|
|
248
|
+
normalizeText(apiKey);
|
|
249
|
+
|
|
250
|
+
const resolvedUserCoordinates =
|
|
251
|
+
extractMapCoordinates(userCoordinates) ||
|
|
252
|
+
extractMapCoordinates(normalizedConfig?.user) ||
|
|
253
|
+
extractMapCoordinates(normalizedConfig?.userCoordinates) ||
|
|
254
|
+
extractMapCoordinates(normalizedAddresses?.user) ||
|
|
255
|
+
extractMapCoordinates(normalizedAddresses?.userCoordinates) ||
|
|
256
|
+
null;
|
|
257
|
+
|
|
258
|
+
const combinedMarkers = [
|
|
259
|
+
...markerPayloads,
|
|
260
|
+
...resolveMarkersFromSource(normalizedConfig),
|
|
261
|
+
...resolveMarkersFromSource(normalizedAddresses),
|
|
262
|
+
]
|
|
263
|
+
.map(normalizeMapMarkerPayload)
|
|
264
|
+
.filter(Boolean);
|
|
265
|
+
|
|
266
|
+
const resolvedMarkers = dedupeByKey(combinedMarkers, item => normalizeText(item?.id));
|
|
267
|
+
|
|
268
|
+
const resolvedPaths = dedupeByKey(
|
|
269
|
+
resolveExplicitPaths({
|
|
270
|
+
config: normalizedConfig,
|
|
271
|
+
addresses: normalizedAddresses,
|
|
272
|
+
paths,
|
|
273
|
+
})
|
|
274
|
+
.map(path => {
|
|
275
|
+
const from = normalizeMapMarkerPayload(path?.from || path?.origin || path?.start);
|
|
276
|
+
const to = normalizeMapMarkerPayload(path?.to || path?.destination || path?.end);
|
|
277
|
+
|
|
278
|
+
if (!from || !to) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
id: normalizeText(path?.id) || `${from.id || `${from.latitude}:${from.longitude}`}-${to.id || `${to.latitude}:${to.longitude}`}`,
|
|
284
|
+
from,
|
|
285
|
+
to,
|
|
286
|
+
color: normalizeText(path?.color),
|
|
287
|
+
};
|
|
288
|
+
})
|
|
289
|
+
.filter(Boolean),
|
|
290
|
+
path => normalizeText(path?.id),
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
apiKey: resolvedApiKey,
|
|
295
|
+
markerPayloads: resolvedMarkers,
|
|
296
|
+
paths: resolvedPaths,
|
|
297
|
+
userCoordinates: resolvedUserCoordinates,
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
export const buildGoogleMapsNavigationUrl = ({latitude, longitude}) => {
|
|
302
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
303
|
+
return '';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
|
|
307
|
+
`${latitude},${longitude}`,
|
|
308
|
+
)}`;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export const buildWazeNavigationUrl = ({latitude, longitude}) => {
|
|
312
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
313
|
+
return '';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return `https://waze.com/ul?ll=${encodeURIComponent(
|
|
317
|
+
`${latitude},${longitude}`,
|
|
318
|
+
)}&navigate=yes`;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const collectCoordinates = ({markerPayloads = [], paths = [], userCoordinates = null}) => {
|
|
322
|
+
const coordinates = [];
|
|
323
|
+
|
|
324
|
+
markerPayloads.forEach(item => {
|
|
325
|
+
const point = extractMapCoordinates(item);
|
|
326
|
+
if (point) {
|
|
327
|
+
coordinates.push(point);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
if (userCoordinates) {
|
|
332
|
+
coordinates.push(userCoordinates);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
paths.forEach(path => {
|
|
336
|
+
const from = extractMapCoordinates(path?.from);
|
|
337
|
+
const to = extractMapCoordinates(path?.to);
|
|
338
|
+
|
|
339
|
+
if (from) {
|
|
340
|
+
coordinates.push(from);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (to) {
|
|
344
|
+
coordinates.push(to);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return coordinates.filter(
|
|
349
|
+
item =>
|
|
350
|
+
Number.isFinite(item?.latitude) &&
|
|
351
|
+
Number.isFinite(item?.longitude),
|
|
352
|
+
);
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
export const buildOpenStreetMapEmbedUrl = ({
|
|
356
|
+
markerPayloads = [],
|
|
357
|
+
paths = [],
|
|
358
|
+
userCoordinates = null,
|
|
359
|
+
} = {}) => {
|
|
360
|
+
const coordinates = collectCoordinates({
|
|
361
|
+
markerPayloads,
|
|
362
|
+
paths,
|
|
363
|
+
userCoordinates,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (coordinates.length === 0) {
|
|
367
|
+
return 'https://www.openstreetmap.org/export/embed.html?layer=mapnik';
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const latitudes = coordinates.map(item => item.latitude);
|
|
371
|
+
const longitudes = coordinates.map(item => item.longitude);
|
|
372
|
+
const south = Math.min(...latitudes);
|
|
373
|
+
const north = Math.max(...latitudes);
|
|
374
|
+
const west = Math.min(...longitudes);
|
|
375
|
+
const east = Math.max(...longitudes);
|
|
376
|
+
const padding = 0.01;
|
|
377
|
+
const center = coordinates[0];
|
|
378
|
+
|
|
379
|
+
return `https://www.openstreetmap.org/export/embed.html?bbox=${encodeURIComponent(
|
|
380
|
+
`${west - padding},${south - padding},${east + padding},${north + padding}`,
|
|
381
|
+
)}&layer=mapnik&marker=${encodeURIComponent(
|
|
382
|
+
`${normalizeCoordinate(center.latitude) || 0},${normalizeCoordinate(center.longitude) || 0}`,
|
|
383
|
+
)}`;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export const buildOpenStreetMapHtml = ({
|
|
387
|
+
markerPayloads = [],
|
|
388
|
+
paths = [],
|
|
389
|
+
userCoordinates = null,
|
|
390
|
+
routeColor = '#0EA5E9',
|
|
391
|
+
} = {}) => {
|
|
392
|
+
const markers = serializeForHtml(markerPayloads);
|
|
393
|
+
const routes = serializeForHtml(paths);
|
|
394
|
+
const user = serializeForHtml(userCoordinates);
|
|
395
|
+
const routeStroke = serializeForHtml(routeColor || '#0EA5E9');
|
|
396
|
+
|
|
397
|
+
return `<!DOCTYPE html>
|
|
398
|
+
<html lang="pt-BR">
|
|
399
|
+
<head>
|
|
400
|
+
<meta charset="utf-8" />
|
|
401
|
+
<meta
|
|
402
|
+
name="viewport"
|
|
403
|
+
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
|
404
|
+
/>
|
|
405
|
+
<link
|
|
406
|
+
rel="stylesheet"
|
|
407
|
+
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
|
408
|
+
/>
|
|
409
|
+
<style>
|
|
410
|
+
html, body, #map {
|
|
411
|
+
margin: 0;
|
|
412
|
+
width: 100%;
|
|
413
|
+
height: 100%;
|
|
414
|
+
overflow: hidden;
|
|
415
|
+
background: #f8fafc;
|
|
416
|
+
font-family: Arial, sans-serif;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.leaflet-container {
|
|
420
|
+
background: #e2e8f0;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.marker-popup {
|
|
424
|
+
min-width: 180px;
|
|
425
|
+
color: #0f172a;
|
|
426
|
+
font-size: 12px;
|
|
427
|
+
line-height: 1.4;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.marker-popup-title {
|
|
431
|
+
font-size: 14px;
|
|
432
|
+
font-weight: 700;
|
|
433
|
+
margin-bottom: 4px;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.marker-popup-line {
|
|
437
|
+
color: #334155;
|
|
438
|
+
margin-top: 2px;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.map-error {
|
|
442
|
+
position: absolute;
|
|
443
|
+
inset: 0;
|
|
444
|
+
display: none;
|
|
445
|
+
align-items: center;
|
|
446
|
+
justify-content: center;
|
|
447
|
+
padding: 24px;
|
|
448
|
+
background: rgba(15, 23, 42, 0.8);
|
|
449
|
+
color: #ffffff;
|
|
450
|
+
text-align: center;
|
|
451
|
+
font-size: 14px;
|
|
452
|
+
font-weight: 700;
|
|
453
|
+
z-index: 10;
|
|
454
|
+
}
|
|
455
|
+
</style>
|
|
456
|
+
</head>
|
|
457
|
+
<body>
|
|
458
|
+
<div id="map"></div>
|
|
459
|
+
<div id="error" class="map-error"></div>
|
|
460
|
+
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
461
|
+
<script>
|
|
462
|
+
window.__SHOP_MAP_MARKERS__ = ${markers};
|
|
463
|
+
window.__SHOP_MAP_PATHS__ = ${routes};
|
|
464
|
+
window.__SHOP_MAP_USER__ = ${user};
|
|
465
|
+
window.__SHOP_MAP_ROUTE_COLOR__ = ${routeStroke};
|
|
466
|
+
|
|
467
|
+
function escapeHtml(value) {
|
|
468
|
+
return String(value || '')
|
|
469
|
+
.replace(/&/g, '&')
|
|
470
|
+
.replace(/</g, '<')
|
|
471
|
+
.replace(/>/g, '>')
|
|
472
|
+
.replace(/"/g, '"')
|
|
473
|
+
.replace(/'/g, ''');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function showError(message) {
|
|
477
|
+
var errorElement = document.getElementById('error');
|
|
478
|
+
errorElement.textContent = message;
|
|
479
|
+
errorElement.style.display = 'flex';
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function toPoint(item) {
|
|
483
|
+
if (!item) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
var latitude = Number(item.latitude);
|
|
488
|
+
var longitude = Number(item.longitude);
|
|
489
|
+
|
|
490
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
lat: latitude,
|
|
496
|
+
lng: longitude,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function buildPopupContent(item) {
|
|
501
|
+
var title = escapeHtml(item && item.title ? item.title : item && item.companyName ? item.companyName : '');
|
|
502
|
+
var addressLine = escapeHtml(item && item.addressLine ? item.addressLine : '');
|
|
503
|
+
var addressExtra = escapeHtml(item && item.addressExtra ? item.addressExtra : '');
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
'<div class="marker-popup">' +
|
|
507
|
+
'<div class="marker-popup-title">' + title + '</div>' +
|
|
508
|
+
(addressLine ? '<div class="marker-popup-line">' + addressLine + '</div>' : '') +
|
|
509
|
+
(addressExtra ? '<div class="marker-popup-line">' + addressExtra + '</div>' : '') +
|
|
510
|
+
'</div>'
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function resolveCircleColor(item, fallback) {
|
|
515
|
+
if (item && item.__mapRole === 'origin') {
|
|
516
|
+
return '#16a34a';
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (item && item.__mapRole === 'destination') {
|
|
520
|
+
return '#c10015';
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (item && item.__mapRole === 'user') {
|
|
524
|
+
return '#0ea5e9';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return fallback || '#64748b';
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function addPointToBounds(bounds, point) {
|
|
531
|
+
if (!point) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
bounds.extend([point.lat, point.lng]);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function createCircleMarker(map, point, item, color) {
|
|
539
|
+
return L.circleMarker([point.lat, point.lng], {
|
|
540
|
+
radius: item && item.__mapRole === 'user' ? 9 : 8,
|
|
541
|
+
color: color,
|
|
542
|
+
weight: 2,
|
|
543
|
+
fillColor: color,
|
|
544
|
+
fillOpacity: 0.92,
|
|
545
|
+
}).addTo(map);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function resolveMarkerIcon(item) {
|
|
549
|
+
var iconUrl = item && item.markerIconUrl ? String(item.markerIconUrl).trim() : '';
|
|
550
|
+
|
|
551
|
+
if (!iconUrl) {
|
|
552
|
+
return Promise.resolve(null);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return new Promise(function (resolve) {
|
|
556
|
+
var image = new Image();
|
|
557
|
+
|
|
558
|
+
image.onload = function () {
|
|
559
|
+
resolve(
|
|
560
|
+
L.icon({
|
|
561
|
+
iconUrl: iconUrl,
|
|
562
|
+
iconSize: [42, 42],
|
|
563
|
+
iconAnchor: [21, 42],
|
|
564
|
+
popupAnchor: [0, -36],
|
|
565
|
+
}),
|
|
566
|
+
);
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
image.onerror = function () {
|
|
570
|
+
resolve(null);
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
image.src = iconUrl;
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function createMapMarker(map, point, item, color) {
|
|
578
|
+
if (
|
|
579
|
+
item &&
|
|
580
|
+
(item.__mapRole === 'user' ||
|
|
581
|
+
item.__mapRole === 'origin' ||
|
|
582
|
+
item.__mapRole === 'destination')
|
|
583
|
+
) {
|
|
584
|
+
return Promise.resolve(createCircleMarker(map, point, item, color));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return resolveMarkerIcon(item).then(function (icon) {
|
|
588
|
+
if (icon) {
|
|
589
|
+
return L.marker([point.lat, point.lng], {
|
|
590
|
+
icon: icon,
|
|
591
|
+
}).addTo(map);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return L.marker([point.lat, point.lng]).addTo(map);
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
if (!window.L) {
|
|
600
|
+
showError('Nao foi possivel carregar o mapa.');
|
|
601
|
+
throw new Error('map-library-missing');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (L.Icon && L.Icon.Default && typeof L.Icon.Default.mergeOptions === 'function') {
|
|
605
|
+
L.Icon.Default.mergeOptions({
|
|
606
|
+
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
|
|
607
|
+
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
|
|
608
|
+
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
var map = L.map('map', {
|
|
613
|
+
zoomControl: true,
|
|
614
|
+
attributionControl: true,
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
618
|
+
maxZoom: 19,
|
|
619
|
+
attribution: '© OpenStreetMap contributors',
|
|
620
|
+
}).addTo(map);
|
|
621
|
+
|
|
622
|
+
var bounds = L.latLngBounds([]);
|
|
623
|
+
var markers = Array.isArray(window.__SHOP_MAP_MARKERS__) ? window.__SHOP_MAP_MARKERS__ : [];
|
|
624
|
+
var routes = Array.isArray(window.__SHOP_MAP_PATHS__) ? window.__SHOP_MAP_PATHS__ : [];
|
|
625
|
+
var userCoordinates = window.__SHOP_MAP_USER__;
|
|
626
|
+
|
|
627
|
+
if (
|
|
628
|
+
userCoordinates &&
|
|
629
|
+
Number.isFinite(userCoordinates.latitude) &&
|
|
630
|
+
Number.isFinite(userCoordinates.longitude)
|
|
631
|
+
) {
|
|
632
|
+
var userPoint = {
|
|
633
|
+
lat: Number(userCoordinates.latitude),
|
|
634
|
+
lng: Number(userCoordinates.longitude),
|
|
635
|
+
};
|
|
636
|
+
var userMarker = createCircleMarker(map, userPoint, {__mapRole: 'user'}, '#0ea5e9');
|
|
637
|
+
userMarker.bindPopup('<div class="marker-popup"><div class="marker-popup-title">Sua localização</div></div>');
|
|
638
|
+
addPointToBounds(bounds, userPoint);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
Promise.all(
|
|
642
|
+
markers.map(function (item) {
|
|
643
|
+
var point = toPoint(item);
|
|
644
|
+
|
|
645
|
+
if (!point) {
|
|
646
|
+
return Promise.resolve(null);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
var color = resolveCircleColor(item, '#64748b');
|
|
650
|
+
|
|
651
|
+
return createMapMarker(map, point, item, color).then(function (marker) {
|
|
652
|
+
marker.bindPopup(buildPopupContent(item));
|
|
653
|
+
addPointToBounds(bounds, point);
|
|
654
|
+
return marker;
|
|
655
|
+
});
|
|
656
|
+
}),
|
|
657
|
+
).then(function () {
|
|
658
|
+
function resolveRouteCoordinates(path) {
|
|
659
|
+
var from = toPoint(path && (path.from || path.origin || path.start));
|
|
660
|
+
var to = toPoint(path && (path.to || path.destination || path.end));
|
|
661
|
+
|
|
662
|
+
if (!from || !to) {
|
|
663
|
+
return Promise.resolve(null);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
var routeUrl =
|
|
667
|
+
'https://router.project-osrm.org/route/v1/driving/' +
|
|
668
|
+
from.lng +
|
|
669
|
+
',' +
|
|
670
|
+
from.lat +
|
|
671
|
+
';' +
|
|
672
|
+
to.lng +
|
|
673
|
+
',' +
|
|
674
|
+
to.lat +
|
|
675
|
+
'?overview=full&geometries=geojson&steps=false';
|
|
676
|
+
|
|
677
|
+
return fetch(routeUrl)
|
|
678
|
+
.then(function (response) {
|
|
679
|
+
if (!response || !response.ok) {
|
|
680
|
+
throw new Error('route-unavailable');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return response.json();
|
|
684
|
+
})
|
|
685
|
+
.then(function (payload) {
|
|
686
|
+
var geometry = payload && payload.routes && payload.routes[0] && payload.routes[0].geometry;
|
|
687
|
+
var coordinates = geometry && Array.isArray(geometry.coordinates)
|
|
688
|
+
? geometry.coordinates
|
|
689
|
+
.map(function (pair) {
|
|
690
|
+
return {
|
|
691
|
+
lat: Number(pair && pair[1]),
|
|
692
|
+
lng: Number(pair && pair[0]),
|
|
693
|
+
};
|
|
694
|
+
})
|
|
695
|
+
.filter(function (point) {
|
|
696
|
+
return Number.isFinite(point.lat) && Number.isFinite(point.lng);
|
|
697
|
+
})
|
|
698
|
+
: [];
|
|
699
|
+
|
|
700
|
+
return {
|
|
701
|
+
path: path,
|
|
702
|
+
coordinates: coordinates.length > 1 ? coordinates : [from, to],
|
|
703
|
+
};
|
|
704
|
+
})
|
|
705
|
+
.catch(function () {
|
|
706
|
+
return {
|
|
707
|
+
path: path,
|
|
708
|
+
coordinates: [from, to],
|
|
709
|
+
};
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
Promise.all(routes.map(resolveRouteCoordinates)).then(function (resolvedRoutes) {
|
|
714
|
+
(resolvedRoutes || [])
|
|
715
|
+
.filter(Boolean)
|
|
716
|
+
.forEach(function (entry) {
|
|
717
|
+
var coordinates = Array.isArray(entry.coordinates) ? entry.coordinates : [];
|
|
718
|
+
|
|
719
|
+
if (coordinates.length < 2) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
L.polyline(
|
|
724
|
+
coordinates.map(function (point) {
|
|
725
|
+
return [point.lat, point.lng];
|
|
726
|
+
}),
|
|
727
|
+
{
|
|
728
|
+
color:
|
|
729
|
+
entry.path && entry.path.color
|
|
730
|
+
? entry.path.color
|
|
731
|
+
: window.__SHOP_MAP_ROUTE_COLOR__ || '#0ea5e9',
|
|
732
|
+
weight: 4,
|
|
733
|
+
opacity: 0.78,
|
|
734
|
+
},
|
|
735
|
+
).addTo(map);
|
|
736
|
+
|
|
737
|
+
coordinates.forEach(function (point) {
|
|
738
|
+
addPointToBounds(bounds, point);
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
if (!bounds.isValid()) {
|
|
743
|
+
map.setView([-14.235004, -51.92528], 4);
|
|
744
|
+
} else {
|
|
745
|
+
map.fitBounds(bounds.pad(0.2), {
|
|
746
|
+
padding: [32, 32],
|
|
747
|
+
animate: false,
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
setTimeout(function () {
|
|
752
|
+
map.invalidateSize();
|
|
753
|
+
}, 0);
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
} catch (error) {
|
|
757
|
+
showError('Nao foi possivel carregar o mapa.');
|
|
758
|
+
}
|
|
759
|
+
</script>
|
|
760
|
+
</body>
|
|
761
|
+
</html>`;
|
|
762
|
+
};
|