@aggdirect/coolmap-services 1.4.9 → 4.0.1

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.
@@ -1,123 +1,40 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, Inject } from '@angular/core';
3
- import * as mapboxgl from 'mapbox-gl';
4
- import * as turf from '@turf/turf';
2
+ import { Component, InjectionToken, inject, NgZone, Injectable, signal } from '@angular/core';
5
3
  import { BehaviorSubject } from 'rxjs';
6
- import { MapboxLayer } from '@deck.gl/mapbox';
7
- import { ArcLayer } from '@deck.gl/layers';
8
- import * as i1 from '@angular/common/http';
9
- import * as i2 from '@angular/material/snack-bar';
10
- import * as i2$1 from '@angular/platform-browser';
4
+ import mapboxgl from 'mapbox-gl';
5
+ import * as turf from '@turf/turf';
6
+ import { HttpClient } from '@angular/common/http';
11
7
 
12
- class Route {
13
- index = 0;
14
- type;
15
- // Job Code
16
- customer_contact;
17
- customer_name;
18
- delivery_contact;
19
- delivery_lat;
20
- delivery_location;
21
- delivery_lon;
22
- driver_list;
23
- material;
24
- order_number;
25
- pickup_lat;
26
- pickup_location;
27
- pickup_lon;
28
- project;
29
- total_count;
30
- unit;
31
- values;
32
- job_id;
33
- isSelected;
34
- date;
35
- // Add Route
36
- created_at;
37
- created_by_name;
38
- customer_id;
39
- delivery_lat_lng;
40
- estimated_distance;
41
- estimated_time;
42
- materials_id;
43
- path;
44
- pickup_lat_lng;
45
- route_id;
46
- route_name;
47
- unit_id;
48
- note;
49
- materialLabel;
50
- isActive;
51
- prevent;
52
- is_system_pickup;
53
- is_system_delivery;
54
- }
55
- const EstinationData = ['estimated_distance', 'estimated_time'];
56
- var EstinationEnum;
57
- (function (EstinationEnum) {
58
- EstinationEnum["estimated_distance"] = "miles";
59
- EstinationEnum["estimated_time"] = "time";
60
- })(EstinationEnum || (EstinationEnum = {}));
61
- const JobCodeOverviewData = ['order_number', 'customer_name', 'project', 'unit', 'material', 'customer_contact', 'delivery_contact', 'pickup_location', 'delivery_location'];
62
- var JobCodeOverviewEnum;
63
- (function (JobCodeOverviewEnum) {
64
- JobCodeOverviewEnum["material"] = "Material";
65
- JobCodeOverviewEnum["order_number"] = "Job Code";
66
- JobCodeOverviewEnum["customer_name"] = "Customer";
67
- JobCodeOverviewEnum["customer_contact"] = "Customer Contact";
68
- JobCodeOverviewEnum["delivery_contact"] = "Delivery Contact";
69
- JobCodeOverviewEnum["project"] = "Project Name";
70
- JobCodeOverviewEnum["unit"] = "Job Type";
71
- JobCodeOverviewEnum["pickup_location"] = "Pickup";
72
- JobCodeOverviewEnum["delivery_location"] = "Delivery";
73
- })(JobCodeOverviewEnum || (JobCodeOverviewEnum = {}));
74
- const DriversmsCardKey = ['order_number', 'date', 'values', 'material', 'unit', 'pickup_location', 'delivery_location'];
75
- var DriverSmsCardEnum;
76
- (function (DriverSmsCardEnum) {
77
- DriverSmsCardEnum["order_number"] = "Jobcode";
78
- DriverSmsCardEnum["date"] = "Date";
79
- DriverSmsCardEnum["values"] = "Total tasks";
80
- DriverSmsCardEnum["material"] = "Material";
81
- DriverSmsCardEnum["unit"] = "Unit";
82
- DriverSmsCardEnum["pickup_location"] = "Pickup Address";
83
- DriverSmsCardEnum["delivery_location"] = "Delivery Address";
84
- })(DriverSmsCardEnum || (DriverSmsCardEnum = {}));
85
- class PopupData {
86
- coordinate;
87
- pickup;
88
- jobCode;
89
- customer;
90
- drop;
91
- routeType;
92
- title;
93
- material;
94
- type;
8
+ class CoolmapServices {
9
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapServices, deps: [], target: i0.ɵɵFactoryTarget.Component });
10
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: CoolmapServices, isStandalone: true, selector: "lib-coolmap-services", ngImport: i0, template: `
11
+ <p>
12
+ coolmap-services works!
13
+ </p>
14
+ `, isInline: true, styles: [""] });
95
15
  }
96
- class CoolmapConfigModel {
97
- analyticsRESTURL;
98
- RESTURLPrefix;
99
- repository;
100
- mapboxStyle;
101
- mapboxAccessToken;
16
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapServices, decorators: [{
17
+ type: Component,
18
+ args: [{ selector: 'lib-coolmap-services', imports: [], template: `
19
+ <p>
20
+ coolmap-services works!
21
+ </p>
22
+ ` }]
23
+ }] });
24
+
25
+ const COOLMAP_CONFIG = new InjectionToken('COOLMAP_CONFIG');
26
+ function provideCoolmapConfig(config) {
27
+ return {
28
+ provide: COOLMAP_CONFIG,
29
+ useValue: config
30
+ };
102
31
  }
103
32
 
104
33
  class UtilsService {
105
- http;
106
- snackBar;
107
- config;
108
- ngZone;
109
- analyticsRESTURL = '';
110
- RESTURLPrefix = '';
111
- pickupOptions = [];
112
- destOptions = [];
113
- ownerOptions = [];
114
- customerOptions = [];
115
- unitOptions = [];
116
- materialOptions = [];
117
- jcodeOptions = [];
118
- driverOption = [];
119
- truckingCompanayOption = [];
120
- routeNameOptions = [];
34
+ http = inject(HttpClient);
35
+ config = inject(COOLMAP_CONFIG);
36
+ ngZone = inject(NgZone);
37
+ // BehaviorSubjects for state (converting to simple Signals later where practical)
121
38
  preventnavChange = new BehaviorSubject(false);
122
39
  navChangeObserve = this.preventnavChange.asObservable();
123
40
  clearViewRouteforJobCode = new BehaviorSubject(false);
@@ -134,97 +51,96 @@ class UtilsService {
134
51
  materialsListForCustomer;
135
52
  customersList;
136
53
  locationList;
137
- pickUpAutocomplete;
138
- constructor(http, snackBar, config, ngZone) {
139
- this.http = http;
140
- this.snackBar = snackBar;
141
- this.config = config;
142
- this.ngZone = ngZone;
143
- this.analyticsRESTURL = config.analyticsRESTURL;
144
- this.RESTURLPrefix = config.RESTURLPrefix;
145
- }
54
+ // Options filtering properties
55
+ pickupOptions = [];
56
+ destOptions = [];
57
+ ownerOptions = [];
58
+ customerOptions = [];
59
+ unitOptions = [];
60
+ materialOptions = [];
61
+ jcodeOptions = [];
62
+ driverOption = [];
63
+ truckingCompanayOption = [];
64
+ routeNameOptions = [];
146
65
  getDateFormat(strVal, seprater) {
147
66
  seprater = seprater ? seprater : '-';
148
67
  const mydate = strVal;
149
- return (mydate?.getFullYear() +
68
+ if (!mydate)
69
+ return '';
70
+ const month = String(mydate.getMonth() + 1).padStart(2, '0');
71
+ const day = String(mydate.getDate()).padStart(2, '0');
72
+ return (mydate.getFullYear() +
150
73
  seprater +
151
- ((mydate ? mydate.getMonth() : 0) + 1) +
74
+ month +
152
75
  seprater +
153
- mydate?.getDate());
76
+ day);
154
77
  }
155
78
  getData(path) {
156
- return this.http.get(`${this.analyticsRESTURL}${this.config.repository}/${path}`);
79
+ return this.http.get(`${this.config.analyticsRESTURL}${this.config.repository}/${path}`);
157
80
  }
158
81
  getRestData(path) {
159
- return this.http.get(`${this.RESTURLPrefix}${path}`);
82
+ return this.http.get(`${this.config.RESTURLPrefix}${path}`);
160
83
  }
161
84
  postdata(path, data) {
162
- return this.http.post(`${this.analyticsRESTURL}${this.config.repository}/${path}`, data);
85
+ return this.http.post(`${this.config.analyticsRESTURL}${this.config.repository}/${path}`, data);
163
86
  }
164
87
  postDataWithRestUrl(path, data) {
165
- return this.http.post(`${this.RESTURLPrefix}${path}`, data);
166
- }
167
- autocomplete(searchElementRef, type) {
168
- return new Promise((resolve, reject) => {
169
- const autocomplete = new google.maps.places.Autocomplete(searchElementRef.nativeElement, {
170
- types: ['address']
171
- });
172
- google.maps.event.addListener(autocomplete, 'place_changed', () => {
173
- this.ngZone.run(() => {
174
- const place = autocomplete.getPlace();
175
- resolve({ lat: place.geometry.location.lat(), lng: place.geometry.location.lng(), formatted_address: place['formatted_address'] });
176
- });
177
- });
178
- });
88
+ return this.http.post(`${this.config.RESTURLPrefix}${path}`, data);
179
89
  }
90
+ // Instead of MatSnackBar, we just use a generic console/alert fallback for now.
180
91
  openSnackBar(message, className) {
181
- this.snackBar.open(message, '', {
182
- duration: 5000,
183
- verticalPosition: 'top',
184
- horizontalPosition: 'center',
185
- panelClass: [className ? className : 'default'],
186
- });
92
+ }
93
+ clearOptions() {
94
+ this.pickupOptions = [];
95
+ this.destOptions = [];
96
+ this.ownerOptions = [];
97
+ this.customerOptions = [];
98
+ this.unitOptions = [];
99
+ this.materialOptions = [];
100
+ this.jcodeOptions = [];
101
+ this.routeNameOptions = [];
187
102
  }
188
103
  makeOptions(item) {
104
+ // Porting V1 makeOptions safely
189
105
  if (item.order_number) {
190
- this.jcodeOptions.findIndex((elem) => elem.job_id === item.job_id) === -1
191
- ? this.jcodeOptions.push({
192
- job_code: item.order_number,
193
- job_id: item.job_id,
194
- })
195
- : null;
196
- if (item.driver_list && item.driver_list?.length > 0) {
197
- item.driver_list.forEach((driver) => {
198
- this.driverOption.findIndex((elem) => elem === driver['driver_name']) === -1
199
- ? this.driverOption.push(driver['driver_name'])
200
- : null;
201
- this.truckingCompanayOption.findIndex((elem) => elem === driver['trucking_company']) === -1
202
- ? this.truckingCompanayOption.push(driver['trucking_company'])
203
- : null;
106
+ if (this.jcodeOptions.findIndex((elem) => elem.job_id === item.job_id) === -1) {
107
+ this.jcodeOptions.push({ job_code: item.order_number, job_id: item.job_id });
108
+ }
109
+ // Assuming tasks structure if Mapbox Route logic was merged, else driver_list from V1
110
+ const driver_list = item.driver_list || [];
111
+ if (driver_list.length > 0) {
112
+ driver_list.forEach((driver) => {
113
+ if (this.driverOption.findIndex((elem) => elem === driver.driver_name) === -1) {
114
+ this.driverOption.push(driver.driver_name);
115
+ }
116
+ if (this.truckingCompanayOption.findIndex((elem) => elem === driver.trucking_company) === -1) {
117
+ this.truckingCompanayOption.push(driver.trucking_company);
118
+ }
204
119
  });
205
120
  }
206
121
  }
207
- this.pickupOptions.findIndex((elem) => elem === item.pickup_location) === -1
208
- ? this.pickupOptions.push(item.pickup_location)
209
- : null;
210
- this.destOptions.findIndex((elem) => elem === item.delivery_location) === -1
211
- ? this.destOptions.push(item.delivery_location)
212
- : null;
213
- this.customerOptions.findIndex((customer) => customer === item.customer_name) === -1
214
- ? this.customerOptions.push(item.customer_name)
215
- : null;
216
- this.unitOptions.findIndex((elem) => elem === item.unit) === -1
217
- ? this.unitOptions.push(item.unit)
218
- : null;
219
- if (item.material) {
220
- this.materialOptions.findIndex((elem) => elem === item.material) === -1
221
- ? this.materialOptions.push(item.material)
222
- : null;
223
- }
224
- if (item.route_name)
225
- this.routeNameOptions.findIndex((elem) => elem === item.route_name) === -1
226
- ? this.routeNameOptions.push(item.route_name)
227
- : null;
122
+ if (item.pickup_location && this.pickupOptions.findIndex((elem) => elem === item.pickup_location) === -1)
123
+ this.pickupOptions.push(item.pickup_location);
124
+ if (item.delivery_location && this.destOptions.findIndex((elem) => elem === item.delivery_location) === -1)
125
+ this.destOptions.push(item.delivery_location);
126
+ if (item.customer_name && this.customerOptions.findIndex((customer) => customer === item.customer_name) === -1)
127
+ this.customerOptions.push(item.customer_name);
128
+ if (item.unit && this.unitOptions.findIndex((elem) => elem === item.unit) === -1)
129
+ this.unitOptions.push(item.unit);
130
+ if (item.material && this.materialOptions.findIndex((elem) => elem === item.material) === -1)
131
+ this.materialOptions.push(item.material);
132
+ if (item.route_name && this.routeNameOptions.findIndex((elem) => elem === item.route_name) === -1)
133
+ this.routeNameOptions.push(item.route_name);
134
+ }
135
+ setdictValue(key, value) {
136
+ this.dict.set(key, value);
137
+ }
138
+ getdictValue(key) {
139
+ const val = this.dict.get(key);
140
+ return val ? JSON.parse(val) : null;
141
+ }
142
+ removedictValue(key) {
143
+ this.dict.delete(key);
228
144
  }
229
145
  filter(value, filters) {
230
146
  if (typeof value !== 'string') {
@@ -235,24 +151,24 @@ class UtilsService {
235
151
  return [];
236
152
  }
237
153
  const searchResults = [];
238
- this.unitOptions.map((unit) => {
154
+ this.unitOptions.forEach((unit) => {
239
155
  if (unit.toLowerCase().includes(filterValue)) {
240
156
  searchResults.push({ type: 'unit', label: unit, value: unit });
241
157
  }
242
158
  });
243
- this.customerOptions.map((unit) => {
159
+ this.customerOptions.forEach((unit) => {
244
160
  if (unit.toLowerCase().includes(filterValue)) {
245
161
  searchResults.push({ type: 'customer', label: unit, value: unit });
246
162
  }
247
163
  });
248
- if (this.materialOptions.length) {
249
- this.materialOptions.map((unit) => {
164
+ if (this.materialOptions && this.materialOptions.length) {
165
+ this.materialOptions.forEach((unit) => {
250
166
  if (unit.toLowerCase().includes(filterValue)) {
251
167
  searchResults.push({ type: 'material', label: unit, value: unit });
252
168
  }
253
169
  });
254
170
  }
255
- this.pickupOptions.map((unit) => {
171
+ this.pickupOptions.forEach((unit) => {
256
172
  if (unit.toLowerCase().includes(filterValue)) {
257
173
  searchResults.push({
258
174
  type: 'pickup location',
@@ -261,7 +177,7 @@ class UtilsService {
261
177
  });
262
178
  }
263
179
  });
264
- this.destOptions.map((unit) => {
180
+ this.destOptions.forEach((unit) => {
265
181
  if (unit.toLowerCase().includes(filterValue)) {
266
182
  searchResults.push({
267
183
  type: 'destination location',
@@ -270,7 +186,7 @@ class UtilsService {
270
186
  });
271
187
  }
272
188
  });
273
- this.jcodeOptions.map((unit) => {
189
+ this.jcodeOptions.forEach((unit) => {
274
190
  if (unit.job_code.toLowerCase().includes(filterValue)) {
275
191
  searchResults.push({
276
192
  type: 'job',
@@ -279,12 +195,12 @@ class UtilsService {
279
195
  });
280
196
  }
281
197
  });
282
- this.driverOption.map((unit) => {
198
+ this.driverOption.forEach((unit) => {
283
199
  if (unit.toLowerCase().includes(filterValue)) {
284
200
  searchResults.push({ type: 'Driver', label: unit, value: unit });
285
201
  }
286
202
  });
287
- this.truckingCompanayOption.map((unit) => {
203
+ this.truckingCompanayOption.forEach((unit) => {
288
204
  if (unit.toLowerCase().includes(filterValue)) {
289
205
  searchResults.push({
290
206
  type: 'Trucking Company',
@@ -293,20 +209,19 @@ class UtilsService {
293
209
  });
294
210
  }
295
211
  });
296
- this.routeNameOptions.map((unit) => {
212
+ this.routeNameOptions.forEach((unit) => {
297
213
  if (unit.toLowerCase().includes(filterValue)) {
298
214
  searchResults.push({ type: 'Route name', label: unit, value: unit });
299
215
  }
300
216
  });
217
+ // Filter out options that are already selected in the filters array
301
218
  const searchDict = {};
302
- filters.map((filter) => {
219
+ filters.forEach((filter) => {
303
220
  searchDict[filter['name'] + filter['type']] = filter;
304
221
  });
305
222
  const furtherFilter = [];
306
- searchResults.map((search) => {
307
- if (search['label'] + search['type'] in searchDict) {
308
- }
309
- else {
223
+ searchResults.forEach((search) => {
224
+ if (!(search['label'] + search['type'] in searchDict)) {
310
225
  furtherFilter.push(search);
311
226
  }
312
227
  });
@@ -317,15 +232,13 @@ class UtilsService {
317
232
  const result_list_boolean = [];
318
233
  if (filterval.length > 0) {
319
234
  if (filterval[0]['type'] === 'unit') {
320
- result_list_boolean.push(filterval[0]['name'] ===
321
- element[filterval[0]['type']]);
235
+ result_list_boolean.push(filterval[0]['name'] === element['unit']);
322
236
  }
323
237
  if (filterval[0]['type'] === 'customer') {
324
238
  result_list_boolean.push(filterval[0]['name'] === element['customer_name']);
325
239
  }
326
240
  if (filterval[0]['type'] === 'material') {
327
- result_list_boolean.push(filterval[0]['name'] ===
328
- element[filterval[0]['type']]);
241
+ result_list_boolean.push(filterval[0]['name'] === element['material']);
329
242
  }
330
243
  if (filterval[0]['type'] === 'pickup location') {
331
244
  result_list_boolean.push(filterval[0]['name'] === element['pickup_location']);
@@ -340,14 +253,16 @@ class UtilsService {
340
253
  result_list_boolean.push(filterval[0]['name'] === element['route_name']);
341
254
  }
342
255
  if (filterval[0]['type'] === 'Driver') {
343
- const index = element.driver_list?.findIndex((ele) => {
256
+ const driverList = element.driver_list || [];
257
+ const index = driverList.findIndex((ele) => {
344
258
  return filterval[0]['name'] === ele['driver_name'];
345
259
  });
346
260
  if (index !== -1)
347
261
  result_list_boolean.push(true);
348
262
  }
349
263
  if (filterval[0]['type'] === 'Trucking Company') {
350
- const index = element.driver_list?.findIndex((ele) => {
264
+ const driverList = element.driver_list || [];
265
+ const index = driverList.findIndex((ele) => {
351
266
  return filterval[0]['name'] === ele['trucking_company'];
352
267
  });
353
268
  if (index !== -1)
@@ -360,32 +275,8 @@ class UtilsService {
360
275
  return false;
361
276
  });
362
277
  }
363
- clearOptions() {
364
- this.pickupOptions = [];
365
- this.destOptions = [];
366
- this.ownerOptions = [];
367
- this.customerOptions = [];
368
- this.unitOptions = [];
369
- this.materialOptions = [];
370
- this.jcodeOptions = [];
371
- this.routeNameOptions = [];
372
- }
373
- setdictValue(key, value) {
374
- this.dict.set(key, value);
375
- }
376
- getdictValue(key) {
377
- return JSON.parse(this.dict.get(key));
378
- }
379
- removedictValue(key) {
380
- this.dict.delete(key);
381
- }
382
- conveySearchIcon(value) {
383
- if (value && typeof value !== 'object')
384
- return true;
385
- return false;
386
- }
387
278
  fetchUnitsList() {
388
- return new Promise((resolve, reject) => {
279
+ return new Promise((resolve) => {
389
280
  if (!this.unitsList) {
390
281
  this.getData('unit/list/view').subscribe((res) => {
391
282
  if (res) {
@@ -399,21 +290,8 @@ class UtilsService {
399
290
  }
400
291
  });
401
292
  }
402
- fetchMaterialsList() {
403
- return new Promise((resolve, reject) => {
404
- if (!this.materialsList) {
405
- this.getData('material/list/view').subscribe((res) => {
406
- this.materialsList = this.filtermaterialList(res.data);
407
- resolve(this.materialsList);
408
- });
409
- }
410
- else {
411
- resolve(this.materialsList);
412
- }
413
- });
414
- }
415
293
  fetchMaterialsListForCustomer() {
416
- return new Promise((resolve, reject) => {
294
+ return new Promise((resolve) => {
417
295
  if (!this.materialsListForCustomer) {
418
296
  this.getData('material/list/view').subscribe((res) => {
419
297
  this.materialsListForCustomer = this.filtermaterialList(res.data);
@@ -427,9 +305,9 @@ class UtilsService {
427
305
  }
428
306
  filtermaterialList(list) {
429
307
  let meterialList = [];
430
- list.map((item) => {
431
- if (item.sub.length > 0) {
432
- item.sub.map((subItem) => {
308
+ list.forEach((item) => {
309
+ if (item.sub && item.sub.length > 0) {
310
+ item.sub.forEach((subItem) => {
433
311
  if (subItem.add_to_marketplace) {
434
312
  subItem.material = item.material;
435
313
  subItem.material_id = item.material_id;
@@ -443,7 +321,7 @@ class UtilsService {
443
321
  return meterialList;
444
322
  }
445
323
  fetchCustomersList() {
446
- return new Promise((resolve, reject) => {
324
+ return new Promise((resolve) => {
447
325
  if (!this.customersList) {
448
326
  this.getData('company/list/view').subscribe((res) => {
449
327
  if (res) {
@@ -459,99 +337,129 @@ class UtilsService {
459
337
  }
460
338
  fetchLocationlist() {
461
339
  return new Promise((resolve, reject) => {
462
- this.getRestData('locations/all').subscribe((res) => {
463
- if (res) {
464
- this.locationList = res.data;
465
- this.locationList = this.locationList.map((object) => {
466
- object.formatted_address = `${object.name} | ${object.street} ${object.city}, ${object.state} ${object.zip}`;
467
- const newItem = {
468
- city: object.city,
469
- lng: object.longitude,
470
- location_id: object.location_id,
471
- lat: object.latitude,
472
- name: object.name,
473
- state: object.state,
474
- street: object.street,
475
- zip: object.zip,
476
- formatted_address: object.formatted_address
477
- };
478
- return newItem;
479
- });
480
- resolve(this.locationList);
340
+ this.getRestData('locations/all').subscribe({
341
+ next: (res) => {
342
+ if (res) {
343
+ this.locationList = res.data.map((object) => {
344
+ object.formatted_address = `${object.name} | ${object.street} ${object.city}, ${object.state} ${object.zip}`;
345
+ return {
346
+ city: object.city,
347
+ lng: object.longitude,
348
+ location_id: object.location_id,
349
+ lat: object.latitude,
350
+ name: object.name,
351
+ state: object.state,
352
+ street: object.street,
353
+ zip: object.zip,
354
+ formatted_address: object.formatted_address
355
+ };
356
+ });
357
+ resolve(this.locationList);
358
+ }
359
+ },
360
+ error: (err) => {
361
+ console.error(err);
362
+ reject(err);
481
363
  }
482
- }, err => {
483
- console.log(err);
484
364
  });
485
365
  });
486
366
  }
487
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: UtilsService, deps: [{ token: i1.HttpClient }, { token: i2.MatSnackBar }, { token: 'memberData' }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
488
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: UtilsService, providedIn: 'root' });
367
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
368
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UtilsService, providedIn: 'root' });
489
369
  }
490
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: UtilsService, decorators: [{
370
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UtilsService, decorators: [{
491
371
  type: Injectable,
492
372
  args: [{ providedIn: 'root' }]
493
- }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.MatSnackBar }, { type: CoolmapConfigModel, decorators: [{
494
- type: Inject,
495
- args: ['memberData']
496
- }] }, { type: i0.NgZone }] });
373
+ }] });
497
374
 
498
- class CoolmapService {
499
- utils;
500
- eventManager;
501
- config;
375
+ class MapboxService {
502
376
  map;
503
- markerOriginList = [];
504
- markerDestinationList = [];
377
+ mapContainer = null;
378
+ markerOriginList = new Map();
379
+ markerDestinationList = new Map();
380
+ activeRoutesRegistry = new Map();
381
+ pathCache = new Map();
382
+ // Native 3D Simulation Engine
383
+ svgOverlay = null;
384
+ activeArcs = {};
505
385
  initiatecoolmap = new BehaviorSubject(true);
506
386
  reintiatecoolmap = this.initiatecoolmap.asObservable();
387
+ isMapReady = signal(false, ...(ngDevMode ? [{ debugName: "isMapReady" }] : []));
507
388
  bounds = new mapboxgl.LngLatBounds();
508
389
  originDestinationCordinates = [];
509
390
  padding;
510
- windowActualHeightWidth;
391
+ windowActualHeightWidth = { availHeight: 0 };
511
392
  popup;
512
393
  customTopForCustomer;
513
- constructor(utils, eventManager, config) {
514
- this.utils = utils;
515
- this.eventManager = eventManager;
516
- this.config = config;
394
+ utils = inject(UtilsService);
395
+ config = inject(COOLMAP_CONFIG);
396
+ currentStyleIsDark = false;
397
+ constructor() {
517
398
  this.customTopForCustomer = this.config.repository === 'customer' ? 65 : 0;
518
- this.windowActualHeightWidth = { availHeight: 0 };
519
- window.addEventListener('resize', this.onResize.bind(this));
399
+ if (typeof window !== 'undefined') {
400
+ this.windowActualHeightWidth.availHeight = window.innerHeight > window.screen.availHeight ? window.innerHeight : window.screen.availHeight;
401
+ window.addEventListener('resize', this.onResize.bind(this));
402
+ }
403
+ }
404
+ updateTheme(isDark) {
405
+ if (!this.map) {
406
+ this.currentStyleIsDark = isDark;
407
+ return;
408
+ }
409
+ if (this.currentStyleIsDark === isDark)
410
+ return;
411
+ this.currentStyleIsDark = isDark;
412
+ const style = isDark
413
+ ? 'mapbox://styles/mapbox/dark-v11'
414
+ : 'mapbox://styles/mapbox/light-v11';
415
+ this.map.setStyle(style);
416
+ this.map.once('style.load', () => {
417
+ this.reAddLayers();
418
+ });
419
+ }
420
+ getRegistryKeys() {
421
+ return Array.from(this.activeRoutesRegistry.keys());
520
422
  }
521
- initiateMapForAddRoute(el) {
423
+ // Renamed to initializeMap to preserve the existing bindings in coolmap.component.ts
424
+ initializeMap(el, isDark = false) {
425
+ this.currentStyleIsDark = isDark;
522
426
  return new Promise((resolve, reject) => {
427
+ this.mapContainer = el;
523
428
  if (this.map)
524
429
  this.map.remove();
430
+ const token = typeof window !== 'undefined' ? window.__env?.mapboxAccessToken || '' : '';
431
+ mapboxgl.accessToken = token;
525
432
  this.map = new mapboxgl.Map({
526
- accessToken: this.config.mapboxAccessToken,
527
433
  container: el,
528
- style: this.config.mapboxStyle,
434
+ style: isDark ? 'mapbox://styles/mapbox/dark-v11' : 'mapbox://styles/mapbox/light-v11',
529
435
  center: [-77.036873, 38.907192],
530
436
  zoom: 10,
531
437
  bearing: 0,
532
438
  pitch: 65,
533
439
  interactive: true,
440
+ attributionControl: false
534
441
  });
442
+ this.map.addControl(new mapboxgl.AttributionControl({ compact: true }), 'bottom-right');
443
+ this.map.addControl(new mapboxgl.NavigationControl({ showCompass: false }), 'top-right');
535
444
  this.map.once('load', (res) => {
445
+ this.isMapReady.set(true);
536
446
  resolve(res);
537
447
  });
538
448
  });
539
449
  }
540
- // Below method Load route with animation
541
- loadMapProperty(pinRouteGeojson, index, unit, route, bottom) {
542
- return new Promise((resolve, reject) => {
543
- let origin = pinRouteGeojson.features[0].geometry.coordinates[0];
450
+ // Animated gradient path
451
+ loadMapProperty(pinRouteGeojson, index, unit, routeProps, bottom) {
452
+ return new Promise((resolve) => {
453
+ if (!this.map || !this.isMapReady())
454
+ return resolve(false);
455
+ const origin = pinRouteGeojson.features[0].geometry.coordinates[0];
544
456
  const linecolor = unit === 'Ton' ? '#ff7272' : unit === 'Load' ? '#a3c52e' : '#ae23d1';
545
- let destination = pinRouteGeojson.features[0].geometry.coordinates[pinRouteGeojson.features[0].geometry.coordinates.length - 1];
546
- this.extendBound(pinRouteGeojson.features[0].geometry.coordinates, true).then((res) => {
457
+ const destination = pinRouteGeojson.features[0].geometry.coordinates[pinRouteGeojson.features[0].geometry.coordinates.length - 1];
458
+ this.extendBound(pinRouteGeojson.features[0].geometry.coordinates, true).then(() => {
547
459
  const point = {
548
460
  type: 'FeatureCollection',
549
461
  features: [
550
- {
551
- type: 'Feature',
552
- properties: {},
553
- geometry: { type: 'Point', coordinates: origin },
554
- },
462
+ { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: origin } },
555
463
  ],
556
464
  };
557
465
  const lineDistance = turf.length(pinRouteGeojson.features[0]);
@@ -583,21 +491,9 @@ class CoolmapService {
583
491
  paint: {
584
492
  'line-width': 2,
585
493
  'line-gradient': [
586
- 'interpolate',
587
- ['linear'],
588
- ['line-progress'],
589
- 0,
590
- unit === 'Ton'
591
- ? '#d7f7e4'
592
- : unit === 'Load'
593
- ? '#c9d8f5'
594
- : '#f5dcc1',
595
- 1,
596
- unit === 'Ton'
597
- ? '#ff7272'
598
- : unit === 'Load'
599
- ? '#a3c52e'
600
- : '#ae23d1',
494
+ 'interpolate', ['linear'], ['line-progress'],
495
+ 0, unit === 'Ton' ? '#d7f7e4' : unit === 'Load' ? '#c9d8f5' : '#f5dcc1',
496
+ 1, unit === 'Ton' ? '#ff7272' : unit === 'Load' ? '#a3c52e' : '#ae23d1',
601
497
  ],
602
498
  },
603
499
  layout: { 'line-cap': 'round', 'line-join': 'round' },
@@ -616,7 +512,8 @@ class CoolmapService {
616
512
  destination,
617
513
  lineDistance,
618
514
  linecolor,
619
- route,
515
+ route: routeProps,
516
+ routeType: 'addroute',
620
517
  isViewRoute: true,
621
518
  };
622
519
  this.createMarker(dataSetForMap);
@@ -625,61 +522,218 @@ class CoolmapService {
625
522
  this.map.setPaintProperty(`line${index}`, 'line-opacity', 1);
626
523
  const datasetForPopup = {
627
524
  coordinate: [e.lngLat.lng, e.lngLat.lat],
628
- pickup: route.pickup_location ? route.pickup_location : '',
629
- drop: route.delivery_location ? route.delivery_location : '',
630
- routeType: route.project ? 'Project' : 'Route',
631
- title: route.project
632
- ? route.project
633
- : route.route_name
634
- ? route.route_name
635
- : '',
636
- material: route.material ? route.material : '',
637
- type: route.unit ? route.unit : '',
525
+ pickup: routeProps?.pickup_location || '',
526
+ drop: routeProps?.delivery_location || '',
527
+ routeType: routeProps?.project ? 'Project' : 'Route',
528
+ title: routeProps?.project ? routeProps.project : routeProps?.route_name || '',
529
+ material: routeProps?.material || '',
530
+ type: routeProps?.unit || '',
638
531
  };
639
532
  if (this.popup)
640
533
  this.popup.remove();
641
534
  this.createPopup(datasetForPopup);
642
535
  });
643
- this.map.on('mouseleave', `line${index}`, (e) => {
536
+ this.map.on('mouseleave', `line${index}`, () => {
644
537
  this.map.setPaintProperty(`line${index}`, 'line-width', 2);
645
- if (this.popup) {
538
+ if (this.popup)
646
539
  this.popup.remove();
647
- }
648
540
  });
649
541
  }
650
542
  });
651
- this.map.once('idle', (res) => {
543
+ this.map.once('idle', () => {
652
544
  resolve(true);
653
545
  });
654
546
  });
655
547
  }
656
- // Below method Load route without animation
657
- drawLine(cordinates, index, route, enablefitbound, routeType) {
658
- let linecolor;
659
- let origin = cordinates[0];
660
- let destination = cordinates[cordinates.length - 1];
661
- if (origin[0] &&
662
- origin[1] &&
663
- destination &&
664
- destination[0] &&
665
- destination[1]) {
666
- this.map.once('idle', (res) => {
667
- linecolor = this.provideLineColor(route['unit'], routeType);
548
+ createMarker(routeDetails) {
549
+ if (!this.map || !routeDetails.origin[0] || !routeDetails.origin[1] || !routeDetails.destination[0] || !routeDetails.destination[1])
550
+ return;
551
+ const popup = new mapboxgl.Popup({ closeButton: false }).setHTML('<b>Pickup: </b>' + (routeDetails.route?.pickup_location || ''));
552
+ const popupForDestination = new mapboxgl.Popup({ closeButton: false }).setHTML('<b>Delivery: </b>' + (routeDetails.route?.delivery_location || ''));
553
+ // Origin Marker (Pickup)
554
+ const el = document.createElement('div');
555
+ el.className = 'cursor-pointer active:scale-95 transition-transform duration-200 drop-shadow-lg';
556
+ const pickupLabel = 'P';
557
+ el.innerHTML = `
558
+ <svg width="30" height="38" viewBox="0 0 30 38" fill="none" xmlns="http://www.w3.org/2000/svg">
559
+ <path d="M15 0C6.71573 0 0 6.71573 0 15C0 26.25 15 38 15 38C15 38 30 26.25 30 15C30 6.71573 23.2843 0 15 0Z" fill="${routeDetails.linecolor}"/>
560
+ <circle cx="15" cy="15" r="9" fill="white"/>
561
+ <text x="15" y="19" text-anchor="middle" fill="black" style="font-family: Inter, sans-serif; font-size: 13px; font-weight: 800;">${pickupLabel}</text>
562
+ </svg>
563
+ `;
564
+ const originMarker = new mapboxgl.Marker({ element: el, anchor: 'bottom' })
565
+ .setPopup(popup)
566
+ .setLngLat(routeDetails.origin)
567
+ .addTo(this.map);
568
+ originMarker.getElement().addEventListener('mouseenter', () => originMarker.togglePopup());
569
+ originMarker.getElement().addEventListener('mouseleave', () => originMarker.togglePopup());
570
+ // Destination Marker (Delivery)
571
+ const elementForDestination = document.createElement('div');
572
+ elementForDestination.className = 'cursor-pointer active:scale-95 transition-transform duration-200 drop-shadow-lg';
573
+ const deliveryLabel = 'D';
574
+ elementForDestination.innerHTML = `
575
+ <svg width="30" height="38" viewBox="0 0 30 38" fill="none" xmlns="http://www.w3.org/2000/svg">
576
+ <path d="M15 0C6.71573 0 0 6.71573 0 15C0 26.25 15 38 15 38C15 38 30 26.25 30 15C30 6.71573 23.2843 0 15 0Z" fill="${routeDetails.linecolor}"/>
577
+ <circle cx="15" cy="15" r="9" fill="white"/>
578
+ <text x="15" y="19" text-anchor="middle" fill="black" style="font-family: Inter, sans-serif; font-size: 13px; font-weight: 800;">${deliveryLabel}</text>
579
+ </svg>
580
+ `;
581
+ const destinationMarker = new mapboxgl.Marker({ element: elementForDestination, anchor: 'bottom' })
582
+ .setPopup(popupForDestination)
583
+ .setLngLat(routeDetails.destination)
584
+ .addTo(this.map);
585
+ destinationMarker.getElement().addEventListener('mouseenter', () => destinationMarker.togglePopup());
586
+ destinationMarker.getElement().addEventListener('mouseleave', () => destinationMarker.togglePopup());
587
+ const id = `${routeDetails.routeType}-${routeDetails.index}`;
588
+ this.markerOriginList.set(id, originMarker);
589
+ this.markerDestinationList.set(id, destinationMarker);
590
+ // True 3D Elevated Arc Simulation Engine using Native SVG Overlays
591
+ try {
592
+ this.initSvgOverlay();
593
+ const pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
594
+ pathEl.setAttribute('fill', 'none');
595
+ pathEl.setAttribute('stroke-width', '3');
596
+ pathEl.style.cursor = 'pointer';
597
+ pathEl.style.pointerEvents = 'stroke'; // Allow interactivity!
598
+ const gradId = `arc-grad-${id}`;
599
+ const defs = this.svgOverlay.querySelector('defs');
600
+ let grad = defs.querySelector(`#${gradId}`);
601
+ if (!grad) {
602
+ grad = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
603
+ grad.setAttribute('id', gradId);
604
+ defs.appendChild(grad);
605
+ }
606
+ grad.innerHTML = `
607
+ <stop offset="0%" stop-color="${routeDetails.linecolor}" stop-opacity="1"/>
608
+ <stop offset="100%" stop-color="#ffffff" stop-opacity="0.8"/>
609
+ `;
610
+ pathEl.setAttribute('stroke', `url(#${gradId})`);
611
+ this.svgOverlay.appendChild(pathEl);
612
+ this.activeArcs[id] = {
613
+ origin: routeDetails.origin,
614
+ destination: routeDetails.destination,
615
+ color: routeDetails.linecolor,
616
+ pathEl
617
+ };
618
+ // Native tooltips hovering strictly matching V1 Deck.GL behaviors
619
+ pathEl.addEventListener('mouseenter', (e) => {
620
+ pathEl.setAttribute('stroke-width', '6');
621
+ // Find geo coordinate exactly under cursor natively
622
+ const rect = this.svgOverlay.getBoundingClientRect();
623
+ const lngLat = this.map.unproject([e.clientX - rect.left, e.clientY - rect.top]);
624
+ const payload = {
625
+ layer: { props: { data: { route: routeDetails.route, index: routeDetails.index, routeType: routeDetails.routeType } } },
626
+ coordinate: [lngLat.lng, lngLat.lat],
627
+ color: routeDetails.linecolor
628
+ };
629
+ this.showRoutePopup(payload, e, routeDetails.isViewRoute);
630
+ });
631
+ pathEl.addEventListener('mouseleave', () => {
632
+ pathEl.setAttribute('stroke-width', '3');
633
+ const payload = { color: null, layer: { props: { data: { index: routeDetails.index, routeType: routeDetails.routeType } } } };
634
+ this.showRoutePopup(payload, null, routeDetails.isViewRoute);
635
+ });
636
+ this.updateSvgPaths(); // Force plot
637
+ }
638
+ catch (e) {
639
+ console.warn("Native overarching arc generation skipped:", e);
640
+ }
641
+ }
642
+ showRoutePopup(arcDetails, event, isViewRoute) {
643
+ const data = arcDetails.layer.props.data;
644
+ const layerId = isViewRoute
645
+ ? `line${data.index}`
646
+ : `route-layer-${data.routeType || 'jobcode'}-${data.index}`;
647
+ if (this.popup) {
648
+ this.popup.remove();
649
+ if (this.map.getLayer(layerId)) {
650
+ this.map.setPaintProperty(layerId, 'line-width', 2);
651
+ }
652
+ }
653
+ if (arcDetails.color && this.map.getLayer(layerId) && this.map.getLayoutProperty(layerId, 'visibility') !== 'none') {
654
+ this.map.setPaintProperty(layerId, 'line-width', 5);
655
+ // Removed line-opacity overwrite here because bezier lines inherit opacity and break if set raw on a gradient.
656
+ const datasetForPopup = {
657
+ coordinate: arcDetails.coordinate,
658
+ pickup: arcDetails.layer.props.data.route.pickup_location || '',
659
+ drop: arcDetails.layer.props.data.route.delivery_location || '',
660
+ jobCode: arcDetails.layer.props.data.route.project ? arcDetails.layer.props.data.route.order_number : '',
661
+ customer: arcDetails.layer.props.data.route.project ? arcDetails.layer.props.data.route.customer_name : '',
662
+ routeType: arcDetails.layer.props.data.route.project ? 'Project' : 'Route',
663
+ title: arcDetails.layer.props.data.route.project ? arcDetails.layer.props.data.route.project : arcDetails.layer.props.data.route.route_name || '',
664
+ material: arcDetails.layer.props.data.route.material || '',
665
+ type: arcDetails.layer.props.data.route.unit || '',
666
+ };
667
+ this.createPopup(datasetForPopup);
668
+ }
669
+ }
670
+ createPopup(datasetForPopup) {
671
+ if (!this.map)
672
+ return;
673
+ this.popup = new mapboxgl.Popup({
674
+ closeButton: false,
675
+ closeOnClick: false,
676
+ closeOnMove: true,
677
+ anchor: 'bottom-left',
678
+ });
679
+ // Extract location short names
680
+ const pickupDisplay = datasetForPopup.pickup?.split('|')[1] || datasetForPopup.pickup || 'Unknown Pickup';
681
+ const dropDisplay = datasetForPopup.drop?.split('|')[1] || datasetForPopup.drop || 'Unknown Delivery';
682
+ this.popup
683
+ .setLngLat(datasetForPopup.coordinate)
684
+ .setHTML(`<div class="destination p-3 bg-white dark:bg-slate-800 rounded shadow-lg text-sm text-gray-800 dark:text-gray-200">
685
+ <div class="duration space-y-1 mb-2">
686
+ <p class="pickprt"><b class="text-gray-900 dark:text-white">Pickup Location:</b> ${pickupDisplay}</p>
687
+ <p class="dropprt"><b class="text-gray-900 dark:text-white">Drop Location:</b> ${dropDisplay}</p>
688
+ </div>
689
+ ${datasetForPopup.jobCode ? `<span class="block"><b>Job Code:</b> ${datasetForPopup.jobCode}</span>` : ''}
690
+ ${datasetForPopup.customer ? `<span class="block"><b>Customer:</b> ${datasetForPopup.customer}</span>` : ''}
691
+ <span class="block"><b>${datasetForPopup.routeType} Name:</b> ${datasetForPopup.title}</span>
692
+ ${datasetForPopup.material ? `<span class="block"><b>Material:</b> ${datasetForPopup.material}</span>` : ''}
693
+ <span class="block"><b>Type:</b> ${datasetForPopup.type}</span>
694
+ </div>`)
695
+ .addTo(this.map);
696
+ }
697
+ // Draw Line
698
+ drawLine(cordinates, index, route, enablefitbound, routeType, isStyleRefresh) {
699
+ return new Promise((resolve) => {
700
+ if (!this.map || !this.isMapReady()) {
701
+ resolve();
702
+ return;
703
+ }
704
+ // Calculate bounds for this specific route
705
+ const routeBounds = new mapboxgl.LngLatBounds();
706
+ if (cordinates && cordinates.length > 0) {
707
+ cordinates.forEach((coord) => routeBounds.extend(coord));
708
+ }
709
+ const registryId = `${routeType}-${index}`;
710
+ // Register the route with its coordinates and pre-calculated bounds
711
+ this.activeRoutesRegistry.set(registryId, {
712
+ cordinates,
713
+ route,
714
+ routeType,
715
+ bounds: routeBounds,
716
+ index
717
+ });
718
+ let origin = cordinates[0];
719
+ let destination = cordinates[cordinates.length - 1];
720
+ if (origin[0] && origin[1] && destination && destination[0] && destination[1]) {
721
+ // Execute plotting logic IMMEDIATELY to trigger the map's render cycle
722
+ const linecolor = this.provideLineColor(route['unit'], routeType);
668
723
  if (enablefitbound) {
669
- const padding = {
670
- top: this.padding.top + this.customTopForCustomer,
671
- bottom: this.padding.bottom +
672
- (this.windowActualHeightWidth.availHeight -
673
- (window.innerHeight - 65)),
674
- left: this.padding.left,
675
- right: this.padding.right,
676
- };
677
- this.map.fitBounds([origin, destination], { padding, pitch: 65 }, { fitboundCompleteJob: true });
724
+ const padTop = Math.max(0, (this.padding?.top || 0) + this.customTopForCustomer + 50);
725
+ const padBot = Math.max(0, (this.padding?.bottom || 0) + (this.windowActualHeightWidth.availHeight - (window.innerHeight - 65)) + 50);
726
+ const padLeft = Math.max(0, (this.padding?.left || 0) + 350);
727
+ const padRight = Math.max(0, (this.padding?.right || 0) + 50);
728
+ const customPadding = { top: padTop, bottom: padBot, left: padLeft, right: padRight };
729
+ this.map.fitBounds([origin, destination], { padding: customPadding, pitch: 65 }, { fitboundCompleteJob: true });
678
730
  }
679
- if (this.map.getSource(`route-source-for-job-code${index}`)) {
680
- this.removeRouteAndMarker(index).then(() => { });
731
+ const sourceId = `route-source-${routeType}-${index}`;
732
+ const layerId = `route-layer-${routeType}-${index}`;
733
+ if (this.map.getSource(sourceId)) {
734
+ this.removeRouteAndMarker(index, routeType).then(() => { });
681
735
  }
682
- this.map.addSource(`route-source-for-job-code${index}`, {
736
+ this.map.addSource(sourceId, {
683
737
  type: 'geojson',
684
738
  data: {
685
739
  type: 'Feature',
@@ -688,51 +742,54 @@ class CoolmapService {
688
742
  },
689
743
  });
690
744
  this.map.addLayer({
691
- id: `route-for-job-code${index}`,
745
+ id: layerId,
692
746
  type: 'line',
693
- source: `route-source-for-job-code${index}`,
747
+ source: sourceId,
694
748
  paint: { 'line-color': linecolor, 'line-width': 2 },
695
749
  layout: { 'line-cap': 'round', 'line-join': 'round' },
696
750
  });
697
- const dataSetForMap = {
698
- origin,
699
- destination,
700
- index,
701
- linecolor,
702
- route,
703
- };
704
- this.createMarker(dataSetForMap);
705
- this.map.on('mouseenter', `route-for-job-code${index}`, (e) => {
706
- if (this.popup) {
751
+ if (!isStyleRefresh) {
752
+ const dataSetForMap = { origin, destination, index, linecolor, route, routeType };
753
+ this.createMarker(dataSetForMap);
754
+ }
755
+ this.map.on('mouseenter', layerId, (e) => {
756
+ if (this.popup)
707
757
  this.popup.remove();
708
- }
709
- this.map.setPaintProperty(`route-for-job-code${index}`, 'line-width', 5);
710
- this.map.setPaintProperty(`route-for-job-code${index}`, 'line-opacity', 1);
758
+ this.map.setPaintProperty(layerId, 'line-width', 5);
759
+ this.map.setPaintProperty(layerId, 'line-opacity', 1);
711
760
  const datasetForPopup = {
712
761
  coordinate: [e.lngLat.lng, e.lngLat.lat],
713
- pickup: route.pickup_location ? route.pickup_location : '',
714
- drop: route.delivery_location ? route.delivery_location : '',
762
+ pickup: route.pickup_location || '',
763
+ drop: route.delivery_location || '',
715
764
  jobCode: route.project ? route.order_number : null,
716
765
  customer: route.project ? route.customer_name : null,
717
766
  routeType: route.project ? 'Project' : 'Route',
718
- title: route.project
719
- ? route.project
720
- : route.route_name
721
- ? route.route_name
722
- : '',
723
- material: route.material ? route.material : '',
724
- type: route.unit ? route.unit : '',
767
+ title: route.project ? route.project : route.route_name || '',
768
+ material: route.material || '',
769
+ type: route.unit || '',
725
770
  };
726
771
  this.createPopup(datasetForPopup);
727
772
  });
728
- this.map.on('mouseleave', `route-for-job-code${index}`, (e) => {
729
- this.map.setPaintProperty(`route-for-job-code${index}`, 'line-width', 2);
730
- if (this.popup) {
773
+ this.map.on('mouseleave', layerId, () => {
774
+ this.map.setPaintProperty(layerId, 'line-width', 2);
775
+ if (this.popup)
731
776
  this.popup.remove();
732
- }
733
777
  });
734
- });
735
- }
778
+ // SAFETY: Use a timeout to ensure resolve() is always called even if 'idle' event skips
779
+ let resolved = false;
780
+ const complete = () => {
781
+ if (!resolved) {
782
+ resolved = true;
783
+ resolve();
784
+ }
785
+ };
786
+ this.map.once('idle', complete);
787
+ setTimeout(complete, 500); // 500ms safety timeout
788
+ }
789
+ else {
790
+ resolve();
791
+ }
792
+ });
736
793
  }
737
794
  provideLineColor(unitType, type) {
738
795
  let checkType = type && !['jobrouteList', 'addroute'].includes(type) ? true : false;
@@ -747,83 +804,12 @@ class CoolmapService {
747
804
  case 'Hourly':
748
805
  color = checkType ? '#ffad56' : '#ae23d1';
749
806
  break;
807
+ default:
808
+ color = '#3b82f6';
809
+ break;
750
810
  }
751
811
  return color;
752
812
  }
753
- showRoutePopup(arcDetails, event, isViewRoute) {
754
- if (this.popup) {
755
- this.popup.remove();
756
- this.map.setPaintProperty(`${isViewRoute ? 'line' : 'route-for-job-code'}${arcDetails.layer.props.data.index}`, 'line-width', 2);
757
- }
758
- if (arcDetails.color &&
759
- this.map.getLayoutProperty(arcDetails.layer.id, 'visibility') !== 'none') {
760
- this.map.setPaintProperty(`${isViewRoute ? 'line' : 'route-for-job-code'}${arcDetails.layer.props.data.index}`, 'line-width', 5);
761
- this.map.setPaintProperty(`${isViewRoute ? 'line' : 'route-for-job-code'}${arcDetails.layer.props.data.index}`, 'line-opacity', 1);
762
- const datasetForPopup = {
763
- coordinate: arcDetails.coordinate,
764
- pickup: arcDetails.layer.props.data.route.pickup_location
765
- ? arcDetails.layer.props.data.route.pickup_location
766
- : '',
767
- drop: arcDetails.layer.props.data.route.delivery_location
768
- ? arcDetails.layer.props.data.route.delivery_location
769
- : '',
770
- jobCode: arcDetails.layer.props.data.route.project
771
- ? arcDetails.layer.props.data.route.order_number
772
- : '',
773
- customer: arcDetails.layer.props.data.route.project
774
- ? arcDetails.layer.props.data.route.customer_name
775
- : '',
776
- routeType: arcDetails.layer.props.data.route.project
777
- ? 'Project'
778
- : 'Route',
779
- title: arcDetails.layer.props.data.route.project
780
- ? arcDetails.layer.props.data.route.project
781
- : arcDetails.layer.props.data.route.route_name
782
- ? arcDetails.layer.props.data.route.route_name
783
- : '',
784
- material: arcDetails.layer.props.data.route.material
785
- ? arcDetails.layer.props.data.route.material
786
- : '',
787
- type: arcDetails.layer.props.data.route.unit
788
- ? arcDetails.layer.props.data.route.unit
789
- : '',
790
- };
791
- this.createPopup(datasetForPopup);
792
- }
793
- }
794
- createPopup(datasetForPopup) {
795
- this.popup = new mapboxgl.Popup({
796
- closeButton: false,
797
- closeOnClick: false,
798
- closeOnMove: true,
799
- anchor: 'bottom-left',
800
- });
801
- this.popup
802
- .setLngLat(datasetForPopup.coordinate)
803
- .setHTML(`<div class="destination">
804
- <div class="duration">
805
- <p class="pickprt"><b>Pickup Location:</b> ${datasetForPopup.pickup?.split('|')[1] ? datasetForPopup.pickup.split('|')[1] : datasetForPopup.pickup}</p>
806
- <p class="dropprt"><b>Drop Location:</b> ${datasetForPopup.drop?.split('|')[1] ?
807
- datasetForPopup.drop.split('|')[1] :
808
- datasetForPopup.drop}</p>
809
- </div>
810
- ${datasetForPopup.jobCode
811
- ? '<span><b>Job Code:</b> ' + datasetForPopup.jobCode + '</span>'
812
- : ''}
813
- ${datasetForPopup.customer
814
- ? '<span><b>Customer:</b> ' + datasetForPopup.customer + '</span>'
815
- : ''}
816
- <span>
817
- <b>${datasetForPopup.routeType} Name:</b>
818
- ${datasetForPopup.title}
819
- </span>
820
- ${datasetForPopup.material
821
- ? '<span><b>Material:</b> ' + datasetForPopup.material + '</span>'
822
- : ''}
823
- <span><b>Type:</b> ${datasetForPopup.type}</span>
824
- </div>`)
825
- .addTo(this.map);
826
- }
827
813
  hexToRGB(hex) {
828
814
  return hex
829
815
  .replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#' + r + r + g + g + b + b)
@@ -831,105 +817,143 @@ class CoolmapService {
831
817
  .match(/.{2}/g)
832
818
  .map((x) => parseInt(x, 16));
833
819
  }
834
- createMarker(routeDetails) {
835
- if (routeDetails.origin[0] &&
836
- routeDetails.origin[1] &&
837
- routeDetails.destination[0] &&
838
- routeDetails.destination[1]) {
839
- const popup = new mapboxgl.Popup({ closeButton: false }).setHTML('<b>Pickup: </b>' + routeDetails.route?.pickup_location);
840
- const popupForDestination = new mapboxgl.Popup({
841
- closeButton: false,
842
- }).setHTML('<b>Delivery: </b>' + routeDetails.route?.delivery_location);
843
- const el = document.createElement('div');
844
- el.className = 'marker';
845
- el.innerHTML = `<span class='markerPointer' style='background:${routeDetails.linecolor}'><b>P</b><span class='markerSpan' style='border-top: 10px solid ${routeDetails.linecolor}'></span></span>`;
846
- const originMarker = new mapboxgl.Marker(el)
847
- .setPopup(popup)
848
- .setLngLat(routeDetails.origin)
849
- .addTo(this.map);
850
- originMarker
851
- .getElement()
852
- .addEventListener('mouseenter', () => originMarker.togglePopup());
853
- originMarker
854
- .getElement()
855
- .addEventListener('mouseleave', () => originMarker.togglePopup());
856
- const elementForDestination = document.createElement('div');
857
- elementForDestination.className = 'marker';
858
- elementForDestination.innerHTML = `<span class='markerPointer' style='background:${routeDetails.linecolor}'><b>D</b><span class='markerSpan' style='border-top: 10px solid ${routeDetails.linecolor}'></span></span>`;
859
- const destinationMarker = new mapboxgl.Marker(elementForDestination)
860
- .setPopup(popupForDestination)
861
- .setLngLat(routeDetails.destination)
862
- .addTo(this.map);
863
- destinationMarker
864
- .getElement()
865
- .addEventListener('mouseenter', () => destinationMarker.togglePopup());
866
- destinationMarker
867
- .getElement()
868
- .addEventListener('mouseleave', () => destinationMarker.togglePopup());
869
- this.markerOriginList[routeDetails.index] = originMarker;
870
- this.markerDestinationList[routeDetails.index] = destinationMarker;
871
- const colorArray = this.hexToRGB(routeDetails.linecolor);
872
- const arcLayer = new MapboxLayer({
873
- id: 'arc-layer' + routeDetails.index,
874
- type: ArcLayer,
875
- pickable: true,
876
- data: { route: routeDetails.route, index: routeDetails.index },
877
- getWidth: 1,
878
- getSourcePosition: routeDetails.origin,
879
- getTargetPosition: routeDetails.destination,
880
- getTargetColor: [255, 255, 255],
881
- getSourceColor: [colorArray[0], colorArray[1], colorArray[2]],
882
- onHover: (info, event) => this.showRoutePopup(info, event, routeDetails.isViewRoute),
883
- });
884
- this.map.addLayer(arcLayer);
820
+ // --- SVG 3D Engine Methods ---
821
+ initSvgOverlay() {
822
+ if (this.svgOverlay || !this.mapContainer)
823
+ return;
824
+ this.svgOverlay = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
825
+ this.svgOverlay.style.position = 'absolute';
826
+ this.svgOverlay.style.top = '0';
827
+ this.svgOverlay.style.left = '0';
828
+ this.svgOverlay.style.width = '100%';
829
+ this.svgOverlay.style.height = '100%';
830
+ this.svgOverlay.style.pointerEvents = 'none';
831
+ this.svgOverlay.style.zIndex = '10';
832
+ const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
833
+ this.svgOverlay.appendChild(defs);
834
+ this.mapContainer.appendChild(this.svgOverlay);
835
+ this.map.on('render', () => this.updateSvgPaths());
836
+ }
837
+ updateSvgPaths() {
838
+ if (!this.svgOverlay || !this.map)
839
+ return;
840
+ for (const key in this.activeArcs) {
841
+ const arc = this.activeArcs[key];
842
+ const p1 = this.map.project(arc.origin);
843
+ const p2 = this.map.project(arc.destination);
844
+ // Safety Guard: Check for Infinity and extreme numbers (Number.MAX_VALUE)
845
+ // which Mapbox can return during transitions.
846
+ // 1e15 is a safe threshold for JS math and SVG rendering engines.
847
+ const isUsable = (p) => Number.isFinite(p.x) && Math.abs(p.x) < 1e15 &&
848
+ Number.isFinite(p.y) && Math.abs(p.y) < 1e15;
849
+ if (!isUsable(p1) || !isUsable(p2))
850
+ continue;
851
+ const dx = p2.x - p1.x;
852
+ const dy = p2.y - p1.y;
853
+ const dist = Math.sqrt(dx * dx + dy * dy);
854
+ // Secondary check for distance finiteness
855
+ if (!Number.isFinite(dist) || dist > 1e15)
856
+ continue;
857
+ const mx = (p1.x + p2.x) / 2;
858
+ let controlY = ((p1.y + p2.y) / 2) - (dist * 1.05);
859
+ // Final sanity check for calculated curve peaks
860
+ controlY = Math.max(100, controlY);
861
+ if (!Number.isFinite(controlY) || !Number.isFinite(mx))
862
+ continue;
863
+ // Update with rounded values to ensure clean and valid attribute data
864
+ arc.pathEl.setAttribute('d', `M ${p1.x.toFixed(2)} ${p1.y.toFixed(2)} Q ${mx.toFixed(2)} ${controlY.toFixed(2)} ${p2.x.toFixed(2)} ${p2.y.toFixed(2)}`);
865
+ // Sync the gradient positioning physically mapped to coordinate space
866
+ const grad = this.svgOverlay.querySelector(`#arc-grad-${key}`);
867
+ if (grad) {
868
+ const minX = Math.min(p1.x, p2.x);
869
+ const maxX = Math.max(p1.x, p2.x);
870
+ const minY = Math.min(p1.y, p2.y, controlY);
871
+ const maxY = Math.max(p1.y, p2.y, controlY);
872
+ const w = Math.max(1, maxX - minX);
873
+ const h = Math.max(1, maxY - minY);
874
+ grad.setAttribute('x1', `${((p1.x - minX) / w) * 100}%`);
875
+ grad.setAttribute('y1', `${((p1.y - minY) / h) * 100}%`);
876
+ grad.setAttribute('x2', `${((p2.x - minX) / w) * 100}%`);
877
+ grad.setAttribute('y2', `${((p2.y - minY) / h) * 100}%`);
878
+ }
885
879
  }
886
880
  }
887
- async removeRouteAndMarker(index) {
888
- if (this.map) {
889
- this.map.getLayer(`arc-layer${index}`)
890
- ? this.map.removeLayer(`arc-layer${index}`)
891
- : '';
892
- this.map.getLayer(`line${index}`)
893
- ? this.map.removeLayer(`line${index}`)
894
- : '';
895
- this.map.getLayer(`custom_layer${index}`)
896
- ? this.map.removeLayer(`custom_layer${index}`)
897
- : '';
898
- this.map.getSource(`line${index}`)
899
- ? this.map.removeSource(`line${index}`)
900
- : '';
901
- this.map.getLayer(`route-for-job-code${index}`)
902
- ? this.map.removeLayer(`route-for-job-code${index}`)
903
- : '';
904
- this.map.getSource(`route-source-for-job-code${index}`)
905
- ? this.map.removeSource(`route-source-for-job-code${index}`)
906
- : '';
907
- this.findMarkerBound(index);
908
- this.markerOriginList[index] ? this.markerOriginList[index].remove() : '';
909
- this.markerDestinationList[index]
910
- ? this.markerDestinationList[index].remove()
911
- : '';
912
- await true;
881
+ async removeRouteAndMarker(index, routeType = 'jobcode') {
882
+ const registryId = `${routeType}-${index}`;
883
+ // Clean up registry
884
+ if (this.activeRoutesRegistry.has(registryId)) {
885
+ this.activeRoutesRegistry.delete(registryId);
913
886
  }
887
+ if (!this.map)
888
+ return true;
889
+ try {
890
+ if (this.activeArcs[registryId]) {
891
+ const arc = this.activeArcs[registryId];
892
+ if (arc.pathEl.parentNode)
893
+ arc.pathEl.parentNode.removeChild(arc.pathEl);
894
+ delete this.activeArcs[registryId];
895
+ }
896
+ if (this.svgOverlay) {
897
+ const grad = this.svgOverlay.querySelector(`defs #arc-grad-${registryId}`);
898
+ if (grad && grad.parentNode)
899
+ grad.parentNode.removeChild(grad);
900
+ }
901
+ const layerId = `route-layer-${routeType}-${index}`;
902
+ const sourceId = `route-source-${routeType}-${index}`;
903
+ this.map.getLayer(layerId) ? this.map.removeLayer(layerId) : null;
904
+ this.map.getLayer(`line${index}`) ? this.map.removeLayer(`line${index}`) : null;
905
+ this.map.getSource(sourceId) ? this.map.removeSource(sourceId) : null;
906
+ this.map.getSource(`line${index}`) ? this.map.removeSource(`line${index}`) : null;
907
+ this.markerOriginList.get(registryId)?.remove();
908
+ this.markerOriginList.delete(registryId);
909
+ this.markerDestinationList.get(registryId)?.remove();
910
+ this.markerDestinationList.delete(registryId);
911
+ this.markerOriginList.get(String(index))?.remove();
912
+ this.markerDestinationList.get(String(index))?.remove();
913
+ if (this.popup)
914
+ this.popup.remove();
915
+ return true;
916
+ }
917
+ catch (e) {
918
+ console.warn('Error removing route/marker:', e);
919
+ return false;
920
+ }
921
+ }
922
+ reAddLayers() {
923
+ this.activeRoutesRegistry.forEach((data, regId) => {
924
+ // Use the stored raw index instead of the composite registry key
925
+ this.drawLine(data.cordinates, data.index, data.route, false, data.routeType, true);
926
+ });
914
927
  }
915
928
  findMarkerBound(index) {
916
- const indexOfCordinates = this.originDestinationCordinates.findIndex((x) => x[0][0].toFixed(6) ==
917
- this.markerOriginList[index]?.getLngLat()?.lng.toFixed(6) &&
918
- x[0][1].toFixed(6) ==
919
- this.markerOriginList[index]?.getLngLat()?.lat.toFixed(6));
920
- if (indexOfCordinates >= 0) {
921
- this.originDestinationCordinates.splice(indexOfCordinates, 1);
929
+ const id = String(index);
930
+ const originMarker = this.markerOriginList.get(id);
931
+ const originLng = originMarker?.getLngLat()?.lng;
932
+ const originLat = originMarker?.getLngLat()?.lat;
933
+ if (originLng !== undefined && originLat !== undefined) {
934
+ const indexOfCordinates = this.originDestinationCordinates.findIndex((x) => x[0][0].toFixed(6) === originLng.toFixed(6) &&
935
+ x[0][1].toFixed(6) === originLat.toFixed(6));
936
+ if (indexOfCordinates >= 0) {
937
+ this.originDestinationCordinates.splice(indexOfCordinates, 1);
938
+ }
922
939
  }
923
940
  }
924
941
  async filterRoute(ID, visibility, showAllFitbound) {
925
- if (ID) {
942
+ if (ID && this.map) {
926
943
  if (this.map.getLayer(`route-for-job-code${ID}`)) {
927
944
  this.map.setLayoutProperty(`route-for-job-code${ID}`, 'visibility', visibility);
928
- const originM = this.markerOriginList[ID].getElement();
929
- originM.style.display = visibility === 'visible' ? 'block' : visibility;
930
- const destinationM = this.markerDestinationList[ID].getElement();
931
- destinationM.style.display =
932
- visibility === 'visible' ? 'block' : visibility;
945
+ const id = String(ID);
946
+ const originM = this.markerOriginList.get(id);
947
+ if (originM) {
948
+ originM.getElement().style.display = visibility === 'visible' ? 'block' : 'none';
949
+ }
950
+ const destinationM = this.markerDestinationList.get(id);
951
+ if (destinationM) {
952
+ destinationM.getElement().style.display = visibility === 'visible' ? 'block' : 'none';
953
+ }
954
+ if (this.activeArcs[ID]) {
955
+ this.activeArcs[ID].pathEl.style.display = visibility === 'visible' ? 'block' : 'none';
956
+ }
933
957
  if (visibility === 'none' && showAllFitbound) {
934
958
  this.findMarkerBound(ID);
935
959
  this.extendReBound();
@@ -939,36 +963,32 @@ class CoolmapService {
939
963
  this.map.setLayoutProperty(`arc-layer${ID}`, 'visibility', visibility);
940
964
  }
941
965
  }
942
- await true;
966
+ return true;
943
967
  }
944
968
  extendBound(route, showAllFitbound) {
945
- return new Promise((resolve, reject) => {
946
- if (route) {
969
+ return new Promise((resolve) => {
970
+ if (route && this.map) {
947
971
  if (typeof route === 'string') {
948
972
  let path = route.split(';');
949
- path = path.map((ele) => {
950
- return (ele = this.formateLatLong(ele));
951
- });
952
- path.forEach((ele, index) => {
953
- if (ele.length === 1)
954
- path.splice(index, 1);
955
- });
973
+ path = path.map((ele) => this.formateLatLong(ele));
974
+ path = path.filter((ele) => ele && ele.length > 1);
956
975
  route = path;
957
976
  }
958
- if (route[0][0] &&
959
- route[0][1] &&
960
- route[route.length - 1][0] &&
961
- route[route.length - 1][1]) {
977
+ if (route[0] && route[0][0] && route[0][1] && route[route.length - 1] && route[route.length - 1][0] && route[route.length - 1][1]) {
962
978
  this.originDestinationCordinates.push(route);
963
- route.map((item) => {
964
- this.bounds.extend(item);
979
+ this.bounds = new mapboxgl.LngLatBounds();
980
+ this.originDestinationCordinates.forEach((routeItem) => {
981
+ routeItem.forEach((coord) => {
982
+ this.bounds.extend(coord);
983
+ });
965
984
  });
966
985
  }
967
986
  }
968
- if (showAllFitbound) {
987
+ if (showAllFitbound && this.map) {
969
988
  setTimeout(() => {
970
- if (showAllFitbound && Object.keys(this.bounds).length > 0)
971
- this.map.fitBounds(this.bounds, { pitch: 65 }, { fitboundComplete: true });
989
+ if (showAllFitbound && !this.bounds.isEmpty()) {
990
+ this.map.fitBounds(this.bounds, { pitch: 65, maxZoom: 14, padding: 90 }, { fitboundComplete: true });
991
+ }
972
992
  }, 100);
973
993
  this.map.once('moveend', (event) => {
974
994
  if (event.fitboundComplete) {
@@ -976,28 +996,32 @@ class CoolmapService {
976
996
  }
977
997
  });
978
998
  }
999
+ else {
1000
+ resolve(true);
1001
+ }
979
1002
  });
980
1003
  }
981
1004
  extendReBound(bottom) {
982
- return new Promise((resolve, reject) => {
1005
+ return new Promise((resolve) => {
983
1006
  this.bounds = new mapboxgl.LngLatBounds();
984
- if (this.originDestinationCordinates.length >= 0) {
985
- this.originDestinationCordinates.map((item, index) => {
986
- item.map((route) => {
1007
+ if (this.originDestinationCordinates.length > 0) {
1008
+ this.originDestinationCordinates.forEach((item, index) => {
1009
+ item.forEach((route) => {
987
1010
  this.bounds.extend(route);
988
1011
  });
989
1012
  if (index === this.originDestinationCordinates.length - 1) {
990
- const padding = {
991
- top: this.padding.top + this.customTopForCustomer,
992
- bottom: this.padding.bottom +
993
- (this.windowActualHeightWidth.availHeight -
994
- (window.innerHeight - 65)),
995
- left: this.padding.left,
996
- right: this.padding.right,
1013
+ const padTop = Math.max(0, (this.padding?.top || 0) + this.customTopForCustomer + 80);
1014
+ const padBot = Math.max(0, (this.padding?.bottom || 0) + (this.windowActualHeightWidth.availHeight - (window.innerHeight - 65)) + 80);
1015
+ const padLeft = Math.max(0, (this.padding?.left || 0) + 350);
1016
+ const padRight = Math.max(0, (this.padding?.right || 0) + 80);
1017
+ const customPadding = {
1018
+ top: padTop, bottom: padBot,
1019
+ left: padLeft, right: padRight,
997
1020
  };
998
1021
  setTimeout(() => {
999
- if (this.originDestinationCordinates.length > 0)
1000
- this.map.fitBounds(this.bounds, { padding, pitch: 65 });
1022
+ if (this.originDestinationCordinates.length > 0 && !this.bounds.isEmpty() && this.map) {
1023
+ this.map.fitBounds(this.bounds, { padding: customPadding, pitch: 65, maxZoom: 14 });
1024
+ }
1001
1025
  }, 500);
1002
1026
  resolve(true);
1003
1027
  }
@@ -1008,52 +1032,209 @@ class CoolmapService {
1008
1032
  }
1009
1033
  });
1010
1034
  }
1011
- plotRoute(route, i, type, enablefitbound, showAllFitbound) {
1035
+ async plotRoute(route, i, type, enablefitbound, showAllFitbound) {
1036
+ const cacheKey = route['job_id'] || route['route_id'];
1037
+ // 1. Check cache FIRST
1038
+ if (this.pathCache.has(cacheKey)) {
1039
+ const path = this.pathCache.get(cacheKey);
1040
+ route['path'] = path;
1041
+ this.extendBound(path, showAllFitbound);
1042
+ await this.drawLine(path, i, route, enablefitbound, type);
1043
+ route['index'] = i;
1044
+ return true;
1045
+ }
1046
+ // 2. Check for inline path (Avoid API if already available)
1047
+ if (route['path'] && (Array.isArray(route['path']) || typeof route['path'] === 'string')) {
1048
+ let path = typeof route['path'] === 'string' ? route['path'].split(';') : route['path'];
1049
+ if (Array.isArray(path) && path.length > 0) {
1050
+ // Format if they are still strings
1051
+ if (typeof path[0] === 'string') {
1052
+ path = path.map((ele) => this.formateLatLong(ele));
1053
+ path = path.filter((ele) => ele && ele.length > 1);
1054
+ }
1055
+ if (path.length > 0) {
1056
+ this.pathCache.set(cacheKey, path);
1057
+ route['path'] = path;
1058
+ this.extendBound(path, showAllFitbound);
1059
+ await this.drawLine(path, i, route, enablefitbound, type);
1060
+ route['index'] = i;
1061
+ return true;
1062
+ }
1063
+ }
1064
+ }
1065
+ // 3. Fallback to API call ONLY for jobcode (if path was missing)
1012
1066
  return new Promise((resolve, reject) => {
1013
- let param = {};
1014
- if (['jobcode'].includes(type)) {
1015
- param['job'] = route['job_id'];
1016
- this.utils.postDataWithRestUrl('schedule/job/path', param).subscribe((res) => {
1017
- if (res['data']['route']) {
1018
- let path = res['data']['route'].split(';');
1019
- path = path.map((ele) => {
1020
- return (ele = this.formateLatLong(ele));
1021
- });
1022
- path.forEach((ele, index) => {
1023
- if (ele.length === 1)
1024
- path.splice(index, 1);
1025
- });
1026
- route['path'] = path;
1027
- this.extendBound(route['path'], showAllFitbound);
1028
- if (route['path'] && route['path'].length > 0)
1029
- this.drawLine(route['path'], i, route, enablefitbound, type);
1030
- route['index'] = i;
1031
- }
1032
- else {
1033
- this.extendBound(null, showAllFitbound);
1034
- }
1035
- resolve(true);
1036
- }, (err) => {
1037
- if (err) {
1067
+ if (['jobcode'].includes(type) || route['job_id']) {
1068
+ let param = { job: route['job_id'] };
1069
+ this.utils.postDataWithRestUrl('schedule/job/path', param).subscribe({
1070
+ next: async (res) => {
1071
+ let path = [];
1072
+ if (res['data'] && res['data']['route']) {
1073
+ path = res['data']['route'].split(';');
1074
+ path = path.map((ele) => this.formateLatLong(ele));
1075
+ path = path.filter((ele) => ele && ele.length > 1);
1076
+ this.pathCache.set(cacheKey, path);
1077
+ }
1078
+ if (path && path.length > 0) {
1079
+ route['path'] = path;
1080
+ this.extendBound(route['path'], showAllFitbound);
1081
+ await this.drawLine(route['path'], i, route, enablefitbound, type);
1082
+ route['index'] = i;
1083
+ }
1084
+ else {
1085
+ this.extendBound(null, showAllFitbound);
1086
+ }
1087
+ resolve(true);
1088
+ },
1089
+ error: (err) => {
1090
+ console.warn('Failed path retrieval', err);
1038
1091
  reject(false);
1039
1092
  }
1040
1093
  });
1041
1094
  }
1042
- else if (['jobrouteList', 'addroute'].includes(type)) {
1043
- if (route['path'] && route['path'].length > 0) {
1044
- let path = route['path'].split(';');
1045
- path = path.map((ele) => {
1046
- return (ele = this.formateLatLong(ele));
1047
- });
1048
- path.forEach((ele, index) => {
1049
- if (ele.length === 1)
1050
- path.splice(index, 1);
1051
- });
1052
- this.extendBound(path, showAllFitbound);
1053
- this.drawLine(path, i, route, enablefitbound, type);
1095
+ else {
1096
+ this.extendBound(null, showAllFitbound);
1097
+ resolve(true);
1098
+ }
1099
+ });
1100
+ }
1101
+ /**
1102
+ * Batch toggle visibility for routes on the map.
1103
+ * If visibleIds is empty, shows everything in the registry.
1104
+ * Otherwise, shows only those in visibleIds and hides the rest.
1105
+ */
1106
+ setRoutesVisibility(visibleIds, showAllOverride, prefix) {
1107
+ const showAll = showAllOverride !== undefined ? showAllOverride : (visibleIds.length === 0);
1108
+ this.activeRoutesRegistry.forEach((data, id) => {
1109
+ const registryId = String(id).trim();
1110
+ // If a prefix is provided, only operate on routes matching that prefix.
1111
+ if (prefix && !registryId.startsWith(`${prefix}-`)) {
1112
+ return;
1113
+ }
1114
+ const isVisible = showAll || visibleIds.includes(registryId);
1115
+ const visibility = isVisible ? 'visible' : 'none';
1116
+ const display = isVisible ? 'block' : 'none';
1117
+ // Toggle New Layer Format
1118
+ const newLayerId = `route-layer-${data.routeType}-${data.index || id}`;
1119
+ if (this.map.getLayer(newLayerId)) {
1120
+ this.map.setLayoutProperty(newLayerId, 'visibility', visibility);
1121
+ }
1122
+ // Legacy/Alternate Layer Formats
1123
+ const legacyId = `route-for-job-code${id}`;
1124
+ if (this.map.getLayer(legacyId)) {
1125
+ this.map.setLayoutProperty(legacyId, 'visibility', visibility);
1126
+ }
1127
+ if (this.map.getLayer(`line${id}`)) {
1128
+ this.map.setLayoutProperty(`line${id}`, 'visibility', visibility);
1129
+ }
1130
+ // Toggle Markers
1131
+ const originM = this.markerOriginList.get(registryId);
1132
+ if (originM)
1133
+ originM.getElement().style.display = display;
1134
+ const destM = this.markerDestinationList.get(registryId);
1135
+ if (destM)
1136
+ destM.getElement().style.display = display;
1137
+ // Toggle Arcs
1138
+ if (this.activeArcs[registryId]) {
1139
+ this.activeArcs[registryId].pathEl.style.display = display;
1140
+ }
1141
+ });
1142
+ // Recalculate bounds based on visible routes
1143
+ this.recalculateVisibleBounds(visibleIds);
1144
+ }
1145
+ recalculateVisibleBounds(visibleIds) {
1146
+ const showAll = visibleIds.length === 0;
1147
+ this.bounds = new mapboxgl.LngLatBounds();
1148
+ this.originDestinationCordinates = [];
1149
+ const normalizedVisibleIds = visibleIds.map(vid => String(vid).trim());
1150
+ this.activeRoutesRegistry.forEach((data, id) => {
1151
+ const registryId = String(id).trim();
1152
+ if (showAll || normalizedVisibleIds.includes(registryId)) {
1153
+ if (data.bounds && !data.bounds.isEmpty()) {
1154
+ this.bounds.extend(data.bounds);
1155
+ this.originDestinationCordinates.push(data.cordinates);
1156
+ }
1157
+ else if (data.cordinates && data.cordinates.length > 0) {
1158
+ // Fallback if bounds weren't pre-calculated
1159
+ data.cordinates.forEach((coord) => this.bounds.extend(coord));
1160
+ this.originDestinationCordinates.push(data.cordinates);
1054
1161
  }
1055
1162
  }
1056
1163
  });
1164
+ if (!this.bounds.isEmpty() && this.map) {
1165
+ const canvas = this.map.getCanvas();
1166
+ const mapW = canvas.clientWidth;
1167
+ const mapH = canvas.clientHeight;
1168
+ let padTop = Math.max(0, (Number(this.padding?.top) || 0) + (Number(this.customTopForCustomer) || 0) + 80);
1169
+ let padBot = Math.max(0, (Number(this.padding?.bottom) || 0) + (Math.max(0, Number(this.windowActualHeightWidth.availHeight) - (Number(window.innerHeight) - 65))) + 80);
1170
+ let padLeft = Math.max(0, (Number(this.padding?.left) || 0) + (window.innerWidth > 1000 ? 350 : 50));
1171
+ let padRight = Math.max(0, (Number(this.padding?.right) || 0) + 80);
1172
+ // Safety Clamps: Ensure padding doesn't exceed canvas size
1173
+ const maxHorizPad = mapW * 0.8;
1174
+ const maxVertPad = mapH * 0.8;
1175
+ if (padLeft + padRight > maxHorizPad) {
1176
+ const factor = maxHorizPad / (padLeft + padRight);
1177
+ padLeft *= factor;
1178
+ padRight *= factor;
1179
+ }
1180
+ if (padTop + padBot > maxVertPad) {
1181
+ const factor = maxVertPad / (padTop + padBot);
1182
+ padTop *= factor;
1183
+ padBot *= factor;
1184
+ }
1185
+ try {
1186
+ this.map.fitBounds(this.bounds, {
1187
+ padding: { top: padTop, bottom: padBot, left: padLeft, right: padRight },
1188
+ pitch: 65,
1189
+ maxZoom: 12,
1190
+ duration: 800
1191
+ });
1192
+ }
1193
+ catch (err) {
1194
+ console.warn('fitBounds failed, trying with default padding', err);
1195
+ this.map.fitBounds(this.bounds, { padding: 50, pitch: 65, maxZoom: 16 });
1196
+ }
1197
+ }
1198
+ }
1199
+ async clearAllRoutes() {
1200
+ // 1. Remove all markers
1201
+ this.markerOriginList.forEach(marker => marker.remove());
1202
+ this.markerDestinationList.forEach(marker => marker.remove());
1203
+ this.markerOriginList.clear();
1204
+ this.markerDestinationList.clear();
1205
+ // 2. Remove all arcs
1206
+ Object.keys(this.activeArcs).forEach(index => {
1207
+ const arc = this.activeArcs[index];
1208
+ if (arc.pathEl.parentNode)
1209
+ arc.pathEl.parentNode.removeChild(arc.pathEl);
1210
+ if (this.svgOverlay) {
1211
+ const grad = this.svgOverlay.querySelector(`defs #arc-grad-${index}`);
1212
+ if (grad && grad.parentNode)
1213
+ grad.parentNode.removeChild(grad);
1214
+ }
1215
+ });
1216
+ this.activeArcs = {};
1217
+ // 3. Remove all line layers and sources using registry metadata
1218
+ this.activeRoutesRegistry.forEach((data, regId) => {
1219
+ const type = data.routeType || 'jobcode';
1220
+ const idx = data.index || regId.split('-').pop();
1221
+ const layerId = `route-layer-${type}-${idx}`;
1222
+ const sourceId = `route-source-${type}-${idx}`;
1223
+ this.map.getLayer(layerId) ? this.map.removeLayer(layerId) : null;
1224
+ this.map.getSource(sourceId) ? this.map.removeSource(sourceId) : null;
1225
+ // Legacy fallback cleanup
1226
+ this.map.getLayer(`line${idx}`) ? this.map.removeLayer(`line${idx}`) : null;
1227
+ this.map.getSource(`line${idx}`) ? this.map.removeSource(`line${idx}`) : null;
1228
+ this.map.getLayer(`route-for-job-code${idx}`) ? this.map.removeLayer(`route-for-job-code${idx}`) : null;
1229
+ this.map.getSource(`route-source-for-job-code${idx}`) ? this.map.removeSource(`route-source-for-job-code${idx}`) : null;
1230
+ });
1231
+ // 4. Reset registries and bounds
1232
+ this.activeRoutesRegistry.clear();
1233
+ this.originDestinationCordinates = [];
1234
+ this.bounds = new mapboxgl.LngLatBounds();
1235
+ if (this.popup)
1236
+ this.popup.remove();
1237
+ return true;
1057
1238
  }
1058
1239
  clearBound() {
1059
1240
  this.bounds = new mapboxgl.LngLatBounds();
@@ -1061,32 +1242,24 @@ class CoolmapService {
1061
1242
  this.clearPadding();
1062
1243
  }
1063
1244
  formateLatLong(latlong) {
1064
- return latlong
1065
- ? latlong
1066
- .split(',')
1067
- .map((x) => +x)
1068
- .reverse()
1069
- : null;
1245
+ return latlong ? latlong.split(',').map((x) => +x).reverse() : null;
1070
1246
  }
1071
1247
  clearBoundWithCordinates() {
1072
1248
  this.bounds = new mapboxgl.LngLatBounds();
1073
1249
  this.originDestinationCordinates = [];
1074
1250
  }
1075
1251
  onResize(event) {
1076
- if (!this.bounds.isEmpty()) {
1252
+ if (!this.bounds.isEmpty() && this.map) {
1077
1253
  this.windowActualHeightWidth.availHeight =
1078
- window.innerHeight > window.screen.availHeight
1079
- ? window.innerHeight
1080
- : window.screen.availHeight;
1254
+ window.innerHeight > window.screen.availHeight ? window.innerHeight : window.screen.availHeight;
1081
1255
  setTimeout(() => {
1256
+ const hRes = event.target ? event.target.innerHeight : window.innerHeight;
1082
1257
  this.map.fitBounds(this.bounds, {
1083
1258
  padding: {
1084
- top: this.padding.top + this.customTopForCustomer,
1085
- bottom: this.padding.bottom +
1086
- (this.windowActualHeightWidth.availHeight -
1087
- (event.target.innerHeight - 65)),
1088
- left: this.padding.left,
1089
- right: this.padding.right,
1259
+ top: (this.padding?.top || 0) + this.customTopForCustomer,
1260
+ bottom: (this.padding?.bottom || 0) + (this.windowActualHeightWidth.availHeight - (hRes - 65)),
1261
+ left: (this.padding?.left || 0),
1262
+ right: (this.padding?.right || 0),
1090
1263
  },
1091
1264
  pitch: 65,
1092
1265
  });
@@ -1103,7 +1276,7 @@ class CoolmapService {
1103
1276
  this.padding = null;
1104
1277
  }
1105
1278
  removeJobFromMap(data) {
1106
- data.map((ele, index) => {
1279
+ data.forEach((ele, index) => {
1107
1280
  const id = ele['job_id'] ? ele['job_id'] : ele['route_id'];
1108
1281
  this.removeRouteAndMarker(id);
1109
1282
  if (index === data.length - 1) {
@@ -1111,24 +1284,56 @@ class CoolmapService {
1111
1284
  }
1112
1285
  });
1113
1286
  }
1114
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: CoolmapService, deps: [{ token: UtilsService }, { token: i2$1.EventManager }, { token: 'memberData' }], target: i0.ɵɵFactoryTarget.Injectable });
1115
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: CoolmapService, providedIn: 'root' });
1287
+ resize() {
1288
+ if (this.map) {
1289
+ setTimeout(() => this.map.resize(), 0);
1290
+ }
1291
+ }
1292
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MapboxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1293
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MapboxService, providedIn: 'root' });
1116
1294
  }
1117
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: CoolmapService, decorators: [{
1295
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MapboxService, decorators: [{
1118
1296
  type: Injectable,
1119
- args: [{ providedIn: 'root' }]
1120
- }], ctorParameters: () => [{ type: UtilsService }, { type: i2$1.EventManager }, { type: CoolmapConfigModel, decorators: [{
1121
- type: Inject,
1122
- args: ['memberData']
1123
- }] }] });
1297
+ args: [{
1298
+ providedIn: 'root'
1299
+ }]
1300
+ }], ctorParameters: () => [] });
1301
+
1302
+ class CoolmapService {
1303
+ mapbox = inject(MapboxService);
1304
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1305
+ plottingIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "plottingIds" }] : []));
1306
+ plotRoute(route, i, type = 'jobcode', enablefitbound, showAllFitbound) {
1307
+ if (!route)
1308
+ return;
1309
+ return this.mapbox.plotRoute(route, i, type, enablefitbound, showAllFitbound);
1310
+ }
1311
+ loadMapProperty(pinRouteGeojson, index, unit, routeProps) {
1312
+ this.mapbox.loadMapProperty(pinRouteGeojson, index, unit, routeProps);
1313
+ }
1314
+ removeRouteAndMarker(index, type = 'jobcode') {
1315
+ return this.mapbox.removeRouteAndMarker(index, type);
1316
+ }
1317
+ clearAllRoutes() {
1318
+ return this.mapbox.clearAllRoutes();
1319
+ }
1320
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1321
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapService, providedIn: 'root' });
1322
+ }
1323
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapService, decorators: [{
1324
+ type: Injectable,
1325
+ args: [{
1326
+ providedIn: 'root'
1327
+ }]
1328
+ }] });
1124
1329
 
1125
1330
  /*
1126
- * Public API Surface of @aggdirect/coolmap-services
1331
+ * Public API Surface of coolmap-services
1127
1332
  */
1128
1333
 
1129
1334
  /**
1130
1335
  * Generated bundle index. Do not edit.
1131
1336
  */
1132
1337
 
1133
- export { CoolmapConfigModel, CoolmapService, DriverSmsCardEnum, DriversmsCardKey, EstinationData, EstinationEnum, JobCodeOverviewData, JobCodeOverviewEnum, PopupData, Route, UtilsService };
1338
+ export { COOLMAP_CONFIG, CoolmapService, CoolmapServices, MapboxService, UtilsService, provideCoolmapConfig };
1134
1339
  //# sourceMappingURL=aggdirect-coolmap-services.mjs.map