@controleonline/ui-default 1.0.269 → 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.
Files changed (91) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/manager-worker.yml +19 -0
  3. package/AGENTS.md +46 -50
  4. package/docs/technical/Contrato-de-colunas.md +67 -0
  5. package/docs/technical/Default-Components.md +76 -0
  6. package/docs/technical/DefaultForm.md +80 -0
  7. package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
  8. package/docs/technical/DefaultTable.md +203 -0
  9. package/docs/wiki.md +1 -0
  10. package/package.json +22 -21
  11. package/scripts/validate-wiki-pointer.cjs +13 -0
  12. package/src/react/components/address/DefaultAddress.js +435 -0
  13. package/src/react/components/address/DefaultAddress.styles.js +133 -0
  14. package/src/react/components/address/DefaultAddressMapPane.js +55 -0
  15. package/src/react/components/address/LatLonReadonlyFields.js +51 -0
  16. package/src/react/components/address/defaultAddressHelpers.js +163 -0
  17. package/src/react/components/address/defaultAddressStyles.js +136 -0
  18. package/src/react/components/address/defaultAddressUtils.js +129 -0
  19. package/src/react/components/extra-data/DefaultExtraData.js +152 -0
  20. package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
  21. package/src/react/components/files/DefaultFile.js +231 -46
  22. package/src/react/components/files/DefaultFileColumn.js +123 -0
  23. package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
  24. package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
  25. package/src/react/components/filters/CompactFilterSelector.js +51 -35
  26. package/src/react/components/filters/DefaultColumnFilter.js +58 -41
  27. package/src/react/components/filters/DefaultExternalFilters.js +331 -265
  28. package/src/react/components/form/DefaultForm.js +99 -79
  29. package/src/react/components/help/DefaultTooltip.js +59 -33
  30. package/src/react/components/help/DefaultTooltip.styles.js +23 -3
  31. package/src/react/components/inputs/DefaultColorInput.js +153 -0
  32. package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
  33. package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
  34. package/src/react/components/inputs/DefaultInput.js +69 -5
  35. package/src/react/components/inputs/DefaultInput.styles.js +26 -0
  36. package/src/react/components/inputs/DefaultSelect.js +134 -123
  37. package/src/react/components/inputs/defaultInputUtils.js +69 -43
  38. package/src/react/components/map/DefaultGoogleMap.styles.js +9 -2
  39. package/src/react/components/map/DefaultGoogleMap.web.js +49 -28
  40. package/src/react/components/map/DefaultMap.native.js +73 -73
  41. package/src/react/components/map/DefaultMap.shared.js +911 -762
  42. package/src/react/components/map/DefaultMap.web.js +124 -68
  43. package/src/react/components/table/DefaultColumnMenu.js +109 -108
  44. package/src/react/components/table/DefaultDebug.js +45 -38
  45. package/src/react/components/table/DefaultSearchModal.js +9 -1
  46. package/src/react/components/table/DefaultTable.grouping.js +98 -0
  47. package/src/react/components/table/DefaultTable.js +215 -109
  48. package/src/react/components/table/DefaultTable.styles.js +367 -314
  49. package/src/react/components/table/DefaultTableBody.js +13 -8
  50. package/src/react/components/table/DefaultTableCards.js +20 -1
  51. package/src/react/components/table/DefaultTableControls.js +180 -205
  52. package/src/react/components/table/DefaultTableImportModal.js +108 -0
  53. package/src/react/components/table/DefaultTableInput.js +1 -0
  54. package/src/react/components/table/DefaultTableRowActions.js +1 -1
  55. package/src/react/components/table/DefaultTableRows.js +224 -363
  56. package/src/react/components/table/DefaultTableSearch.js +28 -5
  57. package/src/react/components/table/DefaultTableToolbar.js +92 -3
  58. package/src/react/components/table/useDefaultTablePagination.js +56 -7
  59. package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
  60. package/src/react/components/upload/DefaultUpload.js +436 -0
  61. package/src/react/components/upload/DefaultUpload.styles.js +153 -0
  62. package/src/react/components/upload/DefaultUploadAttachmentsList.js +138 -0
  63. package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
  64. package/src/react/components/upload/defaultUploadHelpers.js +108 -0
  65. package/src/react/components/upload/defaultUploadLibrary.js +248 -0
  66. package/src/react/components/upload/fileUpload.js +93 -0
  67. package/src/react/hooks/usePostalCodeLookup.js +145 -0
  68. package/src/react/index.js +6 -0
  69. package/src/react/services/addressFormUtils.js +196 -0
  70. package/src/react/services/addressGeo.js +227 -0
  71. package/src/react/utils/tableVisibleColumnsPreferences.js +29 -12
  72. package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
  73. package/src/tests/react/components/files/DefaultFile.test.js +6 -6
  74. package/src/tests/react/components/form/DefaultForm.test.js +123 -0
  75. package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
  76. package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
  77. package/src/tests/react/components/table/DefaultTable.test.js +54 -2
  78. package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
  79. package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
  80. package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
  81. package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
  82. package/src/tests/react/components/upload/defaultUploadLibrary.test.js +82 -0
  83. package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
  84. package/src/tests/react/hooks/usePostalCodeLookup.test.js +40 -0
  85. package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
  86. package/src/tests/react/services/addressFormUtils.test.js +176 -0
  87. package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
  88. package/src/tests/react/services/addressGeoLookup.test.js +105 -0
  89. package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
  90. package/src/tests/react/services/smoke-637/index.html +132 -0
  91. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +84 -48
@@ -1,762 +1,911 @@
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, '&amp;')
470
- .replace(/</g, '&lt;')
471
- .replace(/>/g, '&gt;')
472
- .replace(/"/g, '&quot;')
473
- .replace(/'/g, '&#39;');
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: '&copy; 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
- };
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, '&amp;')
470
+ .replace(/</g, '&lt;')
471
+ .replace(/>/g, '&gt;')
472
+ .replace(/"/g, '&quot;')
473
+ .replace(/'/g, '&#39;');
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 normalizeCoordinate(value) {
483
+ return Number(String(value == null ? '' : value).replace(',', '.'));
484
+ }
485
+
486
+ function hasGeocodeQuery(item) {
487
+ return Boolean(item && String(item.geocodeQuery || '').trim());
488
+ }
489
+
490
+ function isValidPoint(latitude, longitude) {
491
+ return (
492
+ Number.isFinite(latitude) &&
493
+ Number.isFinite(longitude) &&
494
+ latitude >= -85 &&
495
+ latitude <= 85 &&
496
+ longitude >= -180 &&
497
+ longitude <= 180
498
+ );
499
+ }
500
+
501
+ function toPoint(item) {
502
+ if (!item) {
503
+ return null;
504
+ }
505
+
506
+ var latitude = normalizeCoordinate(item.latitude);
507
+ var longitude = normalizeCoordinate(item.longitude);
508
+
509
+ if (!isValidPoint(latitude, longitude)) {
510
+ return null;
511
+ }
512
+
513
+ if (
514
+ hasGeocodeQuery(item) &&
515
+ Math.abs(latitude) < 0.000001 &&
516
+ Math.abs(longitude) < 0.000001
517
+ ) {
518
+ return null;
519
+ }
520
+
521
+ return {
522
+ lat: latitude,
523
+ lng: longitude,
524
+ };
525
+ }
526
+
527
+ function geocodePoint(item) {
528
+ var query = item && item.geocodeQuery ? String(item.geocodeQuery).trim() : '';
529
+
530
+ if (!query) {
531
+ return Promise.resolve(null);
532
+ }
533
+
534
+ return fetch(
535
+ 'https://nominatim.openstreetmap.org/search?format=json&limit=1&q=' +
536
+ encodeURIComponent(query),
537
+ )
538
+ .then(function (response) {
539
+ if (!response || !response.ok) {
540
+ return null;
541
+ }
542
+
543
+ return response.json();
544
+ })
545
+ .then(function (payload) {
546
+ var first = Array.isArray(payload) ? payload[0] : null;
547
+ var latitude = normalizeCoordinate(first && first.lat);
548
+ var longitude = normalizeCoordinate(first && first.lon);
549
+
550
+ if (!isValidPoint(latitude, longitude)) {
551
+ return null;
552
+ }
553
+
554
+ return {
555
+ lat: latitude,
556
+ lng: longitude,
557
+ };
558
+ })
559
+ .catch(function () {
560
+ return null;
561
+ });
562
+ }
563
+
564
+ function resolvePoint(item) {
565
+ var point = toPoint(item);
566
+
567
+ if (point) {
568
+ return Promise.resolve(point);
569
+ }
570
+
571
+ return geocodePoint(item);
572
+ }
573
+
574
+ function buildPopupContent(item) {
575
+ var title = escapeHtml(item && item.title ? item.title : item && item.companyName ? item.companyName : '');
576
+ var addressLine = escapeHtml(item && item.addressLine ? item.addressLine : '');
577
+ var addressExtra = escapeHtml(item && item.addressExtra ? item.addressExtra : '');
578
+
579
+ return (
580
+ '<div class="marker-popup">' +
581
+ '<div class="marker-popup-title">' + title + '</div>' +
582
+ (addressLine ? '<div class="marker-popup-line">' + addressLine + '</div>' : '') +
583
+ (addressExtra ? '<div class="marker-popup-line">' + addressExtra + '</div>' : '') +
584
+ '</div>'
585
+ );
586
+ }
587
+
588
+ function resolveCircleColor(item, fallback) {
589
+ if (item && item.__mapRole === 'origin') {
590
+ return '#16a34a';
591
+ }
592
+
593
+ if (item && item.__mapRole === 'destination') {
594
+ return '#c10015';
595
+ }
596
+
597
+ if (item && item.__mapRole === 'user') {
598
+ return '#0ea5e9';
599
+ }
600
+
601
+ return fallback || '#64748b';
602
+ }
603
+
604
+ function addPointToBounds(bounds, point) {
605
+ if (!point) {
606
+ return;
607
+ }
608
+
609
+ bounds.extend([point.lat, point.lng]);
610
+ }
611
+
612
+ function createCircleMarker(map, point, item, color) {
613
+ return L.circleMarker([point.lat, point.lng], {
614
+ radius: item && item.__mapRole === 'user' ? 9 : 8,
615
+ color: color,
616
+ weight: 2,
617
+ fillColor: color,
618
+ fillOpacity: 0.92,
619
+ }).addTo(map);
620
+ }
621
+
622
+ function resolveMarkerIcon(item) {
623
+ var iconUrl = item && item.markerIconUrl ? String(item.markerIconUrl).trim() : '';
624
+
625
+ if (!iconUrl) {
626
+ return Promise.resolve(null);
627
+ }
628
+
629
+ return new Promise(function (resolve) {
630
+ var image = new Image();
631
+
632
+ image.onload = function () {
633
+ resolve(
634
+ L.icon({
635
+ iconUrl: iconUrl,
636
+ iconSize: [42, 42],
637
+ iconAnchor: [21, 42],
638
+ popupAnchor: [0, -36],
639
+ }),
640
+ );
641
+ };
642
+
643
+ image.onerror = function () {
644
+ resolve(null);
645
+ };
646
+
647
+ image.src = iconUrl;
648
+ });
649
+ }
650
+
651
+ function createMapMarker(map, point, item, color) {
652
+ if (
653
+ item &&
654
+ (item.__mapRole === 'user' ||
655
+ item.__mapRole === 'origin' ||
656
+ item.__mapRole === 'destination')
657
+ ) {
658
+ return Promise.resolve(createCircleMarker(map, point, item, color));
659
+ }
660
+
661
+ return resolveMarkerIcon(item).then(function (icon) {
662
+ if (icon) {
663
+ return L.marker([point.lat, point.lng], {
664
+ icon: icon,
665
+ }).addTo(map);
666
+ }
667
+
668
+ return L.marker([point.lat, point.lng]).addTo(map);
669
+ });
670
+ }
671
+
672
+ try {
673
+ if (!window.L) {
674
+ showError('Nao foi possivel carregar o mapa.');
675
+ throw new Error('map-library-missing');
676
+ }
677
+
678
+ if (L.Icon && L.Icon.Default && typeof L.Icon.Default.mergeOptions === 'function') {
679
+ delete L.Icon.Default.prototype._getIconUrl;
680
+ L.Icon.Default.mergeOptions({
681
+ iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
682
+ iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
683
+ shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
684
+ });
685
+ }
686
+
687
+ var map = L.map('map', {
688
+ zoomControl: true,
689
+ attributionControl: true,
690
+ });
691
+
692
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
693
+ maxZoom: 19,
694
+ attribution: '&copy; OpenStreetMap contributors',
695
+ crossOrigin: true,
696
+ }).addTo(map);
697
+
698
+ // CRITICAL: set an initial view immediately so tiles start loading.
699
+ // Without setView/fitBounds, Leaflet keeps an empty gray canvas.
700
+ var initialMarkers = Array.isArray(window.__SHOP_MAP_MARKERS__)
701
+ ? window.__SHOP_MAP_MARKERS__
702
+ : [];
703
+ var initialUser = window.__SHOP_MAP_USER__;
704
+ var initialCenter = null;
705
+ var initialZoom = 4;
706
+
707
+ function tryInitialPoint(item) {
708
+ if (!item) return null;
709
+ var lat = normalizeCoordinate(item.latitude);
710
+ var lng = normalizeCoordinate(item.longitude);
711
+ if (isValidPoint(lat, lng)) {
712
+ return { lat: lat, lng: lng };
713
+ }
714
+ return null;
715
+ }
716
+
717
+ if (initialUser) {
718
+ initialCenter = tryInitialPoint(initialUser);
719
+ if (initialCenter) initialZoom = 15;
720
+ }
721
+ if (!initialCenter) {
722
+ for (var i = 0; i < initialMarkers.length; i++) {
723
+ initialCenter = tryInitialPoint(initialMarkers[i]);
724
+ if (initialCenter) {
725
+ initialZoom = 15;
726
+ break;
727
+ }
728
+ }
729
+ }
730
+ if (!initialCenter) {
731
+ initialCenter = { lat: -14.235004, lng: -51.92528 };
732
+ initialZoom = 4;
733
+ }
734
+ map.setView([initialCenter.lat, initialCenter.lng], initialZoom, {
735
+ animate: false,
736
+ });
737
+
738
+ function forceMapResize() {
739
+ try {
740
+ map.invalidateSize({ animate: false });
741
+ } catch (e) {}
742
+ }
743
+ // iframe / RN-web often mounts with 0 size then expands
744
+ forceMapResize();
745
+ setTimeout(forceMapResize, 50);
746
+ setTimeout(forceMapResize, 250);
747
+ setTimeout(forceMapResize, 800);
748
+ if (typeof window !== 'undefined') {
749
+ window.addEventListener('resize', forceMapResize);
750
+ if (typeof ResizeObserver !== 'undefined') {
751
+ try {
752
+ var ro = new ResizeObserver(function () {
753
+ forceMapResize();
754
+ });
755
+ var mapEl = document.getElementById('map');
756
+ if (mapEl) ro.observe(mapEl);
757
+ } catch (e) {}
758
+ }
759
+ }
760
+
761
+ var bounds = L.latLngBounds([]);
762
+ var markers = initialMarkers;
763
+ var routes = Array.isArray(window.__SHOP_MAP_PATHS__) ? window.__SHOP_MAP_PATHS__ : [];
764
+ var userCoordinates = initialUser;
765
+
766
+ if (
767
+ userCoordinates &&
768
+ isValidPoint(
769
+ normalizeCoordinate(userCoordinates.latitude),
770
+ normalizeCoordinate(userCoordinates.longitude),
771
+ )
772
+ ) {
773
+ var userPoint = {
774
+ lat: normalizeCoordinate(userCoordinates.latitude),
775
+ lng: normalizeCoordinate(userCoordinates.longitude),
776
+ };
777
+ var userMarker = createCircleMarker(map, userPoint, {__mapRole: 'user'}, '#0ea5e9');
778
+ userMarker.bindPopup('<div class="marker-popup"><div class="marker-popup-title">Sua localização</div></div>');
779
+ addPointToBounds(bounds, userPoint);
780
+ }
781
+
782
+ Promise.all(
783
+ markers.map(function (item) {
784
+ return resolvePoint(item).then(function (point) {
785
+ if (!point) {
786
+ return null;
787
+ }
788
+
789
+ var color = resolveCircleColor(item, '#64748b');
790
+
791
+ return createMapMarker(map, point, item, color).then(function (marker) {
792
+ marker.bindPopup(buildPopupContent(item));
793
+ addPointToBounds(bounds, point);
794
+ return marker;
795
+ });
796
+ });
797
+ }),
798
+ ).then(function () {
799
+ function resolveRouteCoordinates(path) {
800
+ var fromSource = path && (path.from || path.origin || path.start);
801
+ var toSource = path && (path.to || path.destination || path.end);
802
+
803
+ return Promise.all([
804
+ resolvePoint(fromSource),
805
+ resolvePoint(toSource),
806
+ ]).then(function (points) {
807
+ var from = points[0];
808
+ var to = points[1];
809
+
810
+ if (!from || !to) {
811
+ return null;
812
+ }
813
+
814
+ var routeUrl =
815
+ 'https://router.project-osrm.org/route/v1/driving/' +
816
+ from.lng +
817
+ ',' +
818
+ from.lat +
819
+ ';' +
820
+ to.lng +
821
+ ',' +
822
+ to.lat +
823
+ '?overview=full&geometries=geojson&steps=false';
824
+
825
+ return fetch(routeUrl)
826
+ .then(function (response) {
827
+ if (!response || !response.ok) {
828
+ throw new Error('route-unavailable');
829
+ }
830
+
831
+ return response.json();
832
+ })
833
+ .then(function (payload) {
834
+ var geometry = payload && payload.routes && payload.routes[0] && payload.routes[0].geometry;
835
+ var coordinates = geometry && Array.isArray(geometry.coordinates)
836
+ ? geometry.coordinates
837
+ .map(function (pair) {
838
+ return {
839
+ lat: normalizeCoordinate(pair && pair[1]),
840
+ lng: normalizeCoordinate(pair && pair[0]),
841
+ };
842
+ })
843
+ .filter(function (point) {
844
+ return isValidPoint(point.lat, point.lng);
845
+ })
846
+ : [];
847
+
848
+ return {
849
+ path: path,
850
+ coordinates: coordinates.length > 1 ? coordinates : [from, to],
851
+ };
852
+ })
853
+ .catch(function () {
854
+ return {
855
+ path: path,
856
+ coordinates: [from, to],
857
+ };
858
+ });
859
+ });
860
+ }
861
+
862
+ Promise.all(routes.map(resolveRouteCoordinates)).then(function (resolvedRoutes) {
863
+ (resolvedRoutes || [])
864
+ .filter(Boolean)
865
+ .forEach(function (entry) {
866
+ var coordinates = Array.isArray(entry.coordinates) ? entry.coordinates : [];
867
+
868
+ if (coordinates.length < 2) {
869
+ return;
870
+ }
871
+
872
+ L.polyline(
873
+ coordinates.map(function (point) {
874
+ return [point.lat, point.lng];
875
+ }),
876
+ {
877
+ color:
878
+ entry.path && entry.path.color
879
+ ? entry.path.color
880
+ : window.__SHOP_MAP_ROUTE_COLOR__ || '#0ea5e9',
881
+ weight: 4,
882
+ opacity: 0.78,
883
+ },
884
+ ).addTo(map);
885
+
886
+ coordinates.forEach(function (point) {
887
+ addPointToBounds(bounds, point);
888
+ });
889
+ });
890
+
891
+ if (!bounds.isValid()) {
892
+ map.setView([-14.235004, -51.92528], 4);
893
+ } else {
894
+ map.fitBounds(bounds.pad(0.2), {
895
+ padding: [32, 32],
896
+ animate: false,
897
+ });
898
+ }
899
+
900
+ setTimeout(function () {
901
+ map.invalidateSize();
902
+ }, 0);
903
+ });
904
+ });
905
+ } catch (error) {
906
+ showError('Nao foi possivel carregar o mapa.');
907
+ }
908
+ </script>
909
+ </body>
910
+ </html>`;
911
+ };