@aggdirect/coolmap 3.0.2 → 5.0.0

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 (46) hide show
  1. package/README.md +39 -33
  2. package/fesm2022/coolmap.mjs +1436 -0
  3. package/fesm2022/coolmap.mjs.map +1 -0
  4. package/index.d.ts +101 -5
  5. package/package.json +12 -32
  6. package/esm2022/aggdirect-coolmap.mjs +0 -5
  7. package/esm2022/lib/component/add-route/add-route.component.mjs +0 -433
  8. package/esm2022/lib/component/job-code/job-code.component.mjs +0 -181
  9. package/esm2022/lib/component/map/map.component.mjs +0 -39
  10. package/esm2022/lib/component/nav/layout/add-route-nav/add-route-nav.component.mjs +0 -151
  11. package/esm2022/lib/component/nav/layout/job-code-nav/job-code-nav.component.mjs +0 -194
  12. package/esm2022/lib/component/nav/nav.component.mjs +0 -54
  13. package/esm2022/lib/component/route-jobcode-list/job-code-list-card/job-code-list-card.component.mjs +0 -77
  14. package/esm2022/lib/component/route-jobcode-list/route-jobcode-list.component.mjs +0 -319
  15. package/esm2022/lib/component/route-jobcode-list/sms-card-details-overview/sms-card-details-overview.component.mjs +0 -19
  16. package/esm2022/lib/component/route-jobcode-list/view-route-list-card/view-route-list-card.component.mjs +0 -37
  17. package/esm2022/lib/component/shared/dialog/dialog.component.mjs +0 -31
  18. package/esm2022/lib/component/shared/estimation-display/estimation-display.component.mjs +0 -18
  19. package/esm2022/lib/component/shared/job-route-list/job-route-list.component.mjs +0 -79
  20. package/esm2022/lib/component/shared/jobcode-overview/jobcode-overview.component.mjs +0 -24
  21. package/esm2022/lib/component/shared/jobcode-status/jobcode-status.component.mjs +0 -15
  22. package/esm2022/lib/component/shared/route-info-card/route-info-card.component.mjs +0 -62
  23. package/esm2022/lib/component/shared/searchoptiongroup/searchoptiongroup.component.mjs +0 -25
  24. package/esm2022/lib/coolmap.module.mjs +0 -193
  25. package/esm2022/public-api.mjs +0 -19
  26. package/fesm2022/aggdirect-coolmap.mjs +0 -1822
  27. package/fesm2022/aggdirect-coolmap.mjs.map +0 -1
  28. package/lib/component/add-route/add-route.component.d.ts +0 -73
  29. package/lib/component/job-code/job-code.component.d.ts +0 -41
  30. package/lib/component/map/map.component.d.ts +0 -15
  31. package/lib/component/nav/layout/add-route-nav/add-route-nav.component.d.ts +0 -49
  32. package/lib/component/nav/layout/job-code-nav/job-code-nav.component.d.ts +0 -48
  33. package/lib/component/nav/nav.component.d.ts +0 -31
  34. package/lib/component/route-jobcode-list/job-code-list-card/job-code-list-card.component.d.ts +0 -20
  35. package/lib/component/route-jobcode-list/route-jobcode-list.component.d.ts +0 -42
  36. package/lib/component/route-jobcode-list/sms-card-details-overview/sms-card-details-overview.component.d.ts +0 -9
  37. package/lib/component/route-jobcode-list/view-route-list-card/view-route-list-card.component.d.ts +0 -12
  38. package/lib/component/shared/dialog/dialog.component.d.ts +0 -13
  39. package/lib/component/shared/estimation-display/estimation-display.component.d.ts +0 -8
  40. package/lib/component/shared/job-route-list/job-route-list.component.d.ts +0 -26
  41. package/lib/component/shared/jobcode-overview/jobcode-overview.component.d.ts +0 -10
  42. package/lib/component/shared/jobcode-status/jobcode-status.component.d.ts +0 -7
  43. package/lib/component/shared/route-info-card/route-info-card.component.d.ts +0 -32
  44. package/lib/component/shared/searchoptiongroup/searchoptiongroup.component.d.ts +0 -14
  45. package/lib/coolmap.module.d.ts +0 -46
  46. package/public-api.d.ts +0 -15
@@ -0,0 +1,1436 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, input, EventEmitter, Output, signal, HostListener, inject, Input, computed, DestroyRef, ChangeDetectorRef, viewChild, effect, CUSTOM_ELEMENTS_SCHEMA, ViewChild } from '@angular/core';
3
+ import * as i2$2 from '@angular/forms';
4
+ import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
5
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
+ import { COOLMAP_CONFIG, UtilsService, CoolmapService, MapboxService } from 'coolmap-services';
7
+ import * as i2 from '@angular/common';
8
+ import { NgClass, CommonModule, CurrencyPipe } from '@angular/common';
9
+ import * as i1 from 'lucide-angular';
10
+ import { X, LucideAngularModule, LoaderCircle, Info, CarFront, CheckCircle, Share2, ChevronLeft, ChevronRight, Check, ChevronDown, Search, Route } from 'lucide-angular';
11
+ import * as i2$1 from '@angular/cdk/scrolling';
12
+ import { ScrollingModule } from '@angular/cdk/scrolling';
13
+ import { pairwise, filter } from 'rxjs';
14
+
15
+ class Coolmap {
16
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: Coolmap, deps: [], target: i0.ɵɵFactoryTarget.Component });
17
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: Coolmap, isStandalone: true, selector: "lib-coolmap", ngImport: i0, template: ``, isInline: true, styles: [""] });
18
+ }
19
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: Coolmap, decorators: [{
20
+ type: Component,
21
+ args: [{ selector: 'lib-coolmap', imports: [], template: `` }]
22
+ }] });
23
+
24
+ class SmsCardDetailsComponent {
25
+ modalOpen = input(false, ...(ngDevMode ? [{ debugName: "modalOpen" }] : []));
26
+ smsListcollapse = input(false, ...(ngDevMode ? [{ debugName: "smsListcollapse" }] : []));
27
+ driverListcollapse = input(false, ...(ngDevMode ? [{ debugName: "driverListcollapse" }] : []));
28
+ modalOpenChange = new EventEmitter();
29
+ close() {
30
+ this.modalOpenChange.emit(false);
31
+ }
32
+ icons = {
33
+ X,
34
+ };
35
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SmsCardDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
36
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: SmsCardDetailsComponent, isStandalone: true, selector: "lib-sms-card-details", inputs: { modalOpen: { classPropertyName: "modalOpen", publicName: "modalOpen", isSignal: true, isRequired: false, transformFunction: null }, smsListcollapse: { classPropertyName: "smsListcollapse", publicName: "smsListcollapse", isSignal: true, isRequired: false, transformFunction: null }, driverListcollapse: { classPropertyName: "driverListcollapse", publicName: "driverListcollapse", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { modalOpenChange: "modalOpenChange" }, ngImport: i0, template: "@if (modalOpen()) {\n<div\n class=\"fixed lg:absolute inset-0 overflow-y-auto lg:top-0 z-[111] lg:z-auto\"\n [ngClass]=\"smsListcollapse() && driverListcollapse() ? 'lg:left-[425px]' : 'lg:left-[670px]'\"\n>\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <div class=\"flex min-h-full lg:items-start lg:justify-start items-center justify-center p-3\">\n <div\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[350px]\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b border-gray-200 dark:border-slate-700 dark:bg-slate-900 rounded-t-lg\"\n >\n <h3 class=\"flex items-center gap-1 text-[13px] font-medium text-gray-900 dark:text-white\">\n SMS All Drivers\n </h3>\n <button\n (click)=\"close()\"\n class=\"p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"20\"></lucide-icon>\n </button>\n </div>\n <div class=\"max-h-[70vh] overflow-y-auto p-2\">\n <div class=\"device-info\">\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Jobcode</span>\n <span class=\"value\">016164</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Date</span>\n <span class=\"value\">2026-4-1</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Total tasks</span>\n <span class=\"value\">1</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Material</span>\n <span class=\"value\">#2 Washed Gravel *</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Unit</span>\n <span class=\"value\">Ton</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Pickup Address</span>\n <span class=\"value\">vaibhav loc-1</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Delivery Address</span>\n <span class=\"value\">vaibhav loc-2</span>\n </div>\n </div>\n <div class=\"mt-4\">\n <textarea\n rows=\"2\"\n placeholder=\"Text here....\"\n class=\"w-full px-3 py-2 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm resize-none ng-pristine ng-valid ng-touched\"\n ></textarea>\n </div>\n <div class=\"mt-2 mb-2 flex justify-end\">\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center gap-2 px-3 py-1 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 text-white font-medium rounded-md transition-colors text-sm\"\n >\n Send SMS\n </button>\n </div>\n </div>\n </div>\n </div>\n</div>\n}\n", styles: ["@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}.device-info{display:flex;flex-direction:column;gap:8px}.value{font-size:13px;font-weight:500;color:var(--text-primary, #1e293b)}:host-context(.dark) .value{color:#f1f5f9}.value.status-ok{color:#22c55e}.value.status-warning{color:#f59e0b}.value.status-error{color:#ef4444}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
37
+ }
38
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SmsCardDetailsComponent, decorators: [{
39
+ type: Component,
40
+ args: [{ selector: 'lib-sms-card-details', standalone: true, imports: [LucideAngularModule, NgClass], template: "@if (modalOpen()) {\n<div\n class=\"fixed lg:absolute inset-0 overflow-y-auto lg:top-0 z-[111] lg:z-auto\"\n [ngClass]=\"smsListcollapse() && driverListcollapse() ? 'lg:left-[425px]' : 'lg:left-[670px]'\"\n>\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <div class=\"flex min-h-full lg:items-start lg:justify-start items-center justify-center p-3\">\n <div\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[350px]\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b border-gray-200 dark:border-slate-700 dark:bg-slate-900 rounded-t-lg\"\n >\n <h3 class=\"flex items-center gap-1 text-[13px] font-medium text-gray-900 dark:text-white\">\n SMS All Drivers\n </h3>\n <button\n (click)=\"close()\"\n class=\"p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"20\"></lucide-icon>\n </button>\n </div>\n <div class=\"max-h-[70vh] overflow-y-auto p-2\">\n <div class=\"device-info\">\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Jobcode</span>\n <span class=\"value\">016164</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Date</span>\n <span class=\"value\">2026-4-1</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Total tasks</span>\n <span class=\"value\">1</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Material</span>\n <span class=\"value\">#2 Washed Gravel *</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Unit</span>\n <span class=\"value\">Ton</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Pickup Address</span>\n <span class=\"value\">vaibhav loc-1</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-sm\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Delivery Address</span>\n <span class=\"value\">vaibhav loc-2</span>\n </div>\n </div>\n <div class=\"mt-4\">\n <textarea\n rows=\"2\"\n placeholder=\"Text here....\"\n class=\"w-full px-3 py-2 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm resize-none ng-pristine ng-valid ng-touched\"\n ></textarea>\n </div>\n <div class=\"mt-2 mb-2 flex justify-end\">\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center gap-2 px-3 py-1 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 text-white font-medium rounded-md transition-colors text-sm\"\n >\n Send SMS\n </button>\n </div>\n </div>\n </div>\n </div>\n</div>\n}\n", styles: ["@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}.device-info{display:flex;flex-direction:column;gap:8px}.value{font-size:13px;font-weight:500;color:var(--text-primary, #1e293b)}:host-context(.dark) .value{color:#f1f5f9}.value.status-ok{color:#22c55e}.value.status-warning{color:#f59e0b}.value.status-error{color:#ef4444}\n"] }]
41
+ }], propDecorators: { modalOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalOpen", required: false }] }], smsListcollapse: [{ type: i0.Input, args: [{ isSignal: true, alias: "smsListcollapse", required: false }] }], driverListcollapse: [{ type: i0.Input, args: [{ isSignal: true, alias: "driverListcollapse", required: false }] }], modalOpenChange: [{
42
+ type: Output
43
+ }] } });
44
+
45
+ class DriverListComponent {
46
+ showSmsModal = false;
47
+ modalOpen = input(false, ...(ngDevMode ? [{ debugName: "modalOpen" }] : []));
48
+ driverListcollapse = input(false, ...(ngDevMode ? [{ debugName: "driverListcollapse" }] : []));
49
+ smsListcollapse = signal(false, ...(ngDevMode ? [{ debugName: "smsListcollapse" }] : []));
50
+ modalOpenChange = new EventEmitter();
51
+ close() {
52
+ this.modalOpenChange.emit(false);
53
+ }
54
+ icons = {
55
+ X,
56
+ };
57
+ showSmsModalPopup() {
58
+ this.showSmsModal = !this.showSmsModal;
59
+ this.smsListcollapse.set(this.showSmsModal);
60
+ }
61
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DriverListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
62
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: DriverListComponent, isStandalone: true, selector: "lib-driver-list", inputs: { modalOpen: { classPropertyName: "modalOpen", publicName: "modalOpen", isSignal: true, isRequired: false, transformFunction: null }, driverListcollapse: { classPropertyName: "driverListcollapse", publicName: "driverListcollapse", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { modalOpenChange: "modalOpenChange" }, ngImport: i0, template: "@if (modalOpen()) {\n<div\n class=\"absolute inset-0 overflow-y-auto top-0\"\n [ngClass]=\"driverListcollapse() ? 'left-[60px]' : 'left-[300px]'\"\n>\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <div class=\"flex min-h-full items-start justify-start p-3\">\n <div\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[350px]\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b border-gray-200 dark:border-slate-700 dark:bg-slate-900 rounded-t-lg\"\n >\n <h3 class=\"flex items-center gap-1 font-medium text-gray-900 dark:text-white text-[13px]\">\n Driver List\n </h3>\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center gap-2 px-3 sm:px-6 py-1 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 text-white font-medium rounded-md transition-colors text-sm\"\n (click)=\"showSmsModalPopup()\"\n >\n SMS\n </button>\n <button\n (click)=\"close()\"\n class=\"p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"20\"></lucide-icon>\n </button>\n </div>\n <div class=\"max-h-[40vh] overflow-y-auto p-2\">\n <ul class=\"flex flex-col gap-2\">\n <li>\n <div\n class=\"driver-row-1 flex flex-col driver-card bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-2 py-2\"\n >\n <span class=\"driver-name\">Nilufarrr Lokmannn</span>\n <span class=\"com-name\">Aggdirect Android LLC 2</span>\n <div class=\"cont_del flex justify-between text-black dark:text-grey-600\">\n <small>Driver contact: <b>8318428713</b></small>\n <small>Company contact: <b>8318428713</b></small>\n </div>\n </div>\n </li>\n <li>\n <div\n class=\"driver-row-1 flex flex-col driver-card bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-2 py-2\"\n >\n <span class=\"driver-name\">Nilufarrr Lokmannn</span>\n <span class=\"com-name\">Aggdirect Android LLC 2</span>\n <div class=\"cont_del flex justify-between\">\n <small>Driver contact: <b>8318428713</b></small>\n <small>Company contact: <b>8318428713</b></small>\n </div>\n </div>\n </li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n}\n\n<lib-sms-card-details\n [(modalOpen)]=\"showSmsModal\"\n [smsListcollapse]=\"smsListcollapse()\"\n [driverListcollapse]=\"driverListcollapse()\"\n></lib-sms-card-details>\n", styles: [".driver-name{font-size:12px;font-weight:600;color:var(--text-primary, #1e293b);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}:host-context(.dark) .driver-name{color:#f1f5f9}.com-name,.cont_del{font-size:12px;color:var(--text-secondary, #64748b);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cont_del{font-size:12px;margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.driver-card{position:relative;display:flex;flex-direction:column;gap:2px;border-radius:8px;cursor:pointer;transition:all .15s ease;min-height:72px}@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: SmsCardDetailsComponent, selector: "lib-sms-card-details", inputs: ["modalOpen", "smsListcollapse", "driverListcollapse"], outputs: ["modalOpenChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
63
+ }
64
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DriverListComponent, decorators: [{
65
+ type: Component,
66
+ args: [{ selector: 'lib-driver-list', standalone: true, imports: [LucideAngularModule, SmsCardDetailsComponent, NgClass], template: "@if (modalOpen()) {\n<div\n class=\"absolute inset-0 overflow-y-auto top-0\"\n [ngClass]=\"driverListcollapse() ? 'left-[60px]' : 'left-[300px]'\"\n>\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <div class=\"flex min-h-full items-start justify-start p-3\">\n <div\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[350px]\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b border-gray-200 dark:border-slate-700 dark:bg-slate-900 rounded-t-lg\"\n >\n <h3 class=\"flex items-center gap-1 font-medium text-gray-900 dark:text-white text-[13px]\">\n Driver List\n </h3>\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center gap-2 px-3 sm:px-6 py-1 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 text-white font-medium rounded-md transition-colors text-sm\"\n (click)=\"showSmsModalPopup()\"\n >\n SMS\n </button>\n <button\n (click)=\"close()\"\n class=\"p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"20\"></lucide-icon>\n </button>\n </div>\n <div class=\"max-h-[40vh] overflow-y-auto p-2\">\n <ul class=\"flex flex-col gap-2\">\n <li>\n <div\n class=\"driver-row-1 flex flex-col driver-card bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-2 py-2\"\n >\n <span class=\"driver-name\">Nilufarrr Lokmannn</span>\n <span class=\"com-name\">Aggdirect Android LLC 2</span>\n <div class=\"cont_del flex justify-between text-black dark:text-grey-600\">\n <small>Driver contact: <b>8318428713</b></small>\n <small>Company contact: <b>8318428713</b></small>\n </div>\n </div>\n </li>\n <li>\n <div\n class=\"driver-row-1 flex flex-col driver-card bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-2 py-2\"\n >\n <span class=\"driver-name\">Nilufarrr Lokmannn</span>\n <span class=\"com-name\">Aggdirect Android LLC 2</span>\n <div class=\"cont_del flex justify-between\">\n <small>Driver contact: <b>8318428713</b></small>\n <small>Company contact: <b>8318428713</b></small>\n </div>\n </div>\n </li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n}\n\n<lib-sms-card-details\n [(modalOpen)]=\"showSmsModal\"\n [smsListcollapse]=\"smsListcollapse()\"\n [driverListcollapse]=\"driverListcollapse()\"\n></lib-sms-card-details>\n", styles: [".driver-name{font-size:12px;font-weight:600;color:var(--text-primary, #1e293b);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}:host-context(.dark) .driver-name{color:#f1f5f9}.com-name,.cont_del{font-size:12px;color:var(--text-secondary, #64748b);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cont_del{font-size:12px;margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.driver-card{position:relative;display:flex;flex-direction:column;gap:2px;border-radius:8px;cursor:pointer;transition:all .15s ease;min-height:72px}@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}\n"] }]
67
+ }], propDecorators: { modalOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalOpen", required: false }] }], driverListcollapse: [{ type: i0.Input, args: [{ isSignal: true, alias: "driverListcollapse", required: false }] }], modalOpenChange: [{
68
+ type: Output
69
+ }] } });
70
+
71
+ class JobDetailsComponent {
72
+ modalOpen = input(false, ...(ngDevMode ? [{ debugName: "modalOpen" }] : []));
73
+ routeData = input(null, ...(ngDevMode ? [{ debugName: "routeData" }] : []));
74
+ modalOpenChange = new EventEmitter();
75
+ dragX = signal(0, ...(ngDevMode ? [{ debugName: "dragX" }] : []));
76
+ dragY = signal(0, ...(ngDevMode ? [{ debugName: "dragY" }] : []));
77
+ isDragging = false;
78
+ dragStartX = 0;
79
+ dragStartY = 0;
80
+ dragInitialX = 0;
81
+ dragInitialY = 0;
82
+ close() {
83
+ this.modalOpenChange.emit(false);
84
+ }
85
+ icons = {
86
+ X,
87
+ };
88
+ startDrag(event) {
89
+ this.isDragging = true;
90
+ this.dragStartX = event.clientX;
91
+ this.dragStartY = event.clientY;
92
+ this.dragInitialX = this.dragX();
93
+ this.dragInitialY = this.dragY();
94
+ event.preventDefault();
95
+ }
96
+ onMouseMove(event) {
97
+ if (!this.isDragging || !this.modalOpen())
98
+ return;
99
+ this.dragX.set(this.dragInitialX + (event.clientX - this.dragStartX));
100
+ this.dragY.set(this.dragInitialY + (event.clientY - this.dragStartY));
101
+ }
102
+ onMouseUp() {
103
+ this.isDragging = false;
104
+ }
105
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JobDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
106
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: JobDetailsComponent, isStandalone: true, selector: "lib-job-details", inputs: { modalOpen: { classPropertyName: "modalOpen", publicName: "modalOpen", isSignal: true, isRequired: false, transformFunction: null }, routeData: { classPropertyName: "routeData", publicName: "routeData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { modalOpenChange: "modalOpenChange" }, host: { listeners: { "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp()" } }, ngImport: i0, template: "@if (modalOpen()) {\n<div class=\"z-[111] xl:z-[80] bottom-0 lg:left-[300px] fixed lg:absolute inset-0 overflow-y-auto\">\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <div class=\"flex min-h-full lg:items-end lg:justify-start items-center justify-center p-3\">\n <div\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[350px]\"\n [style.transform]=\"'translate(' + dragX() + 'px, ' + dragY() + 'px)'\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b cursor-move border-gray-200 dark:border-slate-700 dark:bg-slate-900 rounded-t-lg\"\n (mousedown)=\"startDrag($event)\"\n >\n <h3 class=\"flex items-center gap-1 font-medium text-gray-900 dark:text-white text-[13px]\">\n Job Details\n </h3>\n <button\n (click)=\"close()\"\n class=\"p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"20\"></lucide-icon>\n </button>\n </div>\n <div class=\"max-h-[50vh] overflow-y-auto p-2\">\n <div class=\"stats-row\">\n <div class=\"stat-item active\" title=\"Active routes (assigned + in progress)\">\n <span class=\"stat-count\">{{ routeData()?.values?.Done || 0 }}</span>\n <span class=\"stat-label\">Done</span>\n </div>\n\n <div class=\"stat-item completed\" title=\"Completed tasks\">\n <span class=\"stat-count\">{{ routeData()?.values?.Incomplete || 0 }}</span>\n <span class=\"stat-label\">Imcomplete</span>\n </div>\n <div class=\"stat-item declined\" title=\"Declined routes - needs attention\">\n <span class=\"stat-count\">{{ routeData()?.values?.Ongoing || 0 }}</span>\n <span class=\"stat-label\">Ongoing</span>\n </div>\n <div class=\"stat-item scheduled\" title=\"Scheduled jobs for today\">\n <span class=\"stat-count\">{{ routeData()?.values?.Open || 1 }}</span>\n <span class=\"stat-label\">Open</span>\n </div>\n </div>\n <div\n class=\"location-flow bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-2 py-2 rounded-lg mt-4\"\n >\n <span class=\"pickup truncate block\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n class=\"inline mr-1 text-green-500\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n Pickup: {{ routeData()?.pickup_location || 'N/A' }}\n </span>\n <span class=\"delivery truncate block mt-1\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n class=\"inline mr-1 text-blue-500\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n Delivery: {{ routeData()?.delivery_location || 'N/A' }}\n </span>\n </div>\n <div class=\"device-info mt-2\">\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Jobcode</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.order_number || 'N/A' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Customer</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.customer_name || 'N/A' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Project Name</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.project || 'N/A' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Job Type</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.unit || 'N/A' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Material</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.material || 'General Freight' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Customer Contact</span>\n <span class=\"font-medium\">{{ routeData()?.customer_contact || 'None' }}</span>\n </div>\n <div class=\"flex justify-between py-2 text-xs\">\n <span class=\"text-gray-500 dark:text-slate-500\">Delivery Contact</span>\n <span class=\"font-medium\">{{ routeData()?.delivery_contact || 'None' }}</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n}\n", styles: [".stats-row{display:flex;align-items:center;gap:20px;font-size:12px}.stat-item{display:flex;align-items:center;gap:6px;cursor:default}.stat-count{font-size:15px;font-weight:700;line-height:1}.stat-item.active .stat-count{color:#60a5fa}.stat-item.completed .stat-count{color:#4ade80}.stat-item.declined .stat-count{color:#f87171}.stat-item.scheduled .stat-count{color:#a78bfa}.stat-item.pending .stat-count{color:#fbbf24}.location-flow{display:flex;flex-direction:column;align-items:flex-start;gap:4px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:flex;align-items:center;gap:3px;color:#22c55e}.location-flow .pickup svg{width:8px;height:8px}.location-flow .delivery{display:flex;align-items:center;gap:3px;color:#ef4444}.location-flow .delivery svg{width:8px;height:8px}@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
107
+ }
108
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JobDetailsComponent, decorators: [{
109
+ type: Component,
110
+ args: [{ selector: 'lib-job-details', standalone: true, imports: [LucideAngularModule], template: "@if (modalOpen()) {\n<div class=\"z-[111] xl:z-[80] bottom-0 lg:left-[300px] fixed lg:absolute inset-0 overflow-y-auto\">\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <div class=\"flex min-h-full lg:items-end lg:justify-start items-center justify-center p-3\">\n <div\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[350px]\"\n [style.transform]=\"'translate(' + dragX() + 'px, ' + dragY() + 'px)'\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b cursor-move border-gray-200 dark:border-slate-700 dark:bg-slate-900 rounded-t-lg\"\n (mousedown)=\"startDrag($event)\"\n >\n <h3 class=\"flex items-center gap-1 font-medium text-gray-900 dark:text-white text-[13px]\">\n Job Details\n </h3>\n <button\n (click)=\"close()\"\n class=\"p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"20\"></lucide-icon>\n </button>\n </div>\n <div class=\"max-h-[50vh] overflow-y-auto p-2\">\n <div class=\"stats-row\">\n <div class=\"stat-item active\" title=\"Active routes (assigned + in progress)\">\n <span class=\"stat-count\">{{ routeData()?.values?.Done || 0 }}</span>\n <span class=\"stat-label\">Done</span>\n </div>\n\n <div class=\"stat-item completed\" title=\"Completed tasks\">\n <span class=\"stat-count\">{{ routeData()?.values?.Incomplete || 0 }}</span>\n <span class=\"stat-label\">Imcomplete</span>\n </div>\n <div class=\"stat-item declined\" title=\"Declined routes - needs attention\">\n <span class=\"stat-count\">{{ routeData()?.values?.Ongoing || 0 }}</span>\n <span class=\"stat-label\">Ongoing</span>\n </div>\n <div class=\"stat-item scheduled\" title=\"Scheduled jobs for today\">\n <span class=\"stat-count\">{{ routeData()?.values?.Open || 1 }}</span>\n <span class=\"stat-label\">Open</span>\n </div>\n </div>\n <div\n class=\"location-flow bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-2 py-2 rounded-lg mt-4\"\n >\n <span class=\"pickup truncate block\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n class=\"inline mr-1 text-green-500\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n Pickup: {{ routeData()?.pickup_location || 'N/A' }}\n </span>\n <span class=\"delivery truncate block mt-1\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n class=\"inline mr-1 text-blue-500\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n Delivery: {{ routeData()?.delivery_location || 'N/A' }}\n </span>\n </div>\n <div class=\"device-info mt-2\">\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Jobcode</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.order_number || 'N/A' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Customer</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.customer_name || 'N/A' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Project Name</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.project || 'N/A' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Job Type</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.unit || 'N/A' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Material</span>\n <span class=\"font-medium truncate max-w-[65%] text-right\">{{ routeData()?.material || 'General Freight' }}</span>\n </div>\n <div\n class=\"flex justify-between py-2 border-b border-gray-200 dark:border-slate-600 text-xs\"\n >\n <span class=\"text-gray-500 dark:text-slate-500\">Customer Contact</span>\n <span class=\"font-medium\">{{ routeData()?.customer_contact || 'None' }}</span>\n </div>\n <div class=\"flex justify-between py-2 text-xs\">\n <span class=\"text-gray-500 dark:text-slate-500\">Delivery Contact</span>\n <span class=\"font-medium\">{{ routeData()?.delivery_contact || 'None' }}</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n}\n", styles: [".stats-row{display:flex;align-items:center;gap:20px;font-size:12px}.stat-item{display:flex;align-items:center;gap:6px;cursor:default}.stat-count{font-size:15px;font-weight:700;line-height:1}.stat-item.active .stat-count{color:#60a5fa}.stat-item.completed .stat-count{color:#4ade80}.stat-item.declined .stat-count{color:#f87171}.stat-item.scheduled .stat-count{color:#a78bfa}.stat-item.pending .stat-count{color:#fbbf24}.location-flow{display:flex;flex-direction:column;align-items:flex-start;gap:4px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:flex;align-items:center;gap:3px;color:#22c55e}.location-flow .pickup svg{width:8px;height:8px}.location-flow .delivery{display:flex;align-items:center;gap:3px;color:#ef4444}.location-flow .delivery svg{width:8px;height:8px}@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}\n"] }]
111
+ }], propDecorators: { modalOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalOpen", required: false }] }], routeData: [{ type: i0.Input, args: [{ isSignal: true, alias: "routeData", required: false }] }], modalOpenChange: [{
112
+ type: Output
113
+ }], onMouseMove: [{
114
+ type: HostListener,
115
+ args: ['document:mousemove', ['$event']]
116
+ }], onMouseUp: [{
117
+ type: HostListener,
118
+ args: ['document:mouseup']
119
+ }] } });
120
+
121
+ class JobCodeComponent {
122
+ config = inject(COOLMAP_CONFIG);
123
+ utils = inject(UtilsService);
124
+ coolmapService = inject(CoolmapService);
125
+ showDriverModal = false;
126
+ showDetailModal = false;
127
+ listMode = 'sidebar';
128
+ collapsible = true;
129
+ routes = [];
130
+ selectedRouteIds = [];
131
+ isLoading = false;
132
+ routeSelect = new EventEmitter();
133
+ collapsed = false;
134
+ // New Signal States
135
+ selectedJobDetail = signal(null, ...(ngDevMode ? [{ debugName: "selectedJobDetail" }] : []));
136
+ contextMenuVisible = signal(false, ...(ngDevMode ? [{ debugName: "contextMenuVisible" }] : []));
137
+ driverListcollapse = signal(false, ...(ngDevMode ? [{ debugName: "driverListcollapse" }] : []));
138
+ contextMenuPosition = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "contextMenuPosition" }] : []));
139
+ icons = {
140
+ CarFront,
141
+ Info,
142
+ LoaderCircle
143
+ };
144
+ toggleRouteSelection(route) {
145
+ const id = route.job_id || route.route_id || route.route_details_id;
146
+ if (!id)
147
+ return;
148
+ this.routeSelect.emit(id);
149
+ }
150
+ toggleCollapse() {
151
+ if (this.collapsible) {
152
+ this.collapsed = !this.collapsed;
153
+ this.driverListcollapse.set(this.collapsed);
154
+ }
155
+ }
156
+ calculateStatusPercentage(type, route) {
157
+ const values = route?.values || { Done: 0, Ongoing: 0, Open: 0 };
158
+ const total = (values.Done || 0) + (values.Ongoing || 0) + (values.Open || 0);
159
+ if (total === 0)
160
+ return type === 'open' ? 100 : 0;
161
+ if (type === 'done')
162
+ return Math.round((100 * (values.Done || 0)) / total);
163
+ if (type === 'ongoing')
164
+ return Math.round((100 * (values.Ongoing || 0)) / total);
165
+ if (type === 'open')
166
+ return Math.round((100 * (values.Open || 0)) / total);
167
+ return 0;
168
+ }
169
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JobCodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
170
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: JobCodeComponent, isStandalone: true, selector: "lib-job-code", inputs: { listMode: "listMode", collapsible: "collapsible", routes: "routes", selectedRouteIds: "selectedRouteIds", isLoading: "isLoading" }, outputs: { routeSelect: "routeSelect" }, host: { listeners: { "document:keydown.escape": "icons()" } }, ngImport: i0, template: "<div\n class=\"cards-list-container\"\n [class.floating]=\"listMode === 'floating'\"\n [class.sidebar]=\"listMode === 'sidebar'\"\n [class.inline]=\"listMode === 'inline'\"\n [class.collapsed]=\"collapsed\"\n>\n <div class=\"list-header\">\n <div class=\"header-title\">\n <span class=\"title-text\">Routes</span>\n <span class=\"routes-count\">({{ routes.length || 0 }})</span>\n </div>\n <button\n class=\"collapse-btn\"\n (click)=\"toggleCollapse()\"\n [title]=\"collapsed ? 'Expand' : 'Collapse'\"\n >\n @if (collapsed) {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n } @else {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n }\n </button>\n </div>\n @if (!collapsed) {\n <div class=\"cards-scroll-container\" #scrollContainer>\n @if (isLoading) {\n <div class=\"list-loader\">\n <lucide-icon [img]=\"icons.LoaderCircle\" [size]=\"32\" class=\"animate-spin text-amber-500\"></lucide-icon>\n </div>\n }\n <div class=\"cards-list\">\n @for (route of routes; track $index) {\n <div class=\"card-wrapper\" (click)=\"toggleRouteSelection(route)\">\n <div\n class=\"route-card bg-white dark:bg-slate-800 border transition-all duration-200\"\n [class.border-brand-blue]=\"selectedRouteIds.includes(route.job_id || route.route_id || route.route_details_id || '')\"\n [class.border-gray-300]=\"!selectedRouteIds.includes(route.job_id || route.route_id || route.route_details_id || '')\"\n [class.dark:border-slate-700]=\"!selectedRouteIds.includes(route.job_id || route.route_id || route.route_details_id || '')\"\n [class.selected]=\"selectedRouteIds.includes(route.job_id || route.route_id || route.route_details_id || '')\"\n >\n <div class=\"task-header justify-between\">\n <span class=\"job-code\">{{ route.order_number || 'N/A' }}</span>\n <div class=\"flex gap-2\">\n <div class=\"statusunit text-white\" [ngClass]=\"route.unit || ''\">{{ route.unit?.charAt(0) || 'U' }}</div>\n @if (config.repository !== 'customer') {\n <div\n class=\"statusunit bg-slate-900 dark:bg-white text-white dark:text-black flex items-center justify-center cursor-pointer hover:opacity-80\"\n (click)=\"showDriverModal = true; toggleCollapse(); $event.stopPropagation()\"\n >\n <lucide-icon [img]=\"icons.CarFront\" [size]=\"15\"></lucide-icon>\n </div>\n }\n <div\n class=\"text-black dark:text-white flex items-center justify-center cursor-pointer hover:opacity-80\"\n (click)=\"selectedJobDetail.set(route); showDetailModal = true; $event.stopPropagation()\"\n >\n <lucide-icon [img]=\"icons.Info\" [size]=\"20\"></lucide-icon>\n </div>\n </div>\n </div>\n @if (config.repository === 'coolmap') {\n <div class=\"customer-name font-semibold truncate\">{{ route.customer_name }}</div>\n }\n <div class=\"material-info truncate text-gray-600 dark:text-gray-300\">{{ route.project || 'No Project' }}</div>\n <div class=\"location-flow mt-2 text-sm flex items-center gap-2\">\n <span class=\"pickup flex items-center gap-1 truncate max-w-[40%] text-green-600 dark:text-green-400\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n {{ (route.pickup_location || '').split('|')[0] || 'Unknown' }}\n </span>\n <span class=\"arrow text-gray-400\">\u2192</span>\n <span class=\"delivery truncate max-w-[40%] text-brand-blue\">{{ (route.delivery_location || '').split('|')[0] || 'Unknown' }}</span>\n </div>\n <div class=\"material-info mt-2 truncate font-medium\">{{ route.material || '' }}</div>\n <div class=\"driver-row-4 mt-3 flex items-center gap-2\">\n <div class=\"progress-bar flex-1 h-1.5 bg-gray-200 dark:bg-slate-700 rounded-full overflow-hidden flex\">\n <div class=\"progress-segment completed h-full bg-green-500 transition-all duration-300\" [style.width.%]=\"calculateStatusPercentage('done', route)\"></div>\n <div class=\"progress-segment ongoing h-full transition-all duration-300\" [style.backgroundColor]=\"'#fc0'\" [style.width.%]=\"calculateStatusPercentage('ongoing', route)\"></div>\n <div class=\"progress-segment open h-full bg-gray-300 dark:bg-slate-600 transition-all duration-300\" [style.width.%]=\"calculateStatusPercentage('open', route)\"></div>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n</div>\n<lib-driver-list\n [(modalOpen)]=\"showDriverModal\"\n [driverListcollapse]=\"driverListcollapse()\"\n></lib-driver-list>\n<lib-job-details [(modalOpen)]=\"showDetailModal\" [routeData]=\"selectedJobDetail()\"></lib-job-details>\n", styles: [":host{display:block}.cards-list-container{display:flex;flex-direction:column;height:100%;background:var(--bg-primary, white)}.cards-list-container.floating{position:absolute;top:12px;left:12px;width:280px;max-height:calc(100% - 24px);border-radius:12px;box-shadow:0 4px 24px #00000026;z-index:100;overflow:hidden}.cards-list-container.floating.collapsed{width:48px}.cards-list-container.sidebar{width:100%;border-right:1px solid var(--border-color, #e2e8f0)}.cards-list-container.inline{width:160px;min-width:160px;border-right:1px solid var(--border-color, #e2e8f0)}:host-context(.dark) .cards-list-container{background-color:#1e293b}:host-context(.dark) .cards-list-container.sidebar,:host-context(.dark) .cards-list-container.inline{border-color:#334155}.list-header{display:flex;align-items:center;justify-content:space-between;padding:12px;border-bottom:1px solid var(--border-color, #e2e8f0);background:var(--bg-secondary, #f8fafc)}:host-context(.dark) .list-header{background-color:#0f172a;border-color:#334155}.collapsed .list-header{padding:12px 8px;justify-content:center}.header-title{display:flex;align-items:center;gap:6px}.collapsed .header-title{display:none}.title-text{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b)}:host-context(.dark) .title-text{color:#f1f5f9}.routes-count{font-size:12px;color:var(--text-secondary, #64748b)}.collapse-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;color:var(--text-secondary, #64748b);cursor:pointer;border-radius:6px;transition:all .15s ease}.collapse-btn:hover{background:var(--hover-bg, #e2e8f0);color:var(--text-primary, #1e293b)}:host-context(.dark) .collapse-btn:hover{background-color:#334155;color:#f1f5f9}.cards-scroll-container{flex:1;overflow-y:auto;overflow-x:hidden}.cards-list{display:flex;flex-direction:column;gap:8px;padding:8px}.card-wrapper{border-radius:8px;transition:all .2s ease}.route-card{position:relative;padding:6px 10px;display:flex;flex-direction:column;gap:2px;border-radius:8px;cursor:pointer;transition:all .15s ease;min-height:72px}.route-card:hover{border-color:var(--accent-color, #3b82f6);box-shadow:0 2px 8px #00000014}.route-card.selected{border-color:var(--accent-color, #3b82f6);background:#3b82f60d}.task-header{display:flex;align-items:center;gap:6px;margin-bottom:4px}.job-code{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b)}:host-context(.dark) .job-code{color:#f1f5f9}.customer-name{font-size:12px;color:var(--text-secondary, #64748b);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.material-info{font-size:11px;color:var(--text-tertiary, #94a3b8);margin-bottom:6px;font-weight:500}.location-flow{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:flex;align-items:center;gap:3px;color:#22c55e}.location-flow .pickup svg{width:8px;height:8px}.location-flow .arrow{color:var(--text-tertiary, #94a3b8)}.location-flow .delivery{color:#ef4444}.driver-row-4{display:flex;align-items:center;gap:4px;margin-top:2px}.progress-bar{display:flex;gap:2px}.progress-segment{height:6px;border-radius:2px;position:relative;cursor:pointer}.progress-segment.completed{background:#22c55e}.progress-segment.in_progress{background:#f59e0b}.progress-segment.pending{background:#cbd5e1}.progress-segment.incomplete{background:#ef4444}.progress-segment:after{content:attr(data-tooltip);position:absolute;bottom:calc(100% + 8px);left:50%;transform:translate(-50%);background:#1e293b;color:#fff;padding:6px 10px;border-radius:6px;font-size:10px;white-space:pre-line;min-width:120px;max-width:180px;box-shadow:0 3px 10px #0003;z-index:1000;opacity:0;visibility:hidden;transition:opacity .15s ease;pointer-events:none}.progress-segment:hover:after{opacity:1;visibility:visible}.task-count{font-size:10px;font-weight:600;color:var(--text-secondary, #64748b);min-width:24px;text-align:right}.on-time-indicator{font-size:10px;color:#22c55e}.on-time-indicator.late{color:#ef4444}.statusunit{border-radius:30px;font-size:12px;text-transform:capitalize;font-weight:600;width:20px;height:20px;text-align:center;line-height:20px}.statusunit.Ton{background:#22c55e}.statusunit.Load{background:#3b82f6}.statusunit.Hourly{background:#f59e0b}.list-loader{position:absolute;inset:0;background:#fff6;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:50;border-radius:0 0 12px 12px}:host-context(.dark) .list-loader{background:#0f172a66}.animate-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: DriverListComponent, selector: "lib-driver-list", inputs: ["modalOpen", "driverListcollapse"], outputs: ["modalOpenChange"] }, { kind: "component", type: JobDetailsComponent, selector: "lib-job-details", inputs: ["modalOpen", "routeData"], outputs: ["modalOpenChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
171
+ }
172
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JobCodeComponent, decorators: [{
173
+ type: Component,
174
+ args: [{ selector: 'lib-job-code', standalone: true, imports: [LucideAngularModule, DriverListComponent, JobDetailsComponent, NgClass], template: "<div\n class=\"cards-list-container\"\n [class.floating]=\"listMode === 'floating'\"\n [class.sidebar]=\"listMode === 'sidebar'\"\n [class.inline]=\"listMode === 'inline'\"\n [class.collapsed]=\"collapsed\"\n>\n <div class=\"list-header\">\n <div class=\"header-title\">\n <span class=\"title-text\">Routes</span>\n <span class=\"routes-count\">({{ routes.length || 0 }})</span>\n </div>\n <button\n class=\"collapse-btn\"\n (click)=\"toggleCollapse()\"\n [title]=\"collapsed ? 'Expand' : 'Collapse'\"\n >\n @if (collapsed) {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n } @else {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n }\n </button>\n </div>\n @if (!collapsed) {\n <div class=\"cards-scroll-container\" #scrollContainer>\n @if (isLoading) {\n <div class=\"list-loader\">\n <lucide-icon [img]=\"icons.LoaderCircle\" [size]=\"32\" class=\"animate-spin text-amber-500\"></lucide-icon>\n </div>\n }\n <div class=\"cards-list\">\n @for (route of routes; track $index) {\n <div class=\"card-wrapper\" (click)=\"toggleRouteSelection(route)\">\n <div\n class=\"route-card bg-white dark:bg-slate-800 border transition-all duration-200\"\n [class.border-brand-blue]=\"selectedRouteIds.includes(route.job_id || route.route_id || route.route_details_id || '')\"\n [class.border-gray-300]=\"!selectedRouteIds.includes(route.job_id || route.route_id || route.route_details_id || '')\"\n [class.dark:border-slate-700]=\"!selectedRouteIds.includes(route.job_id || route.route_id || route.route_details_id || '')\"\n [class.selected]=\"selectedRouteIds.includes(route.job_id || route.route_id || route.route_details_id || '')\"\n >\n <div class=\"task-header justify-between\">\n <span class=\"job-code\">{{ route.order_number || 'N/A' }}</span>\n <div class=\"flex gap-2\">\n <div class=\"statusunit text-white\" [ngClass]=\"route.unit || ''\">{{ route.unit?.charAt(0) || 'U' }}</div>\n @if (config.repository !== 'customer') {\n <div\n class=\"statusunit bg-slate-900 dark:bg-white text-white dark:text-black flex items-center justify-center cursor-pointer hover:opacity-80\"\n (click)=\"showDriverModal = true; toggleCollapse(); $event.stopPropagation()\"\n >\n <lucide-icon [img]=\"icons.CarFront\" [size]=\"15\"></lucide-icon>\n </div>\n }\n <div\n class=\"text-black dark:text-white flex items-center justify-center cursor-pointer hover:opacity-80\"\n (click)=\"selectedJobDetail.set(route); showDetailModal = true; $event.stopPropagation()\"\n >\n <lucide-icon [img]=\"icons.Info\" [size]=\"20\"></lucide-icon>\n </div>\n </div>\n </div>\n @if (config.repository === 'coolmap') {\n <div class=\"customer-name font-semibold truncate\">{{ route.customer_name }}</div>\n }\n <div class=\"material-info truncate text-gray-600 dark:text-gray-300\">{{ route.project || 'No Project' }}</div>\n <div class=\"location-flow mt-2 text-sm flex items-center gap-2\">\n <span class=\"pickup flex items-center gap-1 truncate max-w-[40%] text-green-600 dark:text-green-400\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n {{ (route.pickup_location || '').split('|')[0] || 'Unknown' }}\n </span>\n <span class=\"arrow text-gray-400\">\u2192</span>\n <span class=\"delivery truncate max-w-[40%] text-brand-blue\">{{ (route.delivery_location || '').split('|')[0] || 'Unknown' }}</span>\n </div>\n <div class=\"material-info mt-2 truncate font-medium\">{{ route.material || '' }}</div>\n <div class=\"driver-row-4 mt-3 flex items-center gap-2\">\n <div class=\"progress-bar flex-1 h-1.5 bg-gray-200 dark:bg-slate-700 rounded-full overflow-hidden flex\">\n <div class=\"progress-segment completed h-full bg-green-500 transition-all duration-300\" [style.width.%]=\"calculateStatusPercentage('done', route)\"></div>\n <div class=\"progress-segment ongoing h-full transition-all duration-300\" [style.backgroundColor]=\"'#fc0'\" [style.width.%]=\"calculateStatusPercentage('ongoing', route)\"></div>\n <div class=\"progress-segment open h-full bg-gray-300 dark:bg-slate-600 transition-all duration-300\" [style.width.%]=\"calculateStatusPercentage('open', route)\"></div>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n</div>\n<lib-driver-list\n [(modalOpen)]=\"showDriverModal\"\n [driverListcollapse]=\"driverListcollapse()\"\n></lib-driver-list>\n<lib-job-details [(modalOpen)]=\"showDetailModal\" [routeData]=\"selectedJobDetail()\"></lib-job-details>\n", styles: [":host{display:block}.cards-list-container{display:flex;flex-direction:column;height:100%;background:var(--bg-primary, white)}.cards-list-container.floating{position:absolute;top:12px;left:12px;width:280px;max-height:calc(100% - 24px);border-radius:12px;box-shadow:0 4px 24px #00000026;z-index:100;overflow:hidden}.cards-list-container.floating.collapsed{width:48px}.cards-list-container.sidebar{width:100%;border-right:1px solid var(--border-color, #e2e8f0)}.cards-list-container.inline{width:160px;min-width:160px;border-right:1px solid var(--border-color, #e2e8f0)}:host-context(.dark) .cards-list-container{background-color:#1e293b}:host-context(.dark) .cards-list-container.sidebar,:host-context(.dark) .cards-list-container.inline{border-color:#334155}.list-header{display:flex;align-items:center;justify-content:space-between;padding:12px;border-bottom:1px solid var(--border-color, #e2e8f0);background:var(--bg-secondary, #f8fafc)}:host-context(.dark) .list-header{background-color:#0f172a;border-color:#334155}.collapsed .list-header{padding:12px 8px;justify-content:center}.header-title{display:flex;align-items:center;gap:6px}.collapsed .header-title{display:none}.title-text{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b)}:host-context(.dark) .title-text{color:#f1f5f9}.routes-count{font-size:12px;color:var(--text-secondary, #64748b)}.collapse-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;color:var(--text-secondary, #64748b);cursor:pointer;border-radius:6px;transition:all .15s ease}.collapse-btn:hover{background:var(--hover-bg, #e2e8f0);color:var(--text-primary, #1e293b)}:host-context(.dark) .collapse-btn:hover{background-color:#334155;color:#f1f5f9}.cards-scroll-container{flex:1;overflow-y:auto;overflow-x:hidden}.cards-list{display:flex;flex-direction:column;gap:8px;padding:8px}.card-wrapper{border-radius:8px;transition:all .2s ease}.route-card{position:relative;padding:6px 10px;display:flex;flex-direction:column;gap:2px;border-radius:8px;cursor:pointer;transition:all .15s ease;min-height:72px}.route-card:hover{border-color:var(--accent-color, #3b82f6);box-shadow:0 2px 8px #00000014}.route-card.selected{border-color:var(--accent-color, #3b82f6);background:#3b82f60d}.task-header{display:flex;align-items:center;gap:6px;margin-bottom:4px}.job-code{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b)}:host-context(.dark) .job-code{color:#f1f5f9}.customer-name{font-size:12px;color:var(--text-secondary, #64748b);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.material-info{font-size:11px;color:var(--text-tertiary, #94a3b8);margin-bottom:6px;font-weight:500}.location-flow{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:flex;align-items:center;gap:3px;color:#22c55e}.location-flow .pickup svg{width:8px;height:8px}.location-flow .arrow{color:var(--text-tertiary, #94a3b8)}.location-flow .delivery{color:#ef4444}.driver-row-4{display:flex;align-items:center;gap:4px;margin-top:2px}.progress-bar{display:flex;gap:2px}.progress-segment{height:6px;border-radius:2px;position:relative;cursor:pointer}.progress-segment.completed{background:#22c55e}.progress-segment.in_progress{background:#f59e0b}.progress-segment.pending{background:#cbd5e1}.progress-segment.incomplete{background:#ef4444}.progress-segment:after{content:attr(data-tooltip);position:absolute;bottom:calc(100% + 8px);left:50%;transform:translate(-50%);background:#1e293b;color:#fff;padding:6px 10px;border-radius:6px;font-size:10px;white-space:pre-line;min-width:120px;max-width:180px;box-shadow:0 3px 10px #0003;z-index:1000;opacity:0;visibility:hidden;transition:opacity .15s ease;pointer-events:none}.progress-segment:hover:after{opacity:1;visibility:visible}.task-count{font-size:10px;font-weight:600;color:var(--text-secondary, #64748b);min-width:24px;text-align:right}.on-time-indicator{font-size:10px;color:#22c55e}.on-time-indicator.late{color:#ef4444}.statusunit{border-radius:30px;font-size:12px;text-transform:capitalize;font-weight:600;width:20px;height:20px;text-align:center;line-height:20px}.statusunit.Ton{background:#22c55e}.statusunit.Load{background:#3b82f6}.statusunit.Hourly{background:#f59e0b}.list-loader{position:absolute;inset:0;background:#fff6;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:50;border-radius:0 0 12px 12px}:host-context(.dark) .list-loader{background:#0f172a66}.animate-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
175
+ }], propDecorators: { listMode: [{
176
+ type: Input
177
+ }], collapsible: [{
178
+ type: Input
179
+ }], routes: [{
180
+ type: Input
181
+ }], selectedRouteIds: [{
182
+ type: Input
183
+ }], isLoading: [{
184
+ type: Input
185
+ }], routeSelect: [{
186
+ type: Output
187
+ }], icons: [{
188
+ type: HostListener,
189
+ args: ['document:keydown.escape']
190
+ }] } });
191
+
192
+ class RouteInfoCardComponent {
193
+ modalOpen = input(false, ...(ngDevMode ? [{ debugName: "modalOpen" }] : []));
194
+ routeData = input(null, ...(ngDevMode ? [{ debugName: "routeData" }] : []));
195
+ repository = input('coolmap', ...(ngDevMode ? [{ debugName: "repository" }] : []));
196
+ displayMode = input('dispatch', ...(ngDevMode ? [{ debugName: "displayMode" }] : []));
197
+ modalOpenChange = new EventEmitter();
198
+ edit = new EventEmitter();
199
+ utils = inject(UtilsService);
200
+ addRouteModal = false;
201
+ formattedDate = computed(() => {
202
+ const data = this.routeData();
203
+ if (!data?.created_at)
204
+ return 'N/A';
205
+ return this.utils.getDateFormat(new Date(data.created_at), '/');
206
+ }, ...(ngDevMode ? [{ debugName: "formattedDate" }] : []));
207
+ close() {
208
+ this.modalOpenChange.emit(false);
209
+ }
210
+ icons = {
211
+ X,
212
+ };
213
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: RouteInfoCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
214
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: RouteInfoCardComponent, isStandalone: true, selector: "lib-route-info-card", inputs: { modalOpen: { classPropertyName: "modalOpen", publicName: "modalOpen", isSignal: true, isRequired: false, transformFunction: null }, routeData: { classPropertyName: "routeData", publicName: "routeData", isSignal: true, isRequired: false, transformFunction: null }, repository: { classPropertyName: "repository", publicName: "repository", isSignal: true, isRequired: false, transformFunction: null }, displayMode: { classPropertyName: "displayMode", publicName: "displayMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { modalOpenChange: "modalOpenChange", edit: "edit" }, ngImport: i0, template: "@if (modalOpen()) {\n<div \n class=\"z-[112] bottom-0 fixed lg:absolute inset-0 overflow-y-auto\"\n [ngClass]=\"displayMode() === 'catalog' ? 'lg:left-[300px]' : 'lg:right-[300px]'\"\n>\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <div \n class=\"flex min-h-full lg:items-end items-center justify-center p-3\"\n [ngClass]=\"displayMode() === 'catalog' ? 'lg:justify-start' : 'lg:justify-end'\"\n >\n <div\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[350px]\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b border-gray-200 dark:border-slate-700 dark:bg-slate-900 rounded-t-lg\"\n >\n <h3 class=\"flex items-center gap-1 font-medium text-gray-900 dark:text-white text-[13px]\">\n Route Info\n </h3>\n <button\n (click)=\"close()\"\n class=\"p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"20\"></lucide-icon>\n </button>\n </div>\n <div class=\"max-h-[60vh] overflow-y-auto p-3\">\n <div class=\"space-y-2 bg-gray-50 dark:bg-slate-900/50 border border-gray-200 dark:border-slate-700 p-3 rounded-lg mb-4\">\n <p class=\"text-xs\">\n <b class=\"text-gray-900 dark:text-white\">Pickup:</b> \n <span class=\"text-gray-600 dark:text-gray-400\">\n {{ (routeData()?.pickup_location || '').split('|')[1] || routeData()?.pickup_location || 'N/A' }}\n </span>\n </p>\n <p class=\"text-xs\">\n <b class=\"text-gray-900 dark:text-white\">Delivery:</b> \n <span class=\"text-gray-600 dark:text-gray-400\">\n {{ (routeData()?.delivery_location || '').split('|')[1] || routeData()?.delivery_location || 'N/A' }}\n </span>\n </p>\n </div>\n\n <div class=\"space-y-2.5\">\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Name:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.route_name || 'N/A' }}</span>\n </div>\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Customer:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.customer_name || 'N/A' }}</span>\n </div>\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Type:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.unit || 'N/A' }}</span>\n </div>\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Distance:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.estimated_distance || '0' }} Miles</span>\n </div>\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Travel Time:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.estimated_time || '0' }} Min</span>\n </div>\n\n @if (repository() !== 'customer') {\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Trucker Pay Estimate:</b>\n <span class=\"text-brand-blue font-medium text-right ml-4\">{{ routeData()?.trucker_pay_estimate | currency }}</span>\n </div>\n }\n\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Customer Price Estimate:</b>\n <span class=\"text-brand-blue font-medium text-right ml-4\">{{ routeData()?.customer_price_estimate | currency }}</span>\n </div>\n\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Notes:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4 italic\">{{ routeData()?.note || 'None' }}</span>\n </div>\n </div>\n\n <div class=\"mt-4 pt-3 border-t border-gray-100 dark:border-slate-700 flex justify-between items-center\">\n <p class=\"text-[10px] text-gray-400 italic\">\n Created by {{ routeData()?.created_by_name || routeData()?.user || 'System' }} on {{ formattedDate() }}\n </p>\n @if (displayMode() === 'catalog') {\n <button \n type=\"button\"\n (click)=\"edit.emit(); close()\"\n class=\"px-3 py-1 text-[11px] font-bold uppercase tracking-wider bg-orange-500 hover:bg-orange-600 text-white rounded transition-colors\"\n >\n Edit\n </button>\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n}", styles: [".location-flow{display:flex;flex-direction:column;align-items:flex-start;gap:4px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:flex;align-items:flex-start;gap:3px;color:#22c55e}.location-flow .pickup svg{width:8px;height:8px;margin-top:4px}.location-flow .delivery{display:flex;align-items:flex-start;gap:3px;color:#ef4444}.location-flow .delivery svg{width:8px;height:8px;margin-top:4px}@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i2.CurrencyPipe, name: "currency" }] });
215
+ }
216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: RouteInfoCardComponent, decorators: [{
217
+ type: Component,
218
+ args: [{ selector: 'lib-route-info-card', standalone: true, imports: [LucideAngularModule, CommonModule, CurrencyPipe], template: "@if (modalOpen()) {\n<div \n class=\"z-[112] bottom-0 fixed lg:absolute inset-0 overflow-y-auto\"\n [ngClass]=\"displayMode() === 'catalog' ? 'lg:left-[300px]' : 'lg:right-[300px]'\"\n>\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <div \n class=\"flex min-h-full lg:items-end items-center justify-center p-3\"\n [ngClass]=\"displayMode() === 'catalog' ? 'lg:justify-start' : 'lg:justify-end'\"\n >\n <div\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[350px]\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b border-gray-200 dark:border-slate-700 dark:bg-slate-900 rounded-t-lg\"\n >\n <h3 class=\"flex items-center gap-1 font-medium text-gray-900 dark:text-white text-[13px]\">\n Route Info\n </h3>\n <button\n (click)=\"close()\"\n class=\"p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"20\"></lucide-icon>\n </button>\n </div>\n <div class=\"max-h-[60vh] overflow-y-auto p-3\">\n <div class=\"space-y-2 bg-gray-50 dark:bg-slate-900/50 border border-gray-200 dark:border-slate-700 p-3 rounded-lg mb-4\">\n <p class=\"text-xs\">\n <b class=\"text-gray-900 dark:text-white\">Pickup:</b> \n <span class=\"text-gray-600 dark:text-gray-400\">\n {{ (routeData()?.pickup_location || '').split('|')[1] || routeData()?.pickup_location || 'N/A' }}\n </span>\n </p>\n <p class=\"text-xs\">\n <b class=\"text-gray-900 dark:text-white\">Delivery:</b> \n <span class=\"text-gray-600 dark:text-gray-400\">\n {{ (routeData()?.delivery_location || '').split('|')[1] || routeData()?.delivery_location || 'N/A' }}\n </span>\n </p>\n </div>\n\n <div class=\"space-y-2.5\">\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Name:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.route_name || 'N/A' }}</span>\n </div>\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Customer:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.customer_name || 'N/A' }}</span>\n </div>\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Type:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.unit || 'N/A' }}</span>\n </div>\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Distance:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.estimated_distance || '0' }} Miles</span>\n </div>\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Travel Time:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4\">{{ routeData()?.estimated_time || '0' }} Min</span>\n </div>\n\n @if (repository() !== 'customer') {\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Trucker Pay Estimate:</b>\n <span class=\"text-brand-blue font-medium text-right ml-4\">{{ routeData()?.trucker_pay_estimate | currency }}</span>\n </div>\n }\n\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Customer Price Estimate:</b>\n <span class=\"text-brand-blue font-medium text-right ml-4\">{{ routeData()?.customer_price_estimate | currency }}</span>\n </div>\n\n <div class=\"flex justify-between items-start text-xs border-b border-gray-100 dark:border-slate-700/50 pb-2\">\n <b class=\"text-gray-900 dark:text-white whitespace-nowrap\">Notes:</b>\n <span class=\"text-gray-600 dark:text-gray-400 text-right ml-4 italic\">{{ routeData()?.note || 'None' }}</span>\n </div>\n </div>\n\n <div class=\"mt-4 pt-3 border-t border-gray-100 dark:border-slate-700 flex justify-between items-center\">\n <p class=\"text-[10px] text-gray-400 italic\">\n Created by {{ routeData()?.created_by_name || routeData()?.user || 'System' }} on {{ formattedDate() }}\n </p>\n @if (displayMode() === 'catalog') {\n <button \n type=\"button\"\n (click)=\"edit.emit(); close()\"\n class=\"px-3 py-1 text-[11px] font-bold uppercase tracking-wider bg-orange-500 hover:bg-orange-600 text-white rounded transition-colors\"\n >\n Edit\n </button>\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n}", styles: [".location-flow{display:flex;flex-direction:column;align-items:flex-start;gap:4px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:flex;align-items:flex-start;gap:3px;color:#22c55e}.location-flow .pickup svg{width:8px;height:8px;margin-top:4px}.location-flow .delivery{display:flex;align-items:flex-start;gap:3px;color:#ef4444}.location-flow .delivery svg{width:8px;height:8px;margin-top:4px}@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}\n"] }]
219
+ }], propDecorators: { modalOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalOpen", required: false }] }], routeData: [{ type: i0.Input, args: [{ isSignal: true, alias: "routeData", required: false }] }], repository: [{ type: i0.Input, args: [{ isSignal: true, alias: "repository", required: false }] }], displayMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayMode", required: false }] }], modalOpenChange: [{
220
+ type: Output
221
+ }], edit: [{
222
+ type: Output
223
+ }] } });
224
+
225
+ class JobRouteListComponent {
226
+ config = inject(COOLMAP_CONFIG);
227
+ customerRepoDetails;
228
+ utils = inject(UtilsService);
229
+ coolmapService = inject(CoolmapService);
230
+ unitList = [];
231
+ materialsList = [];
232
+ routes = signal([], ...(ngDevMode ? [{ debugName: "routes" }] : []));
233
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
234
+ selectedRouteIds = input([], ...(ngDevMode ? [{ debugName: "selectedRouteIds" }] : []));
235
+ filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
236
+ showSuggestions = signal(false, ...(ngDevMode ? [{ debugName: "showSuggestions" }] : []));
237
+ suggestions = computed(() => {
238
+ const term = this.searchTerm();
239
+ if (term.length < 1)
240
+ return [];
241
+ return this.utils.filter(term, this.filters());
242
+ }, ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
243
+ filteredRoutes = computed(() => {
244
+ let list = this.routes();
245
+ const activeFilters = this.filters();
246
+ // Apply category search from V1 logic
247
+ if (activeFilters.length > 0) {
248
+ list = this.utils.getSearchResults(list, activeFilters);
249
+ }
250
+ const search = this.searchTerm().toLowerCase().trim();
251
+ if (!search || this.showSuggestions())
252
+ return list;
253
+ return list.filter(r => {
254
+ const id = this.getRouteId(r).toLowerCase();
255
+ const order = (r.order_number || '').toLowerCase();
256
+ const name = (r.route_name || '').toLowerCase();
257
+ return id.includes(search) || order.includes(search) || name.includes(search);
258
+ });
259
+ }, ...(ngDevMode ? [{ debugName: "filteredRoutes" }] : []));
260
+ allSelected = computed(() => {
261
+ const visible = this.filteredRoutes();
262
+ const selected = this.selectedRouteIds();
263
+ if (visible.length === 0)
264
+ return false;
265
+ return visible.every(r => selected.includes(this.getRouteId(r)));
266
+ }, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
267
+ isAnyPlotting = computed(() => {
268
+ const prefix = this.modalOpen() ? 'jobrouteList' : 'jobcode';
269
+ const plotting = this.coolmapService.plottingIds();
270
+ return Array.from(plotting).some(id => id.startsWith(`${prefix}-`));
271
+ }, ...(ngDevMode ? [{ debugName: "isAnyPlotting" }] : []));
272
+ showRouteModal = false;
273
+ modalOpen = input(false, ...(ngDevMode ? [{ debugName: "modalOpen" }] : []));
274
+ set initialRoutes(val) {
275
+ if (val) {
276
+ this.routes.set(val);
277
+ this.utils.clearOptions();
278
+ val.forEach(r => this.utils.makeOptions(r));
279
+ }
280
+ }
281
+ selectedRoute = signal(null, ...(ngDevMode ? [{ debugName: "selectedRoute" }] : []));
282
+ modalOpenChange = new EventEmitter();
283
+ routeSelect = new EventEmitter();
284
+ masterToggleEvent = new EventEmitter();
285
+ ngOnInit() {
286
+ this.checkAndCallRouteList();
287
+ }
288
+ checkAndCallRouteList() {
289
+ this.utils.clearOptions();
290
+ if (this.config.repository === 'customer') {
291
+ this.utils.fetchUnitsList().then((res) => {
292
+ this.unitList = res;
293
+ this.utils.fetchMaterialsListForCustomer().then((res) => {
294
+ this.materialsList = res;
295
+ this.getRouteListForCustomer();
296
+ });
297
+ });
298
+ }
299
+ else {
300
+ this.getRouteListForCoolMap();
301
+ }
302
+ }
303
+ getRouteListForCoolMap() {
304
+ this.utils.getData('routes/all').subscribe((res) => {
305
+ if (res && res.data) {
306
+ const list = res.data;
307
+ list.forEach((ele) => this.utils.makeOptions(ele));
308
+ this.routes.set(list);
309
+ }
310
+ });
311
+ }
312
+ getRouteListForCustomer() {
313
+ const customerId = this.customerRepoDetails?.customer.id;
314
+ if (customerId) {
315
+ this.utils.postdata('routes/all', { customer_id: customerId }).subscribe((res) => {
316
+ if (res && res.data) {
317
+ const list = res.data.map((ele) => {
318
+ ele.unit = this.getUnitName(ele);
319
+ this.utils.makeOptions(ele);
320
+ return ele;
321
+ });
322
+ this.routes.set(list);
323
+ }
324
+ });
325
+ }
326
+ }
327
+ getUnitName(data) {
328
+ let unitName = '';
329
+ this.unitList.forEach((res) => {
330
+ if (res.id === data.unit_id) {
331
+ unitName = res.type;
332
+ }
333
+ });
334
+ return unitName || data.unit;
335
+ }
336
+ close() {
337
+ this.modalOpenChange.emit(false);
338
+ }
339
+ isRouteSelected(route) {
340
+ const id = this.getRouteId(route);
341
+ return this.selectedRouteIds().includes(id || '');
342
+ }
343
+ toggleRoute(route) {
344
+ if (this.isPlotting(route))
345
+ return;
346
+ this.routeSelect.emit(route);
347
+ }
348
+ onMasterToggle() {
349
+ if (this.isAnyPlotting())
350
+ return;
351
+ this.masterToggleEvent.emit(this.filteredRoutes());
352
+ }
353
+ addFilter(option) {
354
+ // V1 behavior: keep single filter
355
+ this.filters.set([{ name: option.label, type: option.type, value: option.value }]);
356
+ this.searchTerm.set('');
357
+ this.showSuggestions.set(false);
358
+ }
359
+ removeFilter() {
360
+ this.filters.set([]);
361
+ }
362
+ handleSearchInput(val) {
363
+ this.searchTerm.set(val);
364
+ this.showSuggestions.set(true);
365
+ }
366
+ onSearchBlur() {
367
+ // Delay hiding to allow (click) on suggestions to fire first
368
+ setTimeout(() => {
369
+ this.showSuggestions.set(false);
370
+ }, 200);
371
+ }
372
+ get hasFilter() {
373
+ return this.filters().length > 0;
374
+ }
375
+ isPlotting(route) {
376
+ const prefix = this.modalOpen() ? 'jobrouteList' : 'jobcode';
377
+ const id = this.getRouteId(route);
378
+ return this.coolmapService.plottingIds().has(`${prefix}-${id}`);
379
+ }
380
+ getRouteId(route) {
381
+ return route.job_id || route.route_id || route.route_details_id || '';
382
+ }
383
+ icons = {
384
+ X,
385
+ Info,
386
+ Share2,
387
+ CheckCircle,
388
+ LoaderCircle
389
+ };
390
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JobRouteListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
391
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: JobRouteListComponent, isStandalone: true, selector: "lib-job-route-list", inputs: { customerRepoDetails: { classPropertyName: "customerRepoDetails", publicName: "customerRepoDetails", isSignal: false, isRequired: false, transformFunction: null }, selectedRouteIds: { classPropertyName: "selectedRouteIds", publicName: "selectedRouteIds", isSignal: true, isRequired: false, transformFunction: null }, modalOpen: { classPropertyName: "modalOpen", publicName: "modalOpen", isSignal: true, isRequired: false, transformFunction: null }, initialRoutes: { classPropertyName: "initialRoutes", publicName: "initialRoutes", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { modalOpenChange: "modalOpenChange", routeSelect: "routeSelect", masterToggleEvent: "masterToggleEvent" }, ngImport: i0, template: "@if (modalOpen()) {\n<div class=\"fixed z-[111]\">\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <aside class=\"detail-drawer top-[0] sm:top-[115px]\" (click)=\"$event.stopPropagation()\">\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b border-gray-200 dark:border-slate-700 dark:bg-slate-900\"\n >\n <h3 class=\"flex items-center gap-1 font-semibold text-gray-900 dark:text-white text-[14px]\">\n List of Routes\n <span class=\"text-xs font-normal text-gray-400\">({{ filteredRoutes().length }})</span>\n </h3>\n <button\n type=\"button\"\n (click)=\"onMasterToggle()\"\n [disabled]=\"isAnyPlotting()\"\n class=\"inline-flex items-center justify-center gap-2 px-3 sm:px-4 py-1.5 bg-brand-blue hover:bg-blue-700 disabled:opacity-50 text-white font-medium rounded-md transition-colors text-[12px]\"\n >\n {{ allSelected() ? 'Uncheck All' : 'Check All' }}\n </button>\n <button\n (click)=\"close()\"\n class=\"p-1.5 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"18\"></lucide-icon>\n </button>\n </div>\n <div class=\"p-2\">\n <div class=\"relative w-full\">\n <div class=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <lucide-icon [img]=\"hasFilter ? icons.Info : icons.Share2\" class=\"text-gray-400\" [size]=\"18\"></lucide-icon>\n </div>\n <input\n type=\"text\"\n #searchinput\n [value]=\"searchTerm()\"\n (input)=\"handleSearchInput(searchinput.value)\"\n (focus)=\"showSuggestions.set(true)\"\n (blur)=\"onSearchBlur()\"\n placeholder=\"Search...\"\n class=\"w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm\"\n />\n\n <!-- Autocomplete Suggestions Dropdown -->\n @if (showSuggestions() && suggestions().length > 0) {\n <div class=\"absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 max-h-60 overflow-y-auto\">\n <ul class=\"py-1 text-sm text-gray-700 dark:text-gray-200\">\n @for (option of suggestions(); track option.label + option.type) {\n <li (click)=\"addFilter(option)\"\n class=\"px-4 py-2 hover:bg-brand-blue/10 dark:hover:bg-brand-blue/20 cursor-pointer flex justify-between items-center group\">\n <span>\n <span class=\"font-semibold text-brand-blue capitalize\">{{option.type}}:</span> \n {{option.label}}\n </span>\n <lucide-icon [img]=\"icons.CheckCircle\" class=\"opacity-0 group-hover:opacity-100 text-brand-blue transition-opacity\" [size]=\"16\"></lucide-icon>\n </li>\n }\n </ul>\n </div>\n }\n </div>\n\n <!-- Active Filter Chips -->\n @if (hasFilter) {\n <div class=\"flex flex-wrap gap-2 mt-2 px-1\">\n @for (filter of filters(); track filter.name + filter.type) {\n <div class=\"inline-flex items-center gap-1.5 px-3 py-1 bg-brand-blue/10 border border-brand-blue/20 text-brand-blue rounded-full text-xs font-medium\">\n <span class=\"capitalize\">{{filter.type}}:</span>\n <span class=\"truncate max-w-[150px]\">{{filter.name}}</span>\n <button (click)=\"removeFilter()\" class=\"hover:text-red-500 transition-colors\">\n <lucide-icon [img]=\"icons.X\" [size]=\"14\"></lucide-icon>\n </button>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"overflow-y-auto p-2 drawer-listing-box\">\n <cdk-virtual-scroll-viewport itemSize=\"60\" class=\"routeList-viewport h-[calc(100vh-250px)]\">\n @if (filteredRoutes().length > 0) {\n <ul class=\"route-list-ul gap-2 flex flex-col\">\n @for (route of filteredRoutes(); track getRouteId(route)) {\n <li \n class=\"route-li-v1 bg-white dark:bg-slate-800 border border-gray-300 dark:border-slate-700\" \n [class.selected]=\"isRouteSelected(route)\" \n [class.pointer-events-none]=\"isAnyPlotting()\"\n [class.opacity-60]=\"isAnyPlotting()\"\n (click)=\"toggleRoute(route)\">\n <div class=\"task-header justify-between\">\n <span class=\"job-code\"> {{route.route_name || route.order_number}} </span>\n @if (config.repository === 'coolmap') {\n <span class=\"job-code\">{{route.customer_name}}</span>\n }\n <div class=\"iconprt\">\n @if (isPlotting(route)) {\n <div class=\"plotting-spinner\">\n <lucide-icon [img]=\"icons.LoaderCircle\" class=\"animate-spin text-black dark:text-white flex items-center justify-center relative z-10\" [size]=\"18\"></lucide-icon>\n </div>\n }\n <div class=\"statusunit text-white\" [ngClass]=\"route.unit || ''\">\n {{ route.unit?.charAt(0) || 'U' }}\n </div>\n <div class=\"infoicon\" (click)=\"$event.stopPropagation(); selectedRoute.set(route); showRouteModal = true\">\n <lucide-icon [img]=\"icons.Info\" [size]=\"18\" class=\"text-gray-400\"></lucide-icon>\n </div>\n @if (config.repository === 'customer') {\n <div class=\"infoicon\" (click)=\"$event.stopPropagation()\">\n <lucide-icon [img]=\"icons.Share2\" [size]=\"16\" class=\"text-gray-400\"></lucide-icon>\n </div>\n }\n </div>\n </div>\n \n <div class=\"location-flow bg-gray-100 dark:bg-slate-900 px-2 py-2 pb-3 rounded-lg\">\n <span class=\"pickup\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n {{ (route.pickup_location || '').split('|')[1] ? (route.pickup_location || '').split('|')[1] : (route.pickup_location || '') }}\n </span>\n <span class=\"delivery\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n {{ (route.delivery_location || '').split('|')[1] ? (route.delivery_location || '').split('|')[1] : (route.delivery_location || '') }}\n </span>\n </div>\n </li>\n }\n </ul>\n } @else if (routes().length === 0) {\n <div class=\"p-8 text-center text-gray-400\">\n No routes available for this perspective.\n </div>\n }\n </cdk-virtual-scroll-viewport>\n </div>\n </aside>\n</div>\n}\n<lib-route-info-card [(modalOpen)]=\"showRouteModal\" [routeData]=\"selectedRoute()\" [repository]=\"config.repository\" displayMode=\"dispatch\"></lib-route-info-card>\n", styles: [".detail-drawer{position:fixed;right:0;bottom:36px;width:280px;background:var(--bg-primary, white);border-left:1px solid var(--border-color, #e2e8f0);display:flex;flex-direction:column;z-index:501;animation:slideIn .2s ease}:host-context(.dark) .detail-drawer{background-color:#1e293b;border-color:#334155}.drawer-listing-box{flex:1;overflow:hidden;padding:0!important}.routeList-viewport{height:100%;width:100%}.route-list-ul{list-style:none;padding:10px;margin:0}.route-li-v1{position:relative;padding:6px 10px;display:flex;flex-direction:column;gap:2px;border-radius:8px;cursor:pointer;transition:all .15s ease;min-height:72px}.route-li-v1:hover{background:#ffffff0d}.route-li-v1.selected{border:1px solid #3b82f6}.task-header{display:flex;align-items:center;gap:6px;margin-bottom:4px}.job-code{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b);white-space:nowrap;text-overflow:ellipsis;max-width:190px;overflow:hidden}:host-context(.dark) .job-code{color:#f1f5f9}.iconprt{display:flex;flex-shrink:0;gap:5px}.plotting-spinner{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#1e293bb3;border-radius:4px;z-index:10}.animate-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.statusunit{border-radius:30px;font-size:11px;text-transform:capitalize;font-weight:600;width:18px;height:18px;text-align:center;line-height:18px}.statusunit.Ton{background:#ef4444}.statusunit.Load{background:#3b82f6}.statusunit.Hourly{background:#f59e0b}.pickprtbox{flex:1;min-width:0}.pickprtbox h2{font-size:14px;font-weight:600;color:#fff;margin:0;line-height:1.2}.pickprtbox h3{font-size:12px;color:#94a3b8;margin:2px 0 6px;font-weight:400}.pickdropprt{display:flex;flex-direction:column;gap:4px}.pickdropprt .pickprt,.pickdropprt .dropprt{color:#cbd5e1;font-size:11px}.pickdropprt .pickprt h4,.pickdropprt .dropprt h4{margin:0;font-weight:500;color:#94a3b8}.pickdropprt .pickprt.pickprt h4,.pickdropprt .dropprt.pickprt h4{color:#22c55e}.pickdropprt .pickprt.dropprt h4,.pickdropprt .dropprt.dropprt h4{color:#3b82f6}.infoicon{cursor:pointer}.infoicon:hover lucide-icon{color:#fff}@keyframes slideIn{0%{transform:translate(100%)}to{transform:translate(0)}}.animate-slide-in{animation:slideIn .2s ease}.location-flow{display:flex;flex-direction:column;align-items:flex-start;gap:2px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:inline;align-items:center;gap:3px;color:#22c55e;white-space:nowrap;text-overflow:ellipsis;max-width:200px;overflow:hidden;position:relative;padding-left:15px}.location-flow .pickup svg{width:8px;height:8px;position:absolute;left:0;top:5px}.location-flow .delivery{display:inline;align-items:center;gap:3px;color:#ef4444;white-space:nowrap;text-overflow:ellipsis;max-width:200px;overflow:hidden;position:relative;padding-left:15px;top:5px}.location-flow .delivery svg{width:8px;height:8px;position:absolute;left:0;top:5px}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: RouteInfoCardComponent, selector: "lib-route-info-card", inputs: ["modalOpen", "routeData", "repository", "displayMode"], outputs: ["modalOpenChange", "edit"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i2$1.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "component", type: i2$1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }] });
392
+ }
393
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JobRouteListComponent, decorators: [{
394
+ type: Component,
395
+ args: [{ selector: 'lib-job-route-list', standalone: true, imports: [LucideAngularModule, RouteInfoCardComponent, NgClass, ScrollingModule], template: "@if (modalOpen()) {\n<div class=\"fixed z-[111]\">\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n <aside class=\"detail-drawer top-[0] sm:top-[115px]\" (click)=\"$event.stopPropagation()\">\n <div\n class=\"flex items-center justify-between px-[12px] py-[10px] border-b border-gray-200 dark:border-slate-700 dark:bg-slate-900\"\n >\n <h3 class=\"flex items-center gap-1 font-semibold text-gray-900 dark:text-white text-[14px]\">\n List of Routes\n <span class=\"text-xs font-normal text-gray-400\">({{ filteredRoutes().length }})</span>\n </h3>\n <button\n type=\"button\"\n (click)=\"onMasterToggle()\"\n [disabled]=\"isAnyPlotting()\"\n class=\"inline-flex items-center justify-center gap-2 px-3 sm:px-4 py-1.5 bg-brand-blue hover:bg-blue-700 disabled:opacity-50 text-white font-medium rounded-md transition-colors text-[12px]\"\n >\n {{ allSelected() ? 'Uncheck All' : 'Check All' }}\n </button>\n <button\n (click)=\"close()\"\n class=\"p-1.5 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n <lucide-icon [img]=\"icons.X\" [size]=\"18\"></lucide-icon>\n </button>\n </div>\n <div class=\"p-2\">\n <div class=\"relative w-full\">\n <div class=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <lucide-icon [img]=\"hasFilter ? icons.Info : icons.Share2\" class=\"text-gray-400\" [size]=\"18\"></lucide-icon>\n </div>\n <input\n type=\"text\"\n #searchinput\n [value]=\"searchTerm()\"\n (input)=\"handleSearchInput(searchinput.value)\"\n (focus)=\"showSuggestions.set(true)\"\n (blur)=\"onSearchBlur()\"\n placeholder=\"Search...\"\n class=\"w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm\"\n />\n\n <!-- Autocomplete Suggestions Dropdown -->\n @if (showSuggestions() && suggestions().length > 0) {\n <div class=\"absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 max-h-60 overflow-y-auto\">\n <ul class=\"py-1 text-sm text-gray-700 dark:text-gray-200\">\n @for (option of suggestions(); track option.label + option.type) {\n <li (click)=\"addFilter(option)\"\n class=\"px-4 py-2 hover:bg-brand-blue/10 dark:hover:bg-brand-blue/20 cursor-pointer flex justify-between items-center group\">\n <span>\n <span class=\"font-semibold text-brand-blue capitalize\">{{option.type}}:</span> \n {{option.label}}\n </span>\n <lucide-icon [img]=\"icons.CheckCircle\" class=\"opacity-0 group-hover:opacity-100 text-brand-blue transition-opacity\" [size]=\"16\"></lucide-icon>\n </li>\n }\n </ul>\n </div>\n }\n </div>\n\n <!-- Active Filter Chips -->\n @if (hasFilter) {\n <div class=\"flex flex-wrap gap-2 mt-2 px-1\">\n @for (filter of filters(); track filter.name + filter.type) {\n <div class=\"inline-flex items-center gap-1.5 px-3 py-1 bg-brand-blue/10 border border-brand-blue/20 text-brand-blue rounded-full text-xs font-medium\">\n <span class=\"capitalize\">{{filter.type}}:</span>\n <span class=\"truncate max-w-[150px]\">{{filter.name}}</span>\n <button (click)=\"removeFilter()\" class=\"hover:text-red-500 transition-colors\">\n <lucide-icon [img]=\"icons.X\" [size]=\"14\"></lucide-icon>\n </button>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"overflow-y-auto p-2 drawer-listing-box\">\n <cdk-virtual-scroll-viewport itemSize=\"60\" class=\"routeList-viewport h-[calc(100vh-250px)]\">\n @if (filteredRoutes().length > 0) {\n <ul class=\"route-list-ul gap-2 flex flex-col\">\n @for (route of filteredRoutes(); track getRouteId(route)) {\n <li \n class=\"route-li-v1 bg-white dark:bg-slate-800 border border-gray-300 dark:border-slate-700\" \n [class.selected]=\"isRouteSelected(route)\" \n [class.pointer-events-none]=\"isAnyPlotting()\"\n [class.opacity-60]=\"isAnyPlotting()\"\n (click)=\"toggleRoute(route)\">\n <div class=\"task-header justify-between\">\n <span class=\"job-code\"> {{route.route_name || route.order_number}} </span>\n @if (config.repository === 'coolmap') {\n <span class=\"job-code\">{{route.customer_name}}</span>\n }\n <div class=\"iconprt\">\n @if (isPlotting(route)) {\n <div class=\"plotting-spinner\">\n <lucide-icon [img]=\"icons.LoaderCircle\" class=\"animate-spin text-black dark:text-white flex items-center justify-center relative z-10\" [size]=\"18\"></lucide-icon>\n </div>\n }\n <div class=\"statusunit text-white\" [ngClass]=\"route.unit || ''\">\n {{ route.unit?.charAt(0) || 'U' }}\n </div>\n <div class=\"infoicon\" (click)=\"$event.stopPropagation(); selectedRoute.set(route); showRouteModal = true\">\n <lucide-icon [img]=\"icons.Info\" [size]=\"18\" class=\"text-gray-400\"></lucide-icon>\n </div>\n @if (config.repository === 'customer') {\n <div class=\"infoicon\" (click)=\"$event.stopPropagation()\">\n <lucide-icon [img]=\"icons.Share2\" [size]=\"16\" class=\"text-gray-400\"></lucide-icon>\n </div>\n }\n </div>\n </div>\n \n <div class=\"location-flow bg-gray-100 dark:bg-slate-900 px-2 py-2 pb-3 rounded-lg\">\n <span class=\"pickup\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n {{ (route.pickup_location || '').split('|')[1] ? (route.pickup_location || '').split('|')[1] : (route.pickup_location || '') }}\n </span>\n <span class=\"delivery\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n {{ (route.delivery_location || '').split('|')[1] ? (route.delivery_location || '').split('|')[1] : (route.delivery_location || '') }}\n </span>\n </div>\n </li>\n }\n </ul>\n } @else if (routes().length === 0) {\n <div class=\"p-8 text-center text-gray-400\">\n No routes available for this perspective.\n </div>\n }\n </cdk-virtual-scroll-viewport>\n </div>\n </aside>\n</div>\n}\n<lib-route-info-card [(modalOpen)]=\"showRouteModal\" [routeData]=\"selectedRoute()\" [repository]=\"config.repository\" displayMode=\"dispatch\"></lib-route-info-card>\n", styles: [".detail-drawer{position:fixed;right:0;bottom:36px;width:280px;background:var(--bg-primary, white);border-left:1px solid var(--border-color, #e2e8f0);display:flex;flex-direction:column;z-index:501;animation:slideIn .2s ease}:host-context(.dark) .detail-drawer{background-color:#1e293b;border-color:#334155}.drawer-listing-box{flex:1;overflow:hidden;padding:0!important}.routeList-viewport{height:100%;width:100%}.route-list-ul{list-style:none;padding:10px;margin:0}.route-li-v1{position:relative;padding:6px 10px;display:flex;flex-direction:column;gap:2px;border-radius:8px;cursor:pointer;transition:all .15s ease;min-height:72px}.route-li-v1:hover{background:#ffffff0d}.route-li-v1.selected{border:1px solid #3b82f6}.task-header{display:flex;align-items:center;gap:6px;margin-bottom:4px}.job-code{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b);white-space:nowrap;text-overflow:ellipsis;max-width:190px;overflow:hidden}:host-context(.dark) .job-code{color:#f1f5f9}.iconprt{display:flex;flex-shrink:0;gap:5px}.plotting-spinner{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#1e293bb3;border-radius:4px;z-index:10}.animate-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.statusunit{border-radius:30px;font-size:11px;text-transform:capitalize;font-weight:600;width:18px;height:18px;text-align:center;line-height:18px}.statusunit.Ton{background:#ef4444}.statusunit.Load{background:#3b82f6}.statusunit.Hourly{background:#f59e0b}.pickprtbox{flex:1;min-width:0}.pickprtbox h2{font-size:14px;font-weight:600;color:#fff;margin:0;line-height:1.2}.pickprtbox h3{font-size:12px;color:#94a3b8;margin:2px 0 6px;font-weight:400}.pickdropprt{display:flex;flex-direction:column;gap:4px}.pickdropprt .pickprt,.pickdropprt .dropprt{color:#cbd5e1;font-size:11px}.pickdropprt .pickprt h4,.pickdropprt .dropprt h4{margin:0;font-weight:500;color:#94a3b8}.pickdropprt .pickprt.pickprt h4,.pickdropprt .dropprt.pickprt h4{color:#22c55e}.pickdropprt .pickprt.dropprt h4,.pickdropprt .dropprt.dropprt h4{color:#3b82f6}.infoicon{cursor:pointer}.infoicon:hover lucide-icon{color:#fff}@keyframes slideIn{0%{transform:translate(100%)}to{transform:translate(0)}}.animate-slide-in{animation:slideIn .2s ease}.location-flow{display:flex;flex-direction:column;align-items:flex-start;gap:2px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:inline;align-items:center;gap:3px;color:#22c55e;white-space:nowrap;text-overflow:ellipsis;max-width:200px;overflow:hidden;position:relative;padding-left:15px}.location-flow .pickup svg{width:8px;height:8px;position:absolute;left:0;top:5px}.location-flow .delivery{display:inline;align-items:center;gap:3px;color:#ef4444;white-space:nowrap;text-overflow:ellipsis;max-width:200px;overflow:hidden;position:relative;padding-left:15px;top:5px}.location-flow .delivery svg{width:8px;height:8px;position:absolute;left:0;top:5px}\n"] }]
396
+ }], propDecorators: { customerRepoDetails: [{
397
+ type: Input
398
+ }], selectedRouteIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedRouteIds", required: false }] }], modalOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalOpen", required: false }] }], initialRoutes: [{
399
+ type: Input
400
+ }], modalOpenChange: [{
401
+ type: Output
402
+ }], routeSelect: [{
403
+ type: Output
404
+ }], masterToggleEvent: [{
405
+ type: Output
406
+ }] } });
407
+
408
+ class ViewRouteListComponent {
409
+ showRouteModal = false;
410
+ selectedRoute = signal(null, ...(ngDevMode ? [{ debugName: "selectedRoute" }] : []));
411
+ config = inject(COOLMAP_CONFIG);
412
+ listMode = 'sidebar';
413
+ collapsible = true;
414
+ routes = [];
415
+ selectedRouteIds = [];
416
+ routeSelect = new EventEmitter();
417
+ editRoute = new EventEmitter();
418
+ collapsed = false;
419
+ icons = {
420
+ Info,
421
+ ChevronRight,
422
+ ChevronLeft
423
+ };
424
+ toggleCollapse() {
425
+ if (this.collapsible) {
426
+ this.collapsed = !this.collapsed;
427
+ }
428
+ }
429
+ toggleRouteSelection(route) {
430
+ const id = this.getRouteId(route);
431
+ if (!id)
432
+ return;
433
+ this.routeSelect.emit(id);
434
+ }
435
+ getRouteId(route) {
436
+ return route.job_id || route.route_id || route.route_details_id || '';
437
+ }
438
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ViewRouteListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
439
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: ViewRouteListComponent, isStandalone: true, selector: "lib-view-route-list", inputs: { listMode: "listMode", collapsible: "collapsible", routes: "routes", selectedRouteIds: "selectedRouteIds" }, outputs: { routeSelect: "routeSelect", editRoute: "editRoute" }, ngImport: i0, template: "<div\n class=\"cards-list-container\"\n [class.floating]=\"listMode === 'floating'\"\n [class.sidebar]=\"listMode === 'sidebar'\"\n [class.inline]=\"listMode === 'inline'\"\n [class.collapsed]=\"collapsed\"\n>\n @if (listMode !== 'inline') {\n <div class=\"list-header\">\n <div class=\"header-title\">\n <span class=\"title-text\">View Route</span>\n <span class=\"routes-count\">({{ routes.length || 0 }})</span>\n </div>\n <button\n class=\"collapse-btn\"\n (click)=\"toggleCollapse()\"\n [title]=\"collapsed ? 'Expand' : 'Collapse'\"\n >\n @if (collapsed) {\n <lucide-icon [img]=\"icons.ChevronRight\" [size]=\"16\"></lucide-icon>\n } @else {\n <lucide-icon [img]=\"icons.ChevronLeft\" [size]=\"16\"></lucide-icon>\n }\n </button>\n </div>\n } \n \n @if (!collapsed) {\n <div class=\"cards-scroll-container\" #scrollContainer>\n <div class=\"cards-list\">\n @for (route of routes; track getRouteId(route)) {\n <div class=\"card-wrapper\" (click)=\"toggleRouteSelection(route)\">\n <div\n class=\"route-card bg-white dark:bg-slate-800 border transition-all duration-200\"\n [class.border-brand-blue]=\"selectedRouteIds.includes(getRouteId(route))\"\n [class.border-gray-200]=\"!selectedRouteIds.includes(getRouteId(route))\"\n [class.dark:border-slate-700]=\"!selectedRouteIds.includes(getRouteId(route))\"\n [class.selected]=\"selectedRouteIds.includes(getRouteId(route))\"\n >\n <div class=\"task-header justify-between items-start mb-1\">\n <h2 class=\"text-sm font-semibold text-gray-900 dark:text-white truncate max-w-[80%]\">\n {{ route.route_name || route.order_number || 'Unnamed Route' }}\n </h2>\n <div class=\"flex gap-2 shrink-0\">\n <div class=\"statusunit text-white\" [ngClass]=\"route.unit || ''\">\n {{ route.unit?.charAt(0) || 'U' }}\n </div>\n <button\n type=\"button\"\n class=\"text-gray-400 hover:text-brand-blue transition-colors\"\n (click)=\"$event.stopPropagation(); selectedRoute.set(route); showRouteModal = true\"\n >\n <lucide-icon [img]=\"icons.Info\" [size]=\"18\"></lucide-icon>\n </button>\n </div>\n </div>\n \n @if (route.customer_name) {\n <div class=\"text-[10px] text-gray-500 dark:text-slate-500 mb-2 uppercase tracking-wider font-medium truncate\">\n {{ route.customer_name }}\n </div>\n }\n\n <div class=\"location-flow bg-gray-50 dark:bg-slate-900/50 border border-gray-100 dark:border-slate-700/50 p-2 rounded-lg\">\n <div class=\"text-[11px] truncate flex items-center gap-1.5 text-gray-600 dark:text-gray-400\">\n <div class=\"w-1.5 h-1.5 rounded-full bg-green-500 shrink-0\"></div>\n {{ (route.pickup_location || '').split('|')[1] || route.pickup_location || 'N/A' }}\n </div>\n <div class=\"text-[11px] truncate flex items-center gap-1.5 mt-1 text-gray-600 dark:text-gray-400\">\n <div class=\"w-1.5 h-1.5 rounded-full bg-blue-500 shrink-0\"></div>\n {{ (route.delivery_location || '').split('|')[1] || route.delivery_location || 'N/A' }}\n </div>\n </div>\n </div>\n </div>\n } @empty {\n <div class=\"p-8 text-center text-gray-400 text-sm italic\">\n No routes found in this catalog.\n </div>\n }\n </div>\n </div>\n }\n</div>\n\n<lib-route-info-card \n [(modalOpen)]=\"showRouteModal\" \n [routeData]=\"selectedRoute()\" \n [repository]=\"config.repository\" \n displayMode=\"catalog\"\n (edit)=\"editRoute.emit(selectedRoute()!)\"\n></lib-route-info-card>\n", styles: [":host{display:block}.cards-list-container{display:flex;flex-direction:column;height:100%;background:var(--bg-primary, white)}.cards-list-container.floating{position:absolute;top:12px;left:12px;width:280px;max-height:calc(100% - 24px);border-radius:12px;box-shadow:0 4px 24px #00000026;z-index:100;overflow:hidden}.cards-list-container.floating.collapsed{width:48px}.cards-list-container.sidebar{width:100%;border-right:1px solid var(--border-color, #e2e8f0)}.cards-list-container.inline{width:160px;min-width:160px;border-right:1px solid var(--border-color, #e2e8f0)}:host-context(.dark) .cards-list-container{background-color:#1e293b}:host-context(.dark) .cards-list-container.sidebar,:host-context(.dark) .cards-list-container.inline{border-color:#334155}.list-header{display:flex;align-items:center;justify-content:space-between;padding:12px;border-bottom:1px solid var(--border-color, #e2e8f0);background:var(--bg-secondary, #f8fafc)}:host-context(.dark) .list-header{background-color:#0f172a;border-color:#334155}.collapsed .list-header{padding:12px 8px;justify-content:center}.header-title{display:flex;align-items:center;gap:6px}.collapsed .header-title{display:none}.title-text{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b)}:host-context(.dark) .title-text{color:#f1f5f9}.cards-scroll-container{flex:1;overflow-y:auto;overflow-x:hidden}.cards-list{display:flex;flex-direction:column;gap:8px;padding:8px}.card-wrapper{border-radius:8px;transition:all .2s ease}.route-card{position:relative;padding:6px 10px;display:flex;flex-direction:column;gap:2px;border-radius:8px;cursor:pointer;transition:all .15s ease;min-height:72px}.route-card:hover{border-color:var(--accent-color, #3b82f6);box-shadow:0 2px 8px #00000014}.task-header{display:flex;align-items:center;gap:6px;margin-bottom:4px}.job-code{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b);white-space:nowrap;text-overflow:ellipsis;max-width:190px;overflow:hidden}:host-context(.dark) .job-code{color:#f1f5f9}.statusunit{border-radius:30px;font-size:12px;text-transform:capitalize;font-weight:600;width:20px;height:20px;text-align:center;line-height:20px}.statusunit.Ton{background:#22c55e}.statusunit.Load{background:#3b82f6}.statusunit.Hourly{background:#f59e0b}.location-flow{display:flex;flex-direction:column;align-items:flex-start;gap:2px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:inline;align-items:center;gap:3px;color:#22c55e;white-space:nowrap;text-overflow:ellipsis;max-width:200px;overflow:hidden;position:relative;padding-left:15px}.location-flow .pickup svg{width:8px;height:8px;position:absolute;left:0;top:5px}.location-flow .delivery{display:inline;align-items:center;gap:3px;color:#ef4444;white-space:nowrap;text-overflow:ellipsis;max-width:200px;overflow:hidden;position:relative;padding-left:15px;top:5px}.location-flow .delivery svg{width:8px;height:8px;position:absolute;left:0;top:5px}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: RouteInfoCardComponent, selector: "lib-route-info-card", inputs: ["modalOpen", "routeData", "repository", "displayMode"], outputs: ["modalOpenChange", "edit"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
440
+ }
441
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ViewRouteListComponent, decorators: [{
442
+ type: Component,
443
+ args: [{ selector: 'lib-view-route-list', standalone: true, imports: [LucideAngularModule, RouteInfoCardComponent, NgClass], template: "<div\n class=\"cards-list-container\"\n [class.floating]=\"listMode === 'floating'\"\n [class.sidebar]=\"listMode === 'sidebar'\"\n [class.inline]=\"listMode === 'inline'\"\n [class.collapsed]=\"collapsed\"\n>\n @if (listMode !== 'inline') {\n <div class=\"list-header\">\n <div class=\"header-title\">\n <span class=\"title-text\">View Route</span>\n <span class=\"routes-count\">({{ routes.length || 0 }})</span>\n </div>\n <button\n class=\"collapse-btn\"\n (click)=\"toggleCollapse()\"\n [title]=\"collapsed ? 'Expand' : 'Collapse'\"\n >\n @if (collapsed) {\n <lucide-icon [img]=\"icons.ChevronRight\" [size]=\"16\"></lucide-icon>\n } @else {\n <lucide-icon [img]=\"icons.ChevronLeft\" [size]=\"16\"></lucide-icon>\n }\n </button>\n </div>\n } \n \n @if (!collapsed) {\n <div class=\"cards-scroll-container\" #scrollContainer>\n <div class=\"cards-list\">\n @for (route of routes; track getRouteId(route)) {\n <div class=\"card-wrapper\" (click)=\"toggleRouteSelection(route)\">\n <div\n class=\"route-card bg-white dark:bg-slate-800 border transition-all duration-200\"\n [class.border-brand-blue]=\"selectedRouteIds.includes(getRouteId(route))\"\n [class.border-gray-200]=\"!selectedRouteIds.includes(getRouteId(route))\"\n [class.dark:border-slate-700]=\"!selectedRouteIds.includes(getRouteId(route))\"\n [class.selected]=\"selectedRouteIds.includes(getRouteId(route))\"\n >\n <div class=\"task-header justify-between items-start mb-1\">\n <h2 class=\"text-sm font-semibold text-gray-900 dark:text-white truncate max-w-[80%]\">\n {{ route.route_name || route.order_number || 'Unnamed Route' }}\n </h2>\n <div class=\"flex gap-2 shrink-0\">\n <div class=\"statusunit text-white\" [ngClass]=\"route.unit || ''\">\n {{ route.unit?.charAt(0) || 'U' }}\n </div>\n <button\n type=\"button\"\n class=\"text-gray-400 hover:text-brand-blue transition-colors\"\n (click)=\"$event.stopPropagation(); selectedRoute.set(route); showRouteModal = true\"\n >\n <lucide-icon [img]=\"icons.Info\" [size]=\"18\"></lucide-icon>\n </button>\n </div>\n </div>\n \n @if (route.customer_name) {\n <div class=\"text-[10px] text-gray-500 dark:text-slate-500 mb-2 uppercase tracking-wider font-medium truncate\">\n {{ route.customer_name }}\n </div>\n }\n\n <div class=\"location-flow bg-gray-50 dark:bg-slate-900/50 border border-gray-100 dark:border-slate-700/50 p-2 rounded-lg\">\n <div class=\"text-[11px] truncate flex items-center gap-1.5 text-gray-600 dark:text-gray-400\">\n <div class=\"w-1.5 h-1.5 rounded-full bg-green-500 shrink-0\"></div>\n {{ (route.pickup_location || '').split('|')[1] || route.pickup_location || 'N/A' }}\n </div>\n <div class=\"text-[11px] truncate flex items-center gap-1.5 mt-1 text-gray-600 dark:text-gray-400\">\n <div class=\"w-1.5 h-1.5 rounded-full bg-blue-500 shrink-0\"></div>\n {{ (route.delivery_location || '').split('|')[1] || route.delivery_location || 'N/A' }}\n </div>\n </div>\n </div>\n </div>\n } @empty {\n <div class=\"p-8 text-center text-gray-400 text-sm italic\">\n No routes found in this catalog.\n </div>\n }\n </div>\n </div>\n }\n</div>\n\n<lib-route-info-card \n [(modalOpen)]=\"showRouteModal\" \n [routeData]=\"selectedRoute()\" \n [repository]=\"config.repository\" \n displayMode=\"catalog\"\n (edit)=\"editRoute.emit(selectedRoute()!)\"\n></lib-route-info-card>\n", styles: [":host{display:block}.cards-list-container{display:flex;flex-direction:column;height:100%;background:var(--bg-primary, white)}.cards-list-container.floating{position:absolute;top:12px;left:12px;width:280px;max-height:calc(100% - 24px);border-radius:12px;box-shadow:0 4px 24px #00000026;z-index:100;overflow:hidden}.cards-list-container.floating.collapsed{width:48px}.cards-list-container.sidebar{width:100%;border-right:1px solid var(--border-color, #e2e8f0)}.cards-list-container.inline{width:160px;min-width:160px;border-right:1px solid var(--border-color, #e2e8f0)}:host-context(.dark) .cards-list-container{background-color:#1e293b}:host-context(.dark) .cards-list-container.sidebar,:host-context(.dark) .cards-list-container.inline{border-color:#334155}.list-header{display:flex;align-items:center;justify-content:space-between;padding:12px;border-bottom:1px solid var(--border-color, #e2e8f0);background:var(--bg-secondary, #f8fafc)}:host-context(.dark) .list-header{background-color:#0f172a;border-color:#334155}.collapsed .list-header{padding:12px 8px;justify-content:center}.header-title{display:flex;align-items:center;gap:6px}.collapsed .header-title{display:none}.title-text{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b)}:host-context(.dark) .title-text{color:#f1f5f9}.cards-scroll-container{flex:1;overflow-y:auto;overflow-x:hidden}.cards-list{display:flex;flex-direction:column;gap:8px;padding:8px}.card-wrapper{border-radius:8px;transition:all .2s ease}.route-card{position:relative;padding:6px 10px;display:flex;flex-direction:column;gap:2px;border-radius:8px;cursor:pointer;transition:all .15s ease;min-height:72px}.route-card:hover{border-color:var(--accent-color, #3b82f6);box-shadow:0 2px 8px #00000014}.task-header{display:flex;align-items:center;gap:6px;margin-bottom:4px}.job-code{font-size:13px;font-weight:600;color:var(--text-primary, #1e293b);white-space:nowrap;text-overflow:ellipsis;max-width:190px;overflow:hidden}:host-context(.dark) .job-code{color:#f1f5f9}.statusunit{border-radius:30px;font-size:12px;text-transform:capitalize;font-weight:600;width:20px;height:20px;text-align:center;line-height:20px}.statusunit.Ton{background:#22c55e}.statusunit.Load{background:#3b82f6}.statusunit.Hourly{background:#f59e0b}.location-flow{display:flex;flex-direction:column;align-items:flex-start;gap:2px;font-size:11px;color:var(--text-secondary, #64748b);margin-bottom:6px}.location-flow .pickup{display:inline;align-items:center;gap:3px;color:#22c55e;white-space:nowrap;text-overflow:ellipsis;max-width:200px;overflow:hidden;position:relative;padding-left:15px}.location-flow .pickup svg{width:8px;height:8px;position:absolute;left:0;top:5px}.location-flow .delivery{display:inline;align-items:center;gap:3px;color:#ef4444;white-space:nowrap;text-overflow:ellipsis;max-width:200px;overflow:hidden;position:relative;padding-left:15px;top:5px}.location-flow .delivery svg{width:8px;height:8px;position:absolute;left:0;top:5px}\n"] }]
444
+ }], propDecorators: { listMode: [{
445
+ type: Input
446
+ }], collapsible: [{
447
+ type: Input
448
+ }], routes: [{
449
+ type: Input
450
+ }], selectedRouteIds: [{
451
+ type: Input
452
+ }], routeSelect: [{
453
+ type: Output
454
+ }], editRoute: [{
455
+ type: Output
456
+ }] } });
457
+
458
+ class AddRouteComponent {
459
+ config = inject(COOLMAP_CONFIG);
460
+ modalOpen = input(false, ...(ngDevMode ? [{ debugName: "modalOpen" }] : []));
461
+ routeData = input(null, ...(ngDevMode ? [{ debugName: "routeData" }] : []));
462
+ customerRepoDetails = input(null, ...(ngDevMode ? [{ debugName: "customerRepoDetails" }] : []));
463
+ modalOpenChange = new EventEmitter();
464
+ routeDeleted = new EventEmitter();
465
+ routeSaved = new EventEmitter();
466
+ dragX = signal(0, ...(ngDevMode ? [{ debugName: "dragX" }] : []));
467
+ dragY = signal(0, ...(ngDevMode ? [{ debugName: "dragY" }] : []));
468
+ isDragging = false;
469
+ dragStartX = 0;
470
+ dragStartY = 0;
471
+ dragInitialX = 0;
472
+ dragInitialY = 0;
473
+ // New Services
474
+ utils = inject(UtilsService);
475
+ coolMapService = inject(CoolmapService);
476
+ destroyRef = inject(DestroyRef);
477
+ cdr = inject(ChangeDetectorRef);
478
+ addRouteForm = new FormGroup({
479
+ route_name: new FormControl('', [Validators.required]),
480
+ customer_name: new FormControl({ value: '', disabled: true }, [Validators.required]),
481
+ customer_id: new FormControl(''),
482
+ unit_id: new FormControl('', [Validators.required]),
483
+ path: new FormControl('', [Validators.required]),
484
+ pickup_location: new FormControl('', [Validators.required]),
485
+ pickup_lat_lng: new FormControl('', [Validators.required]),
486
+ delivery_location: new FormControl('', [Validators.required]),
487
+ delivery_lat_lng: new FormControl('', [Validators.required]),
488
+ estimated_distance: new FormControl('', [Validators.required]),
489
+ estimated_time: new FormControl('', [Validators.required]),
490
+ note: new FormControl('', [Validators.maxLength(2048)]),
491
+ trucker_pay_estimate: new FormControl(null),
492
+ customer_price_estimate: new FormControl(null),
493
+ pickUpSearchOption: new FormControl(inject(COOLMAP_CONFIG).repository === 'coolmap' ? 'system' : 'google'),
494
+ deliverySearchOtption: new FormControl(inject(COOLMAP_CONFIG).repository === 'coolmap' ? 'system' : 'google'),
495
+ });
496
+ // Data signals
497
+ unitsList = signal([], ...(ngDevMode ? [{ debugName: "unitsList" }] : []));
498
+ locationList = signal([], ...(ngDevMode ? [{ debugName: "locationList" }] : []));
499
+ customersList = signal([], ...(ngDevMode ? [{ debugName: "customersList" }] : []));
500
+ showDeleteModal = signal(false, ...(ngDevMode ? [{ debugName: "showDeleteModal" }] : []));
501
+ routeId = signal(null, ...(ngDevMode ? [{ debugName: "routeId" }] : []));
502
+ preventSave = signal(false, ...(ngDevMode ? [{ debugName: "preventSave" }] : []));
503
+ preventInitialSave = signal(false, ...(ngDevMode ? [{ debugName: "preventInitialSave" }] : []));
504
+ // Search options
505
+ customerOptions = signal([], ...(ngDevMode ? [{ debugName: "customerOptions" }] : []));
506
+ pickupOptions = signal([], ...(ngDevMode ? [{ debugName: "pickupOptions" }] : []));
507
+ deliveryOptions = signal([], ...(ngDevMode ? [{ debugName: "deliveryOptions" }] : []));
508
+ // UI toggles
509
+ showCustomerDropdown = signal(false, ...(ngDevMode ? [{ debugName: "showCustomerDropdown" }] : []));
510
+ showPickupDropdown = signal(false, ...(ngDevMode ? [{ debugName: "showPickupDropdown" }] : []));
511
+ showDeliveryDropdown = signal(false, ...(ngDevMode ? [{ debugName: "showDeliveryDropdown" }] : []));
512
+ loadingLocations = signal(false, ...(ngDevMode ? [{ debugName: "loadingLocations" }] : []));
513
+ loadingCustomers = signal(false, ...(ngDevMode ? [{ debugName: "loadingCustomers" }] : []));
514
+ hideDropdown(type) {
515
+ setTimeout(() => {
516
+ if (type === 'customer')
517
+ this.showCustomerDropdown.set(false);
518
+ if (type === 'pickup')
519
+ this.showPickupDropdown.set(false);
520
+ if (type === 'delivery')
521
+ this.showDeliveryDropdown.set(false);
522
+ }, 200);
523
+ }
524
+ // ElementRefs for Google Maps integration
525
+ filterPickup = viewChild('filterPickup', ...(ngDevMode ? [{ debugName: "filterPickup" }] : []));
526
+ filterDelivery = viewChild('filterDelivery', ...(ngDevMode ? [{ debugName: "filterDelivery" }] : []));
527
+ get isSystemPickup() {
528
+ return this.addRouteForm.get('pickUpSearchOption')?.value === 'system';
529
+ }
530
+ get isSystemDelivery() {
531
+ return this.addRouteForm.get('deliverySearchOtption')?.value === 'system';
532
+ }
533
+ get selectedUnitName() {
534
+ const unitId = this.addRouteForm.value.unit_id;
535
+ if (!unitId)
536
+ return 'Select Unit/Driver';
537
+ const unit = this.unitsList().find(u => u.id === unitId);
538
+ return unit ? unit.type : 'Select Unit/Driver';
539
+ }
540
+ constructor() {
541
+ // Effect for handling routeData Input
542
+ effect(() => {
543
+ const data = this.routeData();
544
+ setTimeout(() => {
545
+ this.inIt(data);
546
+ }, 0);
547
+ });
548
+ effect(() => {
549
+ if (this.modalOpen()) {
550
+ this.coolMapService.clearAllRoutes();
551
+ this.utils.fetchUnitsList().then((res) => {
552
+ this.unitsList.set(res || []);
553
+ });
554
+ if (this.config.repository === 'coolmap') {
555
+ this.loadingLocations.set(true);
556
+ this.addRouteForm.get('pickup_location')?.disable();
557
+ this.addRouteForm.get('delivery_location')?.disable();
558
+ this.utils.fetchLocationlist().then((res) => {
559
+ this.locationList.set(res || []);
560
+ this.pickupOptions.set(res || []);
561
+ this.deliveryOptions.set(res || []);
562
+ this.loadingLocations.set(false);
563
+ this.addRouteForm.get('pickup_location')?.enable();
564
+ this.addRouteForm.get('delivery_location')?.enable();
565
+ });
566
+ this.loadingCustomers.set(true);
567
+ this.utils.fetchCustomersList().then((res) => {
568
+ this.customersList.set(res || []);
569
+ this.customerOptions.set(res || []);
570
+ this.addRouteForm.controls['customer_name'].enable();
571
+ this.loadingCustomers.set(false);
572
+ });
573
+ }
574
+ setTimeout(() => {
575
+ this.bindGoogleAutocomplete();
576
+ }, 150);
577
+ }
578
+ });
579
+ // Reacting to dropdown changes for re-running calculations
580
+ this.addRouteForm.get('pickup_location')?.valueChanges.pipe(takeUntilDestroyed()).subscribe(res => {
581
+ if (res && res !== this.addRouteForm.getRawValue().pickup_location) {
582
+ this.addRouteForm.patchValue({ pickup_lat_lng: '' });
583
+ }
584
+ // Populate dropdown for system
585
+ if (this.isSystemPickup) {
586
+ if (!res) {
587
+ this.pickupOptions.set(this.locationList());
588
+ }
589
+ else {
590
+ const lower = res.toLowerCase();
591
+ this.pickupOptions.set(this.locationList().filter(l => l.formatted_address.toLowerCase().includes(lower)));
592
+ }
593
+ }
594
+ });
595
+ this.addRouteForm.get('delivery_location')?.valueChanges.pipe(takeUntilDestroyed()).subscribe(res => {
596
+ if (res && res !== this.addRouteForm.getRawValue().delivery_location) {
597
+ this.addRouteForm.patchValue({ delivery_lat_lng: '' });
598
+ }
599
+ if (this.isSystemDelivery) {
600
+ if (!res) {
601
+ this.deliveryOptions.set(this.locationList());
602
+ }
603
+ else {
604
+ const lower = res.toLowerCase();
605
+ this.deliveryOptions.set(this.locationList().filter(l => l.formatted_address.toLowerCase().includes(lower)));
606
+ }
607
+ }
608
+ });
609
+ this.addRouteForm.get('customer_name')?.valueChanges.pipe(takeUntilDestroyed()).subscribe(res => {
610
+ if (!res) {
611
+ this.customerOptions.set(this.customersList());
612
+ }
613
+ else {
614
+ const lower = res.toLowerCase();
615
+ this.customerOptions.set(this.customersList().filter(c => c.customer_name.toLowerCase().includes(lower)));
616
+ }
617
+ });
618
+ // Clear path if logic changes
619
+ this.addRouteForm.valueChanges.pipe(takeUntilDestroyed(), pairwise(), filter(([prev, curr]) => {
620
+ return (prev.pickUpSearchOption === curr.pickUpSearchOption) &&
621
+ prev.deliverySearchOtption === curr.deliverySearchOtption;
622
+ })).subscribe(() => {
623
+ if (this.preventInitialSave()) {
624
+ this.preventInitialSave.set(false);
625
+ }
626
+ });
627
+ }
628
+ ngAfterViewInit() {
629
+ // Listen for search mode changes
630
+ this.addRouteForm.get('pickUpSearchOption')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
631
+ this.addRouteForm.patchValue({ pickup_location: null, pickup_lat_lng: null });
632
+ });
633
+ this.addRouteForm.get('deliverySearchOtption')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
634
+ this.addRouteForm.patchValue({ delivery_location: null, delivery_lat_lng: null });
635
+ });
636
+ }
637
+ async onGooglePlaceSelect(event, type) {
638
+ console.log('event', event);
639
+ const place = event.placePrediction?.toPlace();
640
+ if (!place)
641
+ return;
642
+ // The google API wrapper may require raw DOM event access depending on module configuration
643
+ try {
644
+ await place.fetchFields({ fields: ['location', 'formattedAddress', 'displayName'] });
645
+ const res = {
646
+ lat: place.location.lat(),
647
+ lng: place.location.lng(),
648
+ formatted_address: place.formattedAddress || place.displayName
649
+ };
650
+ this.patchAddressValue(res, type);
651
+ }
652
+ catch (e) {
653
+ console.warn("Failed to retrieve Place data natively: ", e);
654
+ }
655
+ }
656
+ bindGoogleAutocomplete() {
657
+ const pickupEl = this.filterPickup()?.nativeElement;
658
+ const deliveryEl = this.filterDelivery()?.nativeElement;
659
+ if (pickupEl) {
660
+ const autocomplete = new window.google.maps.places.Autocomplete(pickupEl);
661
+ autocomplete.addListener('place_changed', () => {
662
+ const place = autocomplete.getPlace();
663
+ if (place.geometry) {
664
+ const res = {
665
+ lat: place.geometry.location.lat(),
666
+ lng: place.geometry.location.lng(),
667
+ formatted_address: place.formatted_address || place.name
668
+ };
669
+ this.patchAddressValue(res, 'pickup');
670
+ }
671
+ });
672
+ }
673
+ if (deliveryEl) {
674
+ const autocomplete = new window.google.maps.places.Autocomplete(deliveryEl);
675
+ autocomplete.addListener('place_changed', () => {
676
+ const place = autocomplete.getPlace();
677
+ if (place.geometry) {
678
+ const res = {
679
+ lat: place.geometry.location.lat(),
680
+ lng: place.geometry.location.lng(),
681
+ formatted_address: place.formatted_address || place.name
682
+ };
683
+ this.patchAddressValue(res, 'delivery');
684
+ }
685
+ });
686
+ }
687
+ }
688
+ inIt(changes) {
689
+ if (changes && Object.keys(changes).length > 0) {
690
+ if (!changes.prevent) {
691
+ this.preventSave.set(false);
692
+ this.patchFormValue(changes).then(() => {
693
+ this.checkAndFetchRouteInformation(true);
694
+ });
695
+ }
696
+ }
697
+ else {
698
+ this.preventSave.set(true);
699
+ this.patchFormValue().then(() => {
700
+ this.checkAndFetchRouteInformation();
701
+ });
702
+ }
703
+ }
704
+ async patchFormValue(data) {
705
+ this.addRouteForm.reset();
706
+ const isCustomerRepo = this.config.repository === 'customer';
707
+ if (data) {
708
+ this.addRouteForm.patchValue(data, { emitEvent: false });
709
+ }
710
+ if (isCustomerRepo && this.customerRepoDetails()) {
711
+ this.addRouteForm.patchValue({
712
+ customer_id: this.customerRepoDetails().customer?.id,
713
+ customer_name: this.customerRepoDetails().customer?.name,
714
+ pickUpSearchOption: 'google',
715
+ deliverySearchOtption: 'google'
716
+ }, { emitEvent: false });
717
+ }
718
+ else {
719
+ this.addRouteForm.patchValue({
720
+ pickUpSearchOption: (!data || (data.hasOwnProperty('is_system_pickup') ? data.is_system_pickup : true)) ? 'system' : 'google',
721
+ deliverySearchOtption: (!data || (data.hasOwnProperty('is_system_delivery') ? data.is_system_delivery : true)) ? 'system' : 'google',
722
+ }, { emitEvent: false });
723
+ }
724
+ this.routeId.set(data?.route_id || data?.id || null);
725
+ await true;
726
+ }
727
+ patchAddressValue(data, type) {
728
+ this.addRouteForm.patchValue({
729
+ [type + '_location']: data.formatted_address,
730
+ [type + '_lat_lng']: `${data.lat},${data.lng}`
731
+ });
732
+ this.checkAndFetchRouteInformation();
733
+ }
734
+ checkAndFetchRouteInformation(isinitial) {
735
+ const val = this.addRouteForm.value;
736
+ if (val.delivery_lat_lng && val.pickup_lat_lng && val.unit_id) {
737
+ const unit = this.unitsList().find((x) => x.id === val.unit_id);
738
+ const param = {
739
+ delivery_lat_lng: val.delivery_lat_lng,
740
+ pickup_lat_lng: val.pickup_lat_lng,
741
+ unit: unit?.type || 'Ton'
742
+ };
743
+ // Mapbox estimation route
744
+ this.utils.postdata('calculate/routes/estimation', param).subscribe({
745
+ next: (res) => {
746
+ setTimeout(() => {
747
+ if (res && res.path) {
748
+ this.addRouteForm.patchValue({
749
+ estimated_distance: res.dist,
750
+ estimated_time: res.time,
751
+ path: res.path,
752
+ trucker_pay_estimate: res.trucker_haul_cost,
753
+ customer_price_estimate: res.customer_price_estimate
754
+ });
755
+ // Decode and plot
756
+ let path = res.path.split(';').map((str) => {
757
+ const parts = str.split(',');
758
+ return [parseFloat(parts[1]), parseFloat(parts[0])];
759
+ });
760
+ const element = {
761
+ type: 'FeatureCollection',
762
+ features: [{ type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: path } }]
763
+ };
764
+ this.coolMapService.removeRouteAndMarker(1).then(() => {
765
+ this.coolMapService.loadMapProperty(element, 1, unit?.type, {
766
+ pickup_location: val.pickup_location,
767
+ delivery_location: val.delivery_location,
768
+ route_name: val.route_name,
769
+ unit: unit?.type
770
+ });
771
+ this.preventSave.set(true);
772
+ this.cdr.detectChanges();
773
+ });
774
+ }
775
+ else {
776
+ console.warn('Could not calculate route');
777
+ this.preventSave.set(true);
778
+ this.utils.openSnackBar((res?.status ? res.status : 'Somthing went wrong, Please try different address.'), 'error');
779
+ this.cdr.detectChanges();
780
+ }
781
+ if (this.routeId() && isinitial) {
782
+ this.preventInitialSave.set(true);
783
+ this.cdr.detectChanges();
784
+ }
785
+ });
786
+ }
787
+ });
788
+ }
789
+ else {
790
+ this.coolMapService.removeRouteAndMarker(1, 'addroute');
791
+ }
792
+ }
793
+ saveRoute() {
794
+ if (this.addRouteForm.valid) {
795
+ const data = this.addRouteForm.getRawValue();
796
+ const endpoint = this.routeId() ? 'update/routes' : 'add/routes';
797
+ if (this.routeId())
798
+ data.id = this.routeId();
799
+ this.utils.postdata(endpoint, data).subscribe({
800
+ next: (res) => {
801
+ if (res.success) {
802
+ this.utils.openSnackBar(res.message, 'success');
803
+ // Emit the saved data for local synchronization
804
+ this.routeSaved.emit(data);
805
+ this.close();
806
+ }
807
+ else {
808
+ this.utils.openSnackBar(res.message, 'error');
809
+ }
810
+ }
811
+ });
812
+ }
813
+ else {
814
+ this.addRouteForm.markAllAsTouched();
815
+ }
816
+ }
817
+ promptDelete() {
818
+ this.showDeleteModal.set(true);
819
+ }
820
+ confirmDelete() {
821
+ if (!this.routeId())
822
+ return;
823
+ this.utils.postdata('delete/routes', { uuid: this.routeId() }).subscribe({
824
+ next: (res) => {
825
+ if (res?.data?.success) {
826
+ this.utils.openSnackBar(res.data.message, 'success');
827
+ const deletedId = this.routeId();
828
+ this.routeDeleted.emit(deletedId);
829
+ this.showDeleteModal.set(false);
830
+ this.close();
831
+ }
832
+ }
833
+ });
834
+ }
835
+ close() {
836
+ const isCoolmap = this.config.repository === 'coolmap';
837
+ this.addRouteForm.reset({
838
+ pickUpSearchOption: isCoolmap ? 'system' : 'google',
839
+ deliverySearchOtption: isCoolmap ? 'system' : 'google'
840
+ });
841
+ this.coolMapService.removeRouteAndMarker(1, 'addroute');
842
+ this.modalOpenChange.emit(false);
843
+ }
844
+ icons = {
845
+ X, ChevronDown, Check, LoaderCircle
846
+ };
847
+ // Dynamic dropdown state logic
848
+ dropdownOpen = signal(false, ...(ngDevMode ? [{ debugName: "dropdownOpen" }] : []));
849
+ selectedUnitDriver = signal(null, ...(ngDevMode ? [{ debugName: "selectedUnitDriver" }] : []));
850
+ toggleDropdown() {
851
+ this.dropdownOpen.update(v => !v);
852
+ }
853
+ selectOption(opt) {
854
+ this.selectedUnitDriver.set(opt);
855
+ this.dropdownOpen.set(false);
856
+ }
857
+ closeDropdowns(event) {
858
+ // Basic click-outside wrapper
859
+ const target = event.target;
860
+ if (!target.closest('.relative')) {
861
+ this.dropdownOpen.set(false);
862
+ }
863
+ }
864
+ startDrag(event) {
865
+ const target = event.target;
866
+ if (target.tagName.toLowerCase() === 'button' || target.closest('button')) {
867
+ return;
868
+ }
869
+ this.isDragging = true;
870
+ this.dragStartX = event.clientX;
871
+ this.dragStartY = event.clientY;
872
+ this.dragInitialX = this.dragX();
873
+ this.dragInitialY = this.dragY();
874
+ event.preventDefault();
875
+ }
876
+ onMouseMove(event) {
877
+ if (!this.isDragging || !this.modalOpen())
878
+ return;
879
+ this.dragX.set(this.dragInitialX + (event.clientX - this.dragStartX));
880
+ this.dragY.set(this.dragInitialY + (event.clientY - this.dragStartY));
881
+ }
882
+ onMouseUp() {
883
+ this.isDragging = false;
884
+ }
885
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AddRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
886
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: AddRouteComponent, isStandalone: true, selector: "lib-add-route", inputs: { modalOpen: { classPropertyName: "modalOpen", publicName: "modalOpen", isSignal: true, isRequired: false, transformFunction: null }, routeData: { classPropertyName: "routeData", publicName: "routeData", isSignal: true, isRequired: false, transformFunction: null }, customerRepoDetails: { classPropertyName: "customerRepoDetails", publicName: "customerRepoDetails", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { modalOpenChange: "modalOpenChange", routeDeleted: "routeDeleted", routeSaved: "routeSaved" }, host: { listeners: { "document:click": "closeDropdowns($event)", "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp()" } }, viewQueries: [{ propertyName: "filterPickup", first: true, predicate: ["filterPickup"], descendants: true, isSignal: true }, { propertyName: "filterDelivery", first: true, predicate: ["filterDelivery"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (modalOpen()) {\n<div class=\"fixed inset-0 z-[112] pointer-events-none overflow-y-auto\">\n <div class=\"flex min-h-full items-center justify-center p-4\">\n <form\n [formGroup]=\"addRouteForm\"\n (ngSubmit)=\"saveRoute()\"\n class=\"pointer-events-auto relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[1000px]\"\n [style.transform]=\"'translate(' + dragX() + 'px, ' + dragY() + 'px)'\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between p-4 border-b border-gray-200 dark:border-slate-700 cursor-move\"\n (mousedown)=\"startDrag($event)\"\n >\n <h3 class=\"flex items-center gap-1 text-lg font-semibold text-gray-900 dark:text-white\">\n {{ routeId() ? 'Edit Route' : 'Add Route' }}\n </h3>\n <div class=\"flex items-center gap-4\">\n <button\n type=\"submit\"\n class=\"inline-flex items-center justify-center gap-2 px-4 py-2 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 text-white font-medium rounded-lg transition-colors text-sm\">\n Save\n </button>\n @if (routeId()) {\n <button\n type=\"button\"\n (click)=\"promptDelete()\"\n class=\"inline-flex items-center gap-2 px-4 py-2 bg-red-600 text-white hover:bg-red-700 font-medium rounded-lg transition-colors text-sm\"\n >\n Delete\n </button>\n }\n <button\n type=\"button\"\n class=\"inline-flex items-center gap-2 px-4 py-2 bg-gray-600 text-white hover:bg-gray-700 font-medium rounded-lg transition-colors text-sm\"\n (click)=\"close()\"\n >\n Close\n </button>\n </div>\n </div>\n <div class=\"max-h-[70vh] overflow-y-auto p-4\">\n <div class=\"grid grid-cols-2 mb-[1rem] gap-4\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Route Name</label\n >\n <input\n type=\"text\"\n formControlName=\"route_name\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm\"\n />\n </div>\n <div class=\"relative\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Customer Name</label>\n <div class=\"relative\">\n <input\n type=\"text\"\n formControlName=\"customer_name\"\n (focus)=\"showCustomerDropdown.set(true)\"\n (blur)=\"hideDropdown('customer')\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm pr-10\"\n />\n @if (loadingCustomers()) {\n <div class=\"absolute right-3 top-1/2 -translate-y-1/2\">\n <lucide-icon [img]=\"icons.LoaderCircle\" [size]=\"18\" class=\"animate-spin text-blue-500\"></lucide-icon>\n </div>\n }\n </div>\n @if (showCustomerDropdown() && customerOptions().length > 0) {\n <div class=\"absolute z-50 w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-lg max-h-48 overflow-y-auto\">\n @for (cust of customerOptions(); track cust.customer_id) {\n <div \n (mousedown)=\"addRouteForm.patchValue({customer_name: cust.customer_name})\"\n class=\"px-4 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer text-sm text-gray-900 dark:text-white\">\n {{ cust.customer_name }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n <div class=\"grid grid-cols-1 mb-[1rem]\">\n <div\n class=\"location-flow bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-3 py-3 rounded-lg\"\n >\n @if (config.repository === 'coolmap') {\n <div class=\"flex gap-2 mb-2\">\n <div class=\"radio w-100 md:w-auto\">\n <input\n type=\"radio\"\n formControlName=\"pickUpSearchOption\"\n value=\"system\"\n class=\"shrink-0 size-4 bg-transparent border-line-3 rounded-full shadow-2xs text-primary focus:ring-0 focus:ring-offset-0 checked:bg-primary-checked checked:border-primary-checked\"\n />\n <label\n class=\"border-solid border-1 border-grey dark:border-slate-400 block shadow-md rounded-lg lg:whitespace-nowrap w-full ml-2\"\n >\n <div class=\"flex justify-between items-center px-4 py-2 text-xs lg:text-base\">\n <div class=\"tracking-wide text-xs\">System Search</div>\n </div>\n </label>\n </div>\n <div class=\"radio w-100 md:w-auto ml-2\">\n <input\n type=\"radio\"\n formControlName=\"pickUpSearchOption\"\n value=\"google\"\n class=\"shrink-0 size-4 bg-transparent border-line-3 rounded-full shadow-2xs text-primary focus:ring-0 focus:ring-offset-0 checked:bg-primary-checked checked:border-primary-checked\"\n />\n <label\n class=\"border-solid border-1 border-grey dark:border-slate-400 block shadow-md rounded-lg lg:whitespace-nowrap w-full ml-2\"\n >\n <div class=\"flex justify-between items-center px-4 py-2 text-xs lg:text-base\">\n <div class=\"tracking-wide text-xs\">Google Search</div>\n </div>\n </label>\n </div>\n </div>\n }\n <div class=\"relative\">\n <input\n #filterPickup\n type=\"text\"\n formControlName=\"pickup_location\"\n placeholder=\"Pickup\"\n (focus)=\"showPickupDropdown.set(true)\"\n (blur)=\"hideDropdown('pickup')\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm pr-10\"\n />\n @if (loadingLocations()) {\n <div class=\"absolute right-3 top-1/2 -translate-y-1/2\">\n <lucide-icon [img]=\"icons.LoaderCircle\" [size]=\"18\" class=\"animate-spin text-blue-500\"></lucide-icon>\n </div>\n }\n @if (isSystemPickup && showPickupDropdown() && pickupOptions().length > 0) {\n <div class=\"absolute z-50 w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-lg max-h-48 overflow-y-auto\">\n @for (loc of pickupOptions(); track loc.formatted_address) {\n <div \n (mousedown)=\"patchAddressValue(loc, 'pickup')\"\n class=\"px-4 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer text-sm text-gray-900 dark:text-white\">\n {{ loc.formatted_address }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"grid grid-cols-1 mb-[1rem]\">\n <div\n class=\"location-flow bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-3 py-3 rounded-lg\"\n >\n @if (config.repository === 'coolmap') {\n <div class=\"flex gap-2 mb-2\">\n <div class=\"radio w-100 md:w-auto\">\n <input\n type=\"radio\"\n formControlName=\"deliverySearchOtption\"\n value=\"system\"\n class=\"shrink-0 size-4 bg-transparent border-line-3 rounded-full shadow-2xs text-primary focus:ring-0 focus:ring-offset-0 checked:bg-primary-checked checked:border-primary-checked\"/>\n <label\n class=\"border-solid border-1 border-grey dark:border-slate-400 block shadow-md rounded-lg lg:whitespace-nowrap w-full ml-2\">\n <div class=\"flex justify-between items-center px-4 py-2 text-xs lg:text-base\">\n <div class=\"tracking-wide text-xs\">System Search</div>\n </div>\n </label>\n </div>\n <div class=\"radio w-100 md:w-auto ml-2\">\n <input\n type=\"radio\"\n formControlName=\"deliverySearchOtption\"\n value=\"google\"\n class=\"shrink-0 size-4 bg-transparent border-line-3 rounded-full shadow-2xs text-primary focus:ring-0 focus:ring-offset-0 checked:bg-primary-checked checked:border-primary-checked\"/>\n <label\n class=\"border-solid border-1 border-grey dark:border-slate-400 block shadow-md rounded-lg lg:whitespace-nowrap w-full ml-2\">\n <div class=\"flex justify-between items-center px-4 py-2 text-xs lg:text-base\">\n <div class=\"tracking-wide text-xs\">Google Search</div>\n </div>\n </label>\n </div>\n </div>\n }\n <div class=\"relative\">\n <input\n #filterDelivery\n type=\"text\"\n formControlName=\"delivery_location\"\n placeholder=\"Delivery\"\n (focus)=\"showDeliveryDropdown.set(true)\"\n (blur)=\"hideDropdown('delivery')\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm pr-10\"\n />\n @if (loadingLocations()) {\n <div class=\"absolute right-3 top-1/2 -translate-y-1/2\">\n <lucide-icon [img]=\"icons.LoaderCircle\" [size]=\"18\" class=\"animate-spin text-blue-500\"></lucide-icon>\n </div>\n }\n @if (isSystemDelivery && showDeliveryDropdown() && deliveryOptions().length > 0) {\n <div class=\"absolute z-50 w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-lg max-h-48 overflow-y-auto\">\n @for (loc of deliveryOptions(); track loc.formatted_address) {\n <div \n (mousedown)=\"patchAddressValue(loc, 'delivery')\"\n class=\"px-4 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer text-sm text-gray-900 dark:text-white\">\n {{ loc.formatted_address }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"flex gap-4 flex-col md:flex-row\">\n <div class=\"w-full md:w-[70%]\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Notes</label\n >\n <textarea\n formControlName=\"note\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm h-[46px]\"\n ></textarea>\n </div>\n <div class=\"w-full md:w-[30%]\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Unit Type</label\n >\n <div class=\"relative w-full\">\n <button\n type=\"button\"\n (click)=\"toggleDropdown(); $event.stopPropagation()\"\n class=\"w-full flex justify-between items-center px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm text-left\"\n >\n <span>{{ selectedUnitName }}</span>\n <lucide-icon [img]=\"icons.ChevronDown\" [size]=\"16\" class=\"text-gray-400\"></lucide-icon>\n </button>\n @if (dropdownOpen()) {\n <div class=\"absolute z-10 mt-1 w-full bg-white dark:bg-slate-800 rounded-lg shadow-lg border border-gray-200 dark:border-slate-700 py-1 max-h-60 overflow-auto\">\n @for (opt of unitsList(); track opt.id) {\n <button\n type=\"button\"\n (click)=\"addRouteForm.patchValue({unit_id: opt.id}); checkAndFetchRouteInformation(); dropdownOpen.set(false)\"\n class=\"w-full flex items-center justify-between px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-slate-700 transition\"\n >\n <span>{{ opt.type }}</span>\n @if (addRouteForm.value.unit_id === opt.id) {\n <lucide-icon [img]=\"icons.Check\" [size]=\"16\" class=\"text-blue-500\"></lucide-icon>\n }\n </button>\n }\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"grid grid-cols-2 mt-2 gap-4\">\n <div class=\"flex flex-col lg:flex-row gap-2\">\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\"\n >\n Estimation of miles - <b>{{ addRouteForm.value.estimated_distance || '0' }} Miles</b>\n </span>\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\"\n >\n Estimation of time - <b>{{ addRouteForm.value.estimated_time || '0' }} Minutes</b>\n </span>\n </div>\n <div class=\"flex flex-col lg:flex-row gap-2 justify-end\">\n @if (config.repository !== 'customer') {\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\"\n >\n Trucker Pay Estimate - <b>{{ addRouteForm.value.trucker_pay_estimate | currency }}</b>\n </span>\n }\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\"\n >\n Customer Price Estimate - <b>{{ addRouteForm.value.customer_price_estimate | currency }}</b>\n </span>\n \n </div>\n </div>\n </div>\n </form>\n\n @if (showDeleteModal()) {\n <div class=\"fixed inset-0 z-[120] flex items-center justify-center bg-black/60 backdrop-blur-sm animate-fade-in pointer-events-auto\">\n <div class=\"bg-white dark:bg-slate-900 rounded-xl shadow-2xl p-6 max-w-sm w-full mx-4 border border-gray-200 dark:border-slate-800\" (click)=\"$event.stopPropagation()\">\n <h3 class=\"text-lg font-bold text-gray-900 dark:text-white mb-2\">Delete Route?</h3>\n <p class=\"text-gray-600 dark:text-gray-400 text-sm mb-6\">\n Are you sure you want to delete this route?\n </p>\n <div class=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n (click)=\"showDeleteModal.set(false)\"\n class=\"px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 dark:text-gray-300 dark:bg-slate-800 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n (click)=\"confirmDelete()\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-lg shadow-sm transition-colors\"\n >\n Delete\n </button>\n </div>\n </div>\n </div>\n }\n\n </div>\n</div>\n}\n", styles: ["@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}.radio{position:relative}.radio input{position:absolute;top:50%;transform:translateY(-50%);left:0;opacity:0;width:100%;height:100%;cursor:pointer}.radio svg{opacity:.5}.radio label{height:auto;border-width:1px;border-color:#334155}.radio input:checked+label{border-color:#3b558e;background-color:#334155;color:#fff}.radio input:checked+label svg{opacity:1}.radio label:hover{cursor:pointer}.grid-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:10px}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i2.CurrencyPipe, name: "currency" }] });
887
+ }
888
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AddRouteComponent, decorators: [{
889
+ type: Component,
890
+ args: [{ selector: 'lib-add-route', standalone: true, imports: [LucideAngularModule, ReactiveFormsModule, CommonModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "@if (modalOpen()) {\n<div class=\"fixed inset-0 z-[112] pointer-events-none overflow-y-auto\">\n <div class=\"flex min-h-full items-center justify-center p-4\">\n <form\n [formGroup]=\"addRouteForm\"\n (ngSubmit)=\"saveRoute()\"\n class=\"pointer-events-auto relative bg-white dark:bg-slate-800 rounded-lg shadow-xl animate-slide-up w-[1000px]\"\n [style.transform]=\"'translate(' + dragX() + 'px, ' + dragY() + 'px)'\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"flex items-center justify-between p-4 border-b border-gray-200 dark:border-slate-700 cursor-move\"\n (mousedown)=\"startDrag($event)\"\n >\n <h3 class=\"flex items-center gap-1 text-lg font-semibold text-gray-900 dark:text-white\">\n {{ routeId() ? 'Edit Route' : 'Add Route' }}\n </h3>\n <div class=\"flex items-center gap-4\">\n <button\n type=\"submit\"\n class=\"inline-flex items-center justify-center gap-2 px-4 py-2 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 text-white font-medium rounded-lg transition-colors text-sm\">\n Save\n </button>\n @if (routeId()) {\n <button\n type=\"button\"\n (click)=\"promptDelete()\"\n class=\"inline-flex items-center gap-2 px-4 py-2 bg-red-600 text-white hover:bg-red-700 font-medium rounded-lg transition-colors text-sm\"\n >\n Delete\n </button>\n }\n <button\n type=\"button\"\n class=\"inline-flex items-center gap-2 px-4 py-2 bg-gray-600 text-white hover:bg-gray-700 font-medium rounded-lg transition-colors text-sm\"\n (click)=\"close()\"\n >\n Close\n </button>\n </div>\n </div>\n <div class=\"max-h-[70vh] overflow-y-auto p-4\">\n <div class=\"grid grid-cols-2 mb-[1rem] gap-4\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Route Name</label\n >\n <input\n type=\"text\"\n formControlName=\"route_name\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm\"\n />\n </div>\n <div class=\"relative\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Customer Name</label>\n <div class=\"relative\">\n <input\n type=\"text\"\n formControlName=\"customer_name\"\n (focus)=\"showCustomerDropdown.set(true)\"\n (blur)=\"hideDropdown('customer')\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm pr-10\"\n />\n @if (loadingCustomers()) {\n <div class=\"absolute right-3 top-1/2 -translate-y-1/2\">\n <lucide-icon [img]=\"icons.LoaderCircle\" [size]=\"18\" class=\"animate-spin text-blue-500\"></lucide-icon>\n </div>\n }\n </div>\n @if (showCustomerDropdown() && customerOptions().length > 0) {\n <div class=\"absolute z-50 w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-lg max-h-48 overflow-y-auto\">\n @for (cust of customerOptions(); track cust.customer_id) {\n <div \n (mousedown)=\"addRouteForm.patchValue({customer_name: cust.customer_name})\"\n class=\"px-4 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer text-sm text-gray-900 dark:text-white\">\n {{ cust.customer_name }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n <div class=\"grid grid-cols-1 mb-[1rem]\">\n <div\n class=\"location-flow bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-3 py-3 rounded-lg\"\n >\n @if (config.repository === 'coolmap') {\n <div class=\"flex gap-2 mb-2\">\n <div class=\"radio w-100 md:w-auto\">\n <input\n type=\"radio\"\n formControlName=\"pickUpSearchOption\"\n value=\"system\"\n class=\"shrink-0 size-4 bg-transparent border-line-3 rounded-full shadow-2xs text-primary focus:ring-0 focus:ring-offset-0 checked:bg-primary-checked checked:border-primary-checked\"\n />\n <label\n class=\"border-solid border-1 border-grey dark:border-slate-400 block shadow-md rounded-lg lg:whitespace-nowrap w-full ml-2\"\n >\n <div class=\"flex justify-between items-center px-4 py-2 text-xs lg:text-base\">\n <div class=\"tracking-wide text-xs\">System Search</div>\n </div>\n </label>\n </div>\n <div class=\"radio w-100 md:w-auto ml-2\">\n <input\n type=\"radio\"\n formControlName=\"pickUpSearchOption\"\n value=\"google\"\n class=\"shrink-0 size-4 bg-transparent border-line-3 rounded-full shadow-2xs text-primary focus:ring-0 focus:ring-offset-0 checked:bg-primary-checked checked:border-primary-checked\"\n />\n <label\n class=\"border-solid border-1 border-grey dark:border-slate-400 block shadow-md rounded-lg lg:whitespace-nowrap w-full ml-2\"\n >\n <div class=\"flex justify-between items-center px-4 py-2 text-xs lg:text-base\">\n <div class=\"tracking-wide text-xs\">Google Search</div>\n </div>\n </label>\n </div>\n </div>\n }\n <div class=\"relative\">\n <input\n #filterPickup\n type=\"text\"\n formControlName=\"pickup_location\"\n placeholder=\"Pickup\"\n (focus)=\"showPickupDropdown.set(true)\"\n (blur)=\"hideDropdown('pickup')\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm pr-10\"\n />\n @if (loadingLocations()) {\n <div class=\"absolute right-3 top-1/2 -translate-y-1/2\">\n <lucide-icon [img]=\"icons.LoaderCircle\" [size]=\"18\" class=\"animate-spin text-blue-500\"></lucide-icon>\n </div>\n }\n @if (isSystemPickup && showPickupDropdown() && pickupOptions().length > 0) {\n <div class=\"absolute z-50 w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-lg max-h-48 overflow-y-auto\">\n @for (loc of pickupOptions(); track loc.formatted_address) {\n <div \n (mousedown)=\"patchAddressValue(loc, 'pickup')\"\n class=\"px-4 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer text-sm text-gray-900 dark:text-white\">\n {{ loc.formatted_address }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"grid grid-cols-1 mb-[1rem]\">\n <div\n class=\"location-flow bg-gray-100 dark:bg-slate-900 border border-gray-300 dark:border-slate-700 px-3 py-3 rounded-lg\"\n >\n @if (config.repository === 'coolmap') {\n <div class=\"flex gap-2 mb-2\">\n <div class=\"radio w-100 md:w-auto\">\n <input\n type=\"radio\"\n formControlName=\"deliverySearchOtption\"\n value=\"system\"\n class=\"shrink-0 size-4 bg-transparent border-line-3 rounded-full shadow-2xs text-primary focus:ring-0 focus:ring-offset-0 checked:bg-primary-checked checked:border-primary-checked\"/>\n <label\n class=\"border-solid border-1 border-grey dark:border-slate-400 block shadow-md rounded-lg lg:whitespace-nowrap w-full ml-2\">\n <div class=\"flex justify-between items-center px-4 py-2 text-xs lg:text-base\">\n <div class=\"tracking-wide text-xs\">System Search</div>\n </div>\n </label>\n </div>\n <div class=\"radio w-100 md:w-auto ml-2\">\n <input\n type=\"radio\"\n formControlName=\"deliverySearchOtption\"\n value=\"google\"\n class=\"shrink-0 size-4 bg-transparent border-line-3 rounded-full shadow-2xs text-primary focus:ring-0 focus:ring-offset-0 checked:bg-primary-checked checked:border-primary-checked\"/>\n <label\n class=\"border-solid border-1 border-grey dark:border-slate-400 block shadow-md rounded-lg lg:whitespace-nowrap w-full ml-2\">\n <div class=\"flex justify-between items-center px-4 py-2 text-xs lg:text-base\">\n <div class=\"tracking-wide text-xs\">Google Search</div>\n </div>\n </label>\n </div>\n </div>\n }\n <div class=\"relative\">\n <input\n #filterDelivery\n type=\"text\"\n formControlName=\"delivery_location\"\n placeholder=\"Delivery\"\n (focus)=\"showDeliveryDropdown.set(true)\"\n (blur)=\"hideDropdown('delivery')\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm pr-10\"\n />\n @if (loadingLocations()) {\n <div class=\"absolute right-3 top-1/2 -translate-y-1/2\">\n <lucide-icon [img]=\"icons.LoaderCircle\" [size]=\"18\" class=\"animate-spin text-blue-500\"></lucide-icon>\n </div>\n }\n @if (isSystemDelivery && showDeliveryDropdown() && deliveryOptions().length > 0) {\n <div class=\"absolute z-50 w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-lg max-h-48 overflow-y-auto\">\n @for (loc of deliveryOptions(); track loc.formatted_address) {\n <div \n (mousedown)=\"patchAddressValue(loc, 'delivery')\"\n class=\"px-4 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer text-sm text-gray-900 dark:text-white\">\n {{ loc.formatted_address }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"flex gap-4 flex-col md:flex-row\">\n <div class=\"w-full md:w-[70%]\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Notes</label\n >\n <textarea\n formControlName=\"note\"\n class=\"w-full px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm h-[46px]\"\n ></textarea>\n </div>\n <div class=\"w-full md:w-[30%]\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Unit Type</label\n >\n <div class=\"relative w-full\">\n <button\n type=\"button\"\n (click)=\"toggleDropdown(); $event.stopPropagation()\"\n class=\"w-full flex justify-between items-center px-3 py-3 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm text-left\"\n >\n <span>{{ selectedUnitName }}</span>\n <lucide-icon [img]=\"icons.ChevronDown\" [size]=\"16\" class=\"text-gray-400\"></lucide-icon>\n </button>\n @if (dropdownOpen()) {\n <div class=\"absolute z-10 mt-1 w-full bg-white dark:bg-slate-800 rounded-lg shadow-lg border border-gray-200 dark:border-slate-700 py-1 max-h-60 overflow-auto\">\n @for (opt of unitsList(); track opt.id) {\n <button\n type=\"button\"\n (click)=\"addRouteForm.patchValue({unit_id: opt.id}); checkAndFetchRouteInformation(); dropdownOpen.set(false)\"\n class=\"w-full flex items-center justify-between px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-slate-700 transition\"\n >\n <span>{{ opt.type }}</span>\n @if (addRouteForm.value.unit_id === opt.id) {\n <lucide-icon [img]=\"icons.Check\" [size]=\"16\" class=\"text-blue-500\"></lucide-icon>\n }\n </button>\n }\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"grid grid-cols-2 mt-2 gap-4\">\n <div class=\"flex flex-col lg:flex-row gap-2\">\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\"\n >\n Estimation of miles - <b>{{ addRouteForm.value.estimated_distance || '0' }} Miles</b>\n </span>\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\"\n >\n Estimation of time - <b>{{ addRouteForm.value.estimated_time || '0' }} Minutes</b>\n </span>\n </div>\n <div class=\"flex flex-col lg:flex-row gap-2 justify-end\">\n @if (config.repository !== 'customer') {\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\"\n >\n Trucker Pay Estimate - <b>{{ addRouteForm.value.trucker_pay_estimate | currency }}</b>\n </span>\n }\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\"\n >\n Customer Price Estimate - <b>{{ addRouteForm.value.customer_price_estimate | currency }}</b>\n </span>\n \n </div>\n </div>\n </div>\n </form>\n\n @if (showDeleteModal()) {\n <div class=\"fixed inset-0 z-[120] flex items-center justify-center bg-black/60 backdrop-blur-sm animate-fade-in pointer-events-auto\">\n <div class=\"bg-white dark:bg-slate-900 rounded-xl shadow-2xl p-6 max-w-sm w-full mx-4 border border-gray-200 dark:border-slate-800\" (click)=\"$event.stopPropagation()\">\n <h3 class=\"text-lg font-bold text-gray-900 dark:text-white mb-2\">Delete Route?</h3>\n <p class=\"text-gray-600 dark:text-gray-400 text-sm mb-6\">\n Are you sure you want to delete this route?\n </p>\n <div class=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n (click)=\"showDeleteModal.set(false)\"\n class=\"px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 dark:text-gray-300 dark:bg-slate-800 dark:hover:bg-slate-700 rounded-lg transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n (click)=\"confirmDelete()\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-lg shadow-sm transition-colors\"\n >\n Delete\n </button>\n </div>\n </div>\n </div>\n }\n\n </div>\n</div>\n}\n", styles: ["@keyframes slide-up{0%{opacity:0;transform:translateY(10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-up{animation:slide-up .2s ease-out}.radio{position:relative}.radio input{position:absolute;top:50%;transform:translateY(-50%);left:0;opacity:0;width:100%;height:100%;cursor:pointer}.radio svg{opacity:.5}.radio label{height:auto;border-width:1px;border-color:#334155}.radio input:checked+label{border-color:#3b558e;background-color:#334155;color:#fff}.radio input:checked+label svg{opacity:1}.radio label:hover{cursor:pointer}.grid-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:10px}\n"] }]
891
+ }], ctorParameters: () => [], propDecorators: { modalOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalOpen", required: false }] }], routeData: [{ type: i0.Input, args: [{ isSignal: true, alias: "routeData", required: false }] }], customerRepoDetails: [{ type: i0.Input, args: [{ isSignal: true, alias: "customerRepoDetails", required: false }] }], modalOpenChange: [{
892
+ type: Output
893
+ }], routeDeleted: [{
894
+ type: Output
895
+ }], routeSaved: [{
896
+ type: Output
897
+ }], filterPickup: [{ type: i0.ViewChild, args: ['filterPickup', { isSignal: true }] }], filterDelivery: [{ type: i0.ViewChild, args: ['filterDelivery', { isSignal: true }] }], closeDropdowns: [{
898
+ type: HostListener,
899
+ args: ['document:click', ['$event']]
900
+ }], onMouseMove: [{
901
+ type: HostListener,
902
+ args: ['document:mousemove', ['$event']]
903
+ }], onMouseUp: [{
904
+ type: HostListener,
905
+ args: ['document:mouseup']
906
+ }] } });
907
+
908
+ class CoolmapComponent {
909
+ mobileMode = false;
910
+ activeSection = 'Jobcode';
911
+ customerRepoDetails;
912
+ darkMode = input(false, ...(ngDevMode ? [{ debugName: "darkMode" }] : []));
913
+ mapContainer;
914
+ coolmapService = inject(CoolmapService);
915
+ utils = inject(UtilsService);
916
+ config = inject(COOLMAP_CONFIG);
917
+ mapService = inject(MapboxService);
918
+ showRouteList = false;
919
+ addRouteModal = false;
920
+ // Filtering Signals (Lifted from JobCode)
921
+ routesList = signal([], ...(ngDevMode ? [{ debugName: "routesList" }] : []));
922
+ viewRoutesList = signal([], ...(ngDevMode ? [{ debugName: "viewRoutesList" }] : []));
923
+ filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
924
+ filteredOptions = signal([], ...(ngDevMode ? [{ debugName: "filteredOptions" }] : []));
925
+ dropdownOpen = signal(false, ...(ngDevMode ? [{ debugName: "dropdownOpen" }] : []));
926
+ dateValue = signal(this.utils.getDateFormat(new Date()), ...(ngDevMode ? [{ debugName: "dateValue" }] : []));
927
+ selectedRouteIds = signal([], ...(ngDevMode ? [{ debugName: "selectedRouteIds" }] : []));
928
+ modalPlottedIds = signal([], ...(ngDevMode ? [{ debugName: "modalPlottedIds" }] : []));
929
+ modalPlottedRoutes = signal([], ...(ngDevMode ? [{ debugName: "modalPlottedRoutes" }] : []));
930
+ selectedRouteForEdit = signal(null, ...(ngDevMode ? [{ debugName: "selectedRouteForEdit" }] : []));
931
+ unitList = signal([], ...(ngDevMode ? [{ debugName: "unitList" }] : []));
932
+ materialsList = signal([], ...(ngDevMode ? [{ debugName: "materialsList" }] : []));
933
+ currentlyPlottedIds = [];
934
+ skipNextRefresh = false;
935
+ mapOpQueue = Promise.resolve();
936
+ activeFilteredRoutes = computed(() => {
937
+ const list = this.activeSection === 'Jobcode' ? this.routesList() : this.viewRoutesList();
938
+ const filtersArray = this.filters();
939
+ if (filtersArray.length === 0)
940
+ return list;
941
+ return this.utils.getSearchResults(list, filtersArray) || [];
942
+ }, ...(ngDevMode ? [{ debugName: "activeFilteredRoutes" }] : []));
943
+ filterForm = new FormGroup({
944
+ search: new FormControl('')
945
+ });
946
+ allSelected = computed(() => {
947
+ const active = this.activeFilteredRoutes();
948
+ const selected = this.selectedRouteIds();
949
+ if (active.length === 0)
950
+ return false;
951
+ return active.every(r => selected.includes(this.getRouteId(r)));
952
+ }, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
953
+ modalAllSelected = computed(() => {
954
+ return false;
955
+ }, ...(ngDevMode ? [{ debugName: "modalAllSelected" }] : []));
956
+ masterStats = computed(() => {
957
+ const routes = this.activeFilteredRoutes();
958
+ const stats = { Done: 0, Incomplete: 0, Ongoing: 0, Open: 0 };
959
+ routes.forEach(route => {
960
+ const v = route?.values || {};
961
+ stats.Done += (v.Done || 0);
962
+ stats.Incomplete += (v.Incomplete || 0);
963
+ stats.Ongoing += (v.Ongoing || 0);
964
+ stats.Open += (v.Open || 0);
965
+ });
966
+ // Fallback default: if no routes, but we expect at least 1 "Open" slot behavior
967
+ if (routes.length > 0 && stats.Done === 0 && stats.Ongoing === 0 && stats.Open === 0 && stats.Incomplete === 0) {
968
+ stats.Open = routes.length;
969
+ }
970
+ return stats;
971
+ }, ...(ngDevMode ? [{ debugName: "masterStats" }] : []));
972
+ masterToggle() {
973
+ if (this.allSelected()) {
974
+ this.selectedRouteIds.set([]);
975
+ }
976
+ else {
977
+ const ids = this.activeFilteredRoutes().map(r => this.getRouteId(r));
978
+ this.selectedRouteIds.set(ids);
979
+ }
980
+ }
981
+ modalMasterToggle(modalRoutes) {
982
+ const currentPlotted = this.modalPlottedRoutes();
983
+ const isAllPlotted = modalRoutes.every(r => currentPlotted.some(p => this.getRouteId(p) === this.getRouteId(r)));
984
+ if (isAllPlotted) {
985
+ // Unplot all routes in this modal's view
986
+ const modalIdsInView = modalRoutes.map(r => this.getRouteId(r));
987
+ this.modalPlottedRoutes.update(curr => curr.filter(r => !modalIdsInView.includes(this.getRouteId(r))));
988
+ }
989
+ else {
990
+ // Plot all routes in this modal's view (prevent duplicates)
991
+ this.modalPlottedRoutes.update(curr => {
992
+ const newRoutes = modalRoutes.filter(mr => !curr.some(c => this.getRouteId(c) === this.getRouteId(mr)));
993
+ return [...curr, ...newRoutes];
994
+ });
995
+ }
996
+ this.modalPlottedIds.set(this.modalPlottedRoutes().map(r => this.getRouteId(r)));
997
+ }
998
+ icons = {
999
+ Route,
1000
+ Search,
1001
+ X,
1002
+ LoaderCircle
1003
+ };
1004
+ constructor() {
1005
+ this.filterForm.get('search')?.valueChanges.pipe(takeUntilDestroyed()).subscribe(value => {
1006
+ if (value && typeof value === 'string') {
1007
+ const options = this.utils.filter(value, this.filters());
1008
+ this.filteredOptions.set(options);
1009
+ this.dropdownOpen.set(options.length > 0);
1010
+ }
1011
+ else {
1012
+ this.filteredOptions.set([]);
1013
+ this.dropdownOpen.set(false);
1014
+ }
1015
+ });
1016
+ effect(() => {
1017
+ const isDark = this.darkMode();
1018
+ this.mapService.updateTheme(isDark);
1019
+ });
1020
+ // Effect 1: Manage Floating List (JobCode)
1021
+ effect(async () => {
1022
+ const routes = this.activeFilteredRoutes();
1023
+ const selectedIds = this.selectedRouteIds();
1024
+ // Queue the sync operation
1025
+ this.mapOpQueue = this.mapOpQueue.then(() => this.syncSpecificPrefix('jobcode', routes, selectedIds));
1026
+ });
1027
+ // Effect 2: Manage Modal List (JobRouteList)
1028
+ effect(async () => {
1029
+ const modalRoutes = this.modalPlottedRoutes();
1030
+ // Queue the sync operation
1031
+ this.mapOpQueue = this.mapOpQueue.then(() => this.syncSpecificPrefix('jobrouteList', modalRoutes, modalRoutes.map(r => this.getRouteId(r))));
1032
+ });
1033
+ // Effect 3: Manage View Route Section
1034
+ effect(async () => {
1035
+ if (this.activeSection === 'ViewRoute') {
1036
+ const routes = this.activeFilteredRoutes();
1037
+ const selectedIds = this.selectedRouteIds();
1038
+ // Queue the sync operation
1039
+ this.mapOpQueue = this.mapOpQueue.then(() => this.syncSpecificPrefix('viewroute', routes, selectedIds));
1040
+ }
1041
+ else {
1042
+ // Clear viewroute prefix if not in the section
1043
+ this.mapOpQueue = this.mapOpQueue.then(() => this.syncSpecificPrefix('viewroute', [], []));
1044
+ }
1045
+ });
1046
+ }
1047
+ ngOnInit() {
1048
+ this.loadData(this.dateValue());
1049
+ }
1050
+ getUnitList() {
1051
+ this.utils.getData('unit/all').subscribe((res) => {
1052
+ if (res && res.data) {
1053
+ this.unitList.set(res.data);
1054
+ }
1055
+ });
1056
+ }
1057
+ getMaterialsList() {
1058
+ this.utils.getData('material/all').subscribe((res) => {
1059
+ if (res && res.data) {
1060
+ this.materialsList.set(res.data);
1061
+ }
1062
+ });
1063
+ }
1064
+ ngOnChanges(changes) {
1065
+ if (changes['customerRepoDetails'] && !changes['customerRepoDetails'].firstChange) {
1066
+ if (this.config.repository === 'customer') {
1067
+ if (this.activeSection === 'Jobcode') {
1068
+ this.loadData(this.dateValue());
1069
+ }
1070
+ else {
1071
+ this.loadViewRoutes();
1072
+ }
1073
+ }
1074
+ }
1075
+ if (changes['activeSection'] && !changes['activeSection'].firstChange) {
1076
+ this.selectedRouteIds.set([]);
1077
+ this.filters.set([]);
1078
+ if (this.activeSection === 'Jobcode') {
1079
+ this.loadData(this.dateValue());
1080
+ }
1081
+ else {
1082
+ this.loadViewRoutes();
1083
+ }
1084
+ }
1085
+ }
1086
+ loadData(value, isRefresh = false) {
1087
+ this.dateValue.set(value);
1088
+ this.coolmapService.isLoading.set(true);
1089
+ // Clear selection when the base list changes (date/repo switch)
1090
+ if (!isRefresh) {
1091
+ this.selectedRouteIds.set([]);
1092
+ }
1093
+ if (this.config.repository === 'customer') {
1094
+ this.utils.fetchUnitsList().then((unitsRes) => {
1095
+ this.unitList.set(unitsRes);
1096
+ this.utils.fetchMaterialsListForCustomer().then((matRes) => {
1097
+ this.materialsList.set(matRes);
1098
+ this.executeJobReportFetch(value);
1099
+ });
1100
+ });
1101
+ }
1102
+ else {
1103
+ this.executeJobReportFetch(value);
1104
+ }
1105
+ }
1106
+ executeJobReportFetch(value) {
1107
+ let dataSet;
1108
+ if (this.config.repository === 'customer') {
1109
+ const custId = this.customerRepoDetails?.customer?.id || '';
1110
+ const projId = this.customerRepoDetails?.projectId || '';
1111
+ dataSet = { date: value, customer_id: custId, project_id: projId };
1112
+ }
1113
+ else {
1114
+ dataSet = { date: value };
1115
+ }
1116
+ this.utils.postdata('jobs_report_v2', dataSet).subscribe({
1117
+ next: (res) => {
1118
+ if (res && res.data && typeof res.data !== 'string') {
1119
+ const list = res.data.map((ele) => {
1120
+ ele.date = value;
1121
+ if (this.config.repository === 'customer') {
1122
+ ele.unit = this.getUnitName(ele);
1123
+ ele.material = this.getMaterialName(ele).split('|')[0].trim();
1124
+ }
1125
+ this.utils.makeOptions(ele);
1126
+ return ele;
1127
+ });
1128
+ this.routesList.set(list);
1129
+ }
1130
+ else {
1131
+ this.routesList.set([]);
1132
+ }
1133
+ this.coolmapService.isLoading.set(false);
1134
+ },
1135
+ error: () => {
1136
+ this.routesList.set([]);
1137
+ this.coolmapService.isLoading.set(false);
1138
+ }
1139
+ });
1140
+ }
1141
+ loadViewRoutes(isRefresh = false) {
1142
+ this.coolmapService.isLoading.set(true);
1143
+ if (!isRefresh) {
1144
+ this.selectedRouteIds.set([]);
1145
+ }
1146
+ if (this.config.repository === 'customer') {
1147
+ this.utils.fetchUnitsList().then((unitsRes) => {
1148
+ this.unitList.set(unitsRes);
1149
+ this.utils.fetchMaterialsListForCustomer().then((matRes) => {
1150
+ this.materialsList.set(matRes);
1151
+ this.executeViewRoutesFetch();
1152
+ });
1153
+ });
1154
+ }
1155
+ else {
1156
+ this.executeViewRoutesFetch();
1157
+ }
1158
+ }
1159
+ executeViewRoutesFetch() {
1160
+ if (this.config.repository === 'customer') {
1161
+ const dataSet = { customer_id: this.customerRepoDetails?.customer?.id || '' };
1162
+ this.utils.postdata('routes/all', dataSet).subscribe({
1163
+ next: (res) => {
1164
+ this.handleViewRoutesResponse(res);
1165
+ },
1166
+ error: () => {
1167
+ this.viewRoutesList.set([]);
1168
+ this.coolmapService.isLoading.set(false);
1169
+ }
1170
+ });
1171
+ }
1172
+ else {
1173
+ this.utils.getData('routes/all').subscribe({
1174
+ next: (res) => {
1175
+ this.handleViewRoutesResponse(res);
1176
+ },
1177
+ error: () => {
1178
+ this.viewRoutesList.set([]);
1179
+ this.coolmapService.isLoading.set(false);
1180
+ }
1181
+ });
1182
+ }
1183
+ }
1184
+ handleViewRoutesResponse(res) {
1185
+ if (res && res.data && Array.isArray(res.data)) {
1186
+ const list = res.data.map((ele) => {
1187
+ if (this.config.repository === 'customer') {
1188
+ ele.unit = this.getUnitName(ele);
1189
+ ele.material = this.getMaterialName(ele).split('|')[0].trim();
1190
+ }
1191
+ this.utils.makeOptions(ele);
1192
+ return ele;
1193
+ });
1194
+ this.viewRoutesList.set(list);
1195
+ }
1196
+ else {
1197
+ this.viewRoutesList.set([]);
1198
+ }
1199
+ this.coolmapService.isLoading.set(false);
1200
+ }
1201
+ getUnitName(data) {
1202
+ let unitName = '';
1203
+ this.unitList().forEach((res) => {
1204
+ if (res.id === data.unit_id) {
1205
+ unitName = res.type;
1206
+ }
1207
+ });
1208
+ return unitName || data.unit;
1209
+ }
1210
+ getMaterialName(data) {
1211
+ let materialName = '';
1212
+ this.materialsList().forEach((res) => {
1213
+ if (res.id === data.material_id) {
1214
+ materialName = res.name;
1215
+ }
1216
+ });
1217
+ return materialName || data.material || '';
1218
+ }
1219
+ onDateChange(event) {
1220
+ const target = event.target;
1221
+ if (target && target.value) {
1222
+ this.loadData(target.value);
1223
+ }
1224
+ }
1225
+ selectFilter(option) {
1226
+ this.filterForm.controls['search'].setValue('');
1227
+ this.selectedRouteIds.set([]); // Clear selection when switching filters
1228
+ this.filters.set([
1229
+ { name: option.label, type: option.type, value: option }
1230
+ ]);
1231
+ this.dropdownOpen.set(false);
1232
+ }
1233
+ removeFilter(filter) {
1234
+ this.filters.update(curr => curr.filter(f => f !== filter));
1235
+ }
1236
+ /**
1237
+ * Performs an isolated, diff-based synchronization for a specific route type.
1238
+ * This ensures that 'jobcode' and 'jobrouteList' updates never interfere.
1239
+ */
1240
+ async syncSpecificPrefix(prefix, sourceRoutes, selectedIds) {
1241
+ // 1. Identify desired state for THIS prefix only
1242
+ const desiredIds = new Set(sourceRoutes.map(r => `${prefix}-${this.getRouteId(r)}`));
1243
+ // 2. Get current state for THIS prefix only from map service
1244
+ const currentPlottedIds = this.mapService.getRegistryKeys().filter(k => k.startsWith(`${prefix}-`));
1245
+ // 3. Diff: Identify what to remove and what to add for THIS prefix
1246
+ const toRemove = currentPlottedIds.filter(id => !desiredIds.has(id));
1247
+ const toAdd = Array.from(desiredIds).filter(id => !currentPlottedIds.includes(id));
1248
+ // 4. Perform removals for THIS prefix
1249
+ for (const regId of toRemove) {
1250
+ const id = regId.substring(prefix.length + 1);
1251
+ this.coolmapService.plottingIds.update(set => {
1252
+ const n = new Set(set);
1253
+ n.add(regId);
1254
+ return n;
1255
+ });
1256
+ try {
1257
+ await this.mapService.removeRouteAndMarker(id, prefix);
1258
+ }
1259
+ finally {
1260
+ this.coolmapService.plottingIds.update(set => {
1261
+ const n = new Set(set);
1262
+ n.delete(regId);
1263
+ return n;
1264
+ });
1265
+ }
1266
+ }
1267
+ // 5. Perform additions for THIS prefix
1268
+ if (toAdd.length > 0) {
1269
+ this.coolmapService.isLoading.set(true);
1270
+ for (const regId of toAdd) {
1271
+ const id = regId.substring(prefix.length + 1);
1272
+ const routeObj = sourceRoutes.find(r => this.getRouteId(r) === id);
1273
+ if (routeObj) {
1274
+ this.coolmapService.plottingIds.update(set => {
1275
+ const n = new Set(set);
1276
+ n.add(regId);
1277
+ return n;
1278
+ });
1279
+ try {
1280
+ await this.mapService.plotRoute(routeObj, id, prefix, false, false);
1281
+ }
1282
+ finally {
1283
+ this.coolmapService.plottingIds.update(set => {
1284
+ const n = new Set(set);
1285
+ n.delete(regId);
1286
+ return n;
1287
+ });
1288
+ }
1289
+ }
1290
+ }
1291
+ }
1292
+ // 6. Update Visibility for THIS prefix only
1293
+ // This allows jobcode and jobrouteList to have independent visibility states.
1294
+ const compositeVisible = selectedIds.map(id => `${prefix}-${id}`);
1295
+ const showAllForThisPrefix = (prefix === 'jobcode' && selectedIds.length === 0);
1296
+ this.mapService.setRoutesVisibility(compositeVisible, showAllForThisPrefix, prefix);
1297
+ this.coolmapService.isLoading.set(false);
1298
+ }
1299
+ toggleSelection(id) {
1300
+ this.selectedRouteIds.update(ids => {
1301
+ if (ids.includes(id)) {
1302
+ return ids.filter(i => i !== id);
1303
+ }
1304
+ else {
1305
+ return [...ids, id];
1306
+ }
1307
+ });
1308
+ }
1309
+ toggleModalRoute(route) {
1310
+ const id = this.getRouteId(route);
1311
+ if (!id)
1312
+ return;
1313
+ this.modalPlottedRoutes.update(routes => {
1314
+ const isPlotted = routes.some(r => this.getRouteId(r) === id);
1315
+ if (isPlotted) {
1316
+ return routes.filter(r => this.getRouteId(r) !== id);
1317
+ }
1318
+ else {
1319
+ return [...routes, route];
1320
+ }
1321
+ });
1322
+ // Also update IDs for UI checkboxes
1323
+ this.modalPlottedIds.set(this.modalPlottedRoutes().map(r => this.getRouteId(r)));
1324
+ }
1325
+ async onAddRouteModalChange(isOpen) {
1326
+ this.addRouteModal = isOpen;
1327
+ if (!isOpen) {
1328
+ if (this.skipNextRefresh) {
1329
+ this.skipNextRefresh = false;
1330
+ return;
1331
+ }
1332
+ // Modal closed - restore map state and refresh data
1333
+ await this.refreshMapState();
1334
+ }
1335
+ }
1336
+ async refreshMapState() {
1337
+ if (this.activeSection === 'Jobcode') {
1338
+ await this.loadData(this.dateValue(), true);
1339
+ }
1340
+ else {
1341
+ await this.loadViewRoutes(true);
1342
+ }
1343
+ }
1344
+ onRouteSaved(data) {
1345
+ if (data.id) {
1346
+ // It was an update - surgical local update
1347
+ this.skipNextRefresh = true;
1348
+ const updater = (list) => list.map(r => this.getRouteId(r) === data.id ? { ...r, ...data } : r);
1349
+ this.routesList.update(updater);
1350
+ this.viewRoutesList.update(updater);
1351
+ }
1352
+ else {
1353
+ // It was an add - need a re-fetch to get new ID from server
1354
+ this.skipNextRefresh = false; // explicitly allow refresh on add to get new ID
1355
+ }
1356
+ }
1357
+ onRouteDeleted(id) {
1358
+ this.skipNextRefresh = true;
1359
+ // 1. Remove from local signals
1360
+ this.routesList.update(list => list.filter(r => this.getRouteId(r) !== id));
1361
+ this.viewRoutesList.update(list => list.filter(r => this.getRouteId(r) !== id));
1362
+ // 2. Clear from selections
1363
+ this.selectedRouteIds.update(ids => ids.filter(i => i !== id));
1364
+ this.modalPlottedIds.update(ids => ids.filter(i => i !== id));
1365
+ this.modalPlottedRoutes.update(routes => routes.filter(r => this.getRouteId(r) !== id));
1366
+ // 3. Cleanup Map
1367
+ this.mapService.removeRouteAndMarker(id, 'jobcode');
1368
+ this.mapService.removeRouteAndMarker(id, 'jobrouteList');
1369
+ this.mapService.removeRouteAndMarker(id, 'viewroute');
1370
+ // 4. Update Filter Pills
1371
+ this.filters.update(curr => curr.filter(f => {
1372
+ if (f.type === 'job') {
1373
+ return f.value.job_id !== id;
1374
+ }
1375
+ return true;
1376
+ }));
1377
+ // 5. Refresh Search Suggestions (Options list in Utils)
1378
+ this.utils.clearOptions();
1379
+ [...this.routesList(), ...this.viewRoutesList()].forEach(r => this.utils.makeOptions(r));
1380
+ }
1381
+ openAddRouteModal() {
1382
+ this.selectedRouteForEdit.set(null);
1383
+ this.addRouteModal = true;
1384
+ }
1385
+ openEditModal(route) {
1386
+ this.selectedRouteForEdit.set(route);
1387
+ this.addRouteModal = true;
1388
+ }
1389
+ getRouteId(route) {
1390
+ return route.job_id || route.route_id || route.route_details_id || '';
1391
+ }
1392
+ clearSelection() {
1393
+ this.selectedRouteIds.set([]);
1394
+ }
1395
+ ngAfterViewInit() {
1396
+ setTimeout(() => {
1397
+ this.mapService.initializeMap(this.mapContainer.nativeElement, this.darkMode());
1398
+ setTimeout(() => {
1399
+ this.mapService.resize();
1400
+ }, 200);
1401
+ }, 0);
1402
+ }
1403
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1404
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: CoolmapComponent, isStandalone: true, selector: "lib-coolmap", inputs: { mobileMode: { classPropertyName: "mobileMode", publicName: "mobileMode", isSignal: false, isRequired: false, transformFunction: null }, activeSection: { classPropertyName: "activeSection", publicName: "activeSection", isSignal: false, isRequired: false, transformFunction: null }, customerRepoDetails: { classPropertyName: "customerRepoDetails", publicName: "customerRepoDetails", isSignal: false, isRequired: false, transformFunction: null }, darkMode: { classPropertyName: "darkMode", publicName: "darkMode", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "mapContainer", first: true, predicate: ["mapContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"center-area\">\n <div [formGroup]=\"filterForm\" class=\"toolbar flex flex-col sm:flex-row md:h-[55px] h-auto py-2 px-2 gap-3 items-center\">\n <div class=\"toolbar-left flex items-center gap-3\">\n @if (activeSection === 'Jobcode') {\n <input\n type=\"date\"\n [value]=\"dateValue()\"\n (change)=\"onDateChange($event)\"\n [disabled]=\"coolmapService.isLoading()\"\n class=\"w-auto lg:w-[200px] px-3 py-2 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n <span class=\"toolbar-divider hidden sm:flex h-6 w-[1px] bg-gray-200 dark:bg-slate-700\"></span>\n }\n <div class=\"relative items-center flex\">\n <lucide-icon [img]=\"icons.Search\" [size]=\"18\" class=\"absolute left-3 top-1/2 -translate-y-1/2 text-gray-400\"></lucide-icon>\n <input\n type=\"text\"\n formControlName=\"search\"\n [attr.disabled]=\"coolmapService.isLoading() ? true : null\"\n placeholder=\"Search routes...\"\n class=\"w-[150px] xl:w-[300px] pl-10 pr-10 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-amber-500 focus:border-transparent text-sm disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n\n @if (filterForm.value.search) {\n <button (click)=\"filterForm.controls.search.setValue('')\" class=\"absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600\">\n <lucide-icon [img]=\"icons.X\" [size]=\"16\"></lucide-icon>\n </button>\n }\n\n <!-- Autocomplete Dropdown -->\n @if (dropdownOpen() && filteredOptions().length > 0) {\n <div class=\"absolute z-50 left-0 right-0 top-full mt-1 max-h-60 overflow-y-auto bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-600 rounded shadow-lg\">\n @for (option of filteredOptions(); track option.value + option.type) {\n <div (click)=\"selectFilter(option)\" class=\"px-4 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer text-sm font-medium border-b border-gray-100 dark:border-slate-700 last:border-0 dark:text-white\">\n <span class=\"capitalize text-xs text-gray-500 dark:text-gray-400 font-normal mr-1\">{{ option.type }}:</span>\n {{ option.label }}\n </div>\n }\n </div>\n }\n </div>\n\n @if (filters().length > 0) {\n <div class=\"flex flex-wrap gap-2 ml-2\">\n @for (filter of filters(); track filter.value + filter.type) {\n <div class=\"inline-flex items-center gap-1 px-2 py-1 rounded bg-slate-200 dark:bg-slate-700 text-gray-800 dark:text-gray-200 text-xs font-medium border border-gray-300 dark:border-slate-600\">\n <span class=\"capitalize opacity-80\">{{ filter.type }}:</span> {{ filter.name }}\n <button type=\"button\" (click)=\"removeFilter(filter)\" class=\"hover:opacity-75 transition-opacity ml-1\">\n <lucide-icon [img]=\"icons.X\" [size]=\"12\"></lucide-icon>\n </button>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"toolbar-right\">\n @if (selectedRouteIds().length > 0) {\n <button\n type=\"button\"\n (click)=\"clearSelection()\"\n [disabled]=\"coolmapService.isLoading()\"\n class=\"inline-flex items-center justify-center gap-2 px-5 sm:px-6 py-2 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors text-xs md:text-sm\"\n >\n Show All ({{ selectedRouteIds().length }})\n </button>\n }\n @if (activeSection === 'ViewRoute') {\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center gap-2 px-5 sm:px-6 py-2 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors text-xs md:text-sm\"\n [disabled]=\"coolmapService.isLoading()\"\n (click)=\"openAddRouteModal()\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n Add Route\n </button>\n }\n\n @if(activeSection === 'Jobcode'){\n <button\n class=\"inline-flex items-center gap-2 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed relative group\"\n (click)=\"showRouteList = true\"\n [disabled]=\"coolmapService.isLoading()\">\n <lucide-icon [img]=\"icons.Route\" [size]=\"20\"></lucide-icon>\n <div\n class=\"absolute top-full left-1/2 -translate-x-1/2 mt-2 hidden group-hover:block bg-gray-800 text-white text-[10px] rounded py-1 px-2 whitespace-nowrap\">\n Show Route List\n <div\n class=\"absolute -top-[8px] left-1/2 -translate-x-1/2 border-4 border-transparent border-b-gray-800\">\n </div>\n </div>\n </button>}\n </div>\n </div>\n <div class=\"view-content\">\n <div class=\"full-view\">\n <div class=\"map-container\">\n <div class=\"mapbox-container\" #mapContainer></div>\n </div>\n </div>\n\n <!-- FOR JOBCODE -->\n @if (activeSection === 'Jobcode') {\n <lib-job-code \n listMode=\"floating\" \n [routes]=\"activeFilteredRoutes()\"\n [selectedRouteIds]=\"selectedRouteIds()\"\n [isLoading]=\"coolmapService.isLoading()\"\n (routeSelect)=\"toggleSelection($event)\">\n </lib-job-code>\n }\n\n <!-- FOR VIEWROUTE -->\n @if (activeSection === 'ViewRoute') {\n <lib-view-route-list \n listMode=\"floating\" \n [routes]=\"activeFilteredRoutes()\"\n [selectedRouteIds]=\"selectedRouteIds()\"\n (routeSelect)=\"toggleSelection($event)\"\n (editRoute)=\"openEditModal($event)\"\n ></lib-view-route-list>\n }\n </div>\n @if (config.repository === 'coolmap') {\n <div class=\"status-bar\">\n <div class=\"stats-row\">\n <div class=\"stat-item active\" title=\"Active routes (assigned + in progress)\">\n <span class=\"stat-count\">{{ masterStats().Done }}</span>\n <span class=\"stat-label\">Done</span>\n </div>\n\n <div class=\"stat-item completed\" title=\"Completed tasks\">\n <span class=\"stat-count\">{{ masterStats().Incomplete }}</span>\n <span class=\"stat-label\">Incomplete</span>\n </div>\n\n <div class=\"stat-item declined\" title=\"Declined routes - needs attention\">\n <span class=\"stat-count\">{{ masterStats().Ongoing }}</span>\n <span class=\"stat-label\">Ongoing</span>\n </div>\n\n <div class=\"stat-item scheduled\" title=\"Scheduled jobs for today\">\n <span class=\"stat-count\">{{ masterStats().Open }}</span>\n <span class=\"stat-label\">Open</span>\n </div>\n </div>\n </div>\n }\n</div>\n<lib-job-route-list \n [initialRoutes]=\"viewRoutesList()\"\n [(modalOpen)]=\"showRouteList\"\n [selectedRouteIds]=\"modalPlottedIds()\"\n (routeSelect)=\"toggleModalRoute($event)\"\n (masterToggleEvent)=\"modalMasterToggle($event)\"\n [customerRepoDetails]=\"customerRepoDetails\"\n></lib-job-route-list>\n<lib-add-route \n [modalOpen]=\"addRouteModal\"\n (modalOpenChange)=\"onAddRouteModalChange($event)\"\n (routeSaved)=\"onRouteSaved($event)\"\n (routeDeleted)=\"onRouteDeleted($event)\"\n [customerRepoDetails]=\"customerRepoDetails\"\n [routeData]=\"selectedRouteForEdit()\"\n></lib-add-route>\n", styles: [":host{display:block;width:100%;height:100%;min-height:400px}.center-area{height:100%;display:flex;flex-direction:column;overflow:hidden}.view-content{flex:1;position:relative;overflow:hidden;z-index:10}.full-view{position:absolute;inset:0}.map-container,.mapbox-container{width:100%;height:100%}.toolbar{align-items:center;justify-content:space-between;background:var(--bg-primary, white);border-bottom:1px solid var(--border-color, #e2e8f0);flex-shrink:0;gap:8px;position:relative;z-index:50}:host-context(.dark) .toolbar{background:var(--bg-dark-primary, #1e293b);border-color:var(--border-dark, #334155);box-shadow:0 1px 3px #0000004d}.toolbar-left{display:flex;align-items:center;gap:12px;flex-shrink:0}.toolbar-divider{width:1px;height:20px;background:var(--border-color, #e2e8f0);margin:0 2px;flex-shrink:0}:host-context(.dark) .toolbar-divider{background:#475569}.toolbar-right{display:flex;align-items:center;gap:10px;flex-shrink:0}.status-bar{display:flex;align-items:center;height:100%;padding:0 16px;background:var(--status-bar-bg, #1e293b);color:#fff;font-size:12px;gap:20px;flex-shrink:0;height:36px}.stats-row{display:flex;align-items:center;gap:20px}.stat-item{display:flex;align-items:center;gap:6px;cursor:default}.stat-count{font-size:15px;font-weight:700;line-height:1}.stat-item.active .stat-count{color:#60a5fa}.stat-item.completed .stat-count{color:#4ade80}.stat-item.declined .stat-count{color:#f87171}.stat-item.scheduled .stat-count{color:#a78bfa}.stat-item.pending .stat-count{color:#fbbf24}.route-list-wrapper{position:absolute;top:10px;left:10px;bottom:10px;width:380px;z-index:20;pointer-events:none;display:flex;flex-direction:column}@media (max-width: 1024px){.route-list-wrapper{width:320px}}.route-list-wrapper>*{pointer-events:auto}.list-loader{position:absolute;inset:0;background:#fff6;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:100;border-radius:12px;border:1px solid rgba(255,255,255,.2);box-shadow:0 8px 32px #1f268712}:host-context(.dark) .list-loader{background:#0f172a66;border-color:#ffffff1a}.animate-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "component", type: JobCodeComponent, selector: "lib-job-code", inputs: ["listMode", "collapsible", "routes", "selectedRouteIds", "isLoading"], outputs: ["routeSelect"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: JobRouteListComponent, selector: "lib-job-route-list", inputs: ["customerRepoDetails", "selectedRouteIds", "modalOpen", "initialRoutes"], outputs: ["modalOpenChange", "routeSelect", "masterToggleEvent"] }, { kind: "component", type: ViewRouteListComponent, selector: "lib-view-route-list", inputs: ["listMode", "collapsible", "routes", "selectedRouteIds"], outputs: ["routeSelect", "editRoute"] }, { kind: "component", type: AddRouteComponent, selector: "lib-add-route", inputs: ["modalOpen", "routeData", "customerRepoDetails"], outputs: ["modalOpenChange", "routeDeleted", "routeSaved"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
1405
+ }
1406
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapComponent, decorators: [{
1407
+ type: Component,
1408
+ args: [{ selector: 'lib-coolmap', standalone: true, imports: [
1409
+ JobCodeComponent,
1410
+ LucideAngularModule,
1411
+ JobRouteListComponent,
1412
+ ViewRouteListComponent,
1413
+ AddRouteComponent,
1414
+ ReactiveFormsModule,
1415
+ ], template: "<div class=\"center-area\">\n <div [formGroup]=\"filterForm\" class=\"toolbar flex flex-col sm:flex-row md:h-[55px] h-auto py-2 px-2 gap-3 items-center\">\n <div class=\"toolbar-left flex items-center gap-3\">\n @if (activeSection === 'Jobcode') {\n <input\n type=\"date\"\n [value]=\"dateValue()\"\n (change)=\"onDateChange($event)\"\n [disabled]=\"coolmapService.isLoading()\"\n class=\"w-auto lg:w-[200px] px-3 py-2 rounded-lg border border-gray-200 dark:border-slate-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-blue focus:border-transparent text-sm disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n <span class=\"toolbar-divider hidden sm:flex h-6 w-[1px] bg-gray-200 dark:bg-slate-700\"></span>\n }\n <div class=\"relative items-center flex\">\n <lucide-icon [img]=\"icons.Search\" [size]=\"18\" class=\"absolute left-3 top-1/2 -translate-y-1/2 text-gray-400\"></lucide-icon>\n <input\n type=\"text\"\n formControlName=\"search\"\n [attr.disabled]=\"coolmapService.isLoading() ? true : null\"\n placeholder=\"Search routes...\"\n class=\"w-[150px] xl:w-[300px] pl-10 pr-10 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-amber-500 focus:border-transparent text-sm disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n\n @if (filterForm.value.search) {\n <button (click)=\"filterForm.controls.search.setValue('')\" class=\"absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600\">\n <lucide-icon [img]=\"icons.X\" [size]=\"16\"></lucide-icon>\n </button>\n }\n\n <!-- Autocomplete Dropdown -->\n @if (dropdownOpen() && filteredOptions().length > 0) {\n <div class=\"absolute z-50 left-0 right-0 top-full mt-1 max-h-60 overflow-y-auto bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-600 rounded shadow-lg\">\n @for (option of filteredOptions(); track option.value + option.type) {\n <div (click)=\"selectFilter(option)\" class=\"px-4 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer text-sm font-medium border-b border-gray-100 dark:border-slate-700 last:border-0 dark:text-white\">\n <span class=\"capitalize text-xs text-gray-500 dark:text-gray-400 font-normal mr-1\">{{ option.type }}:</span>\n {{ option.label }}\n </div>\n }\n </div>\n }\n </div>\n\n @if (filters().length > 0) {\n <div class=\"flex flex-wrap gap-2 ml-2\">\n @for (filter of filters(); track filter.value + filter.type) {\n <div class=\"inline-flex items-center gap-1 px-2 py-1 rounded bg-slate-200 dark:bg-slate-700 text-gray-800 dark:text-gray-200 text-xs font-medium border border-gray-300 dark:border-slate-600\">\n <span class=\"capitalize opacity-80\">{{ filter.type }}:</span> {{ filter.name }}\n <button type=\"button\" (click)=\"removeFilter(filter)\" class=\"hover:opacity-75 transition-opacity ml-1\">\n <lucide-icon [img]=\"icons.X\" [size]=\"12\"></lucide-icon>\n </button>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"toolbar-right\">\n @if (selectedRouteIds().length > 0) {\n <button\n type=\"button\"\n (click)=\"clearSelection()\"\n [disabled]=\"coolmapService.isLoading()\"\n class=\"inline-flex items-center justify-center gap-2 px-5 sm:px-6 py-2 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors text-xs md:text-sm\"\n >\n Show All ({{ selectedRouteIds().length }})\n </button>\n }\n @if (activeSection === 'ViewRoute') {\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center gap-2 px-5 sm:px-6 py-2 bg-amber-500 hover:bg-amber-600 disabled:opacity-50 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors text-xs md:text-sm\"\n [disabled]=\"coolmapService.isLoading()\"\n (click)=\"openAddRouteModal()\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n Add Route\n </button>\n }\n\n @if(activeSection === 'Jobcode'){\n <button\n class=\"inline-flex items-center gap-2 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed relative group\"\n (click)=\"showRouteList = true\"\n [disabled]=\"coolmapService.isLoading()\">\n <lucide-icon [img]=\"icons.Route\" [size]=\"20\"></lucide-icon>\n <div\n class=\"absolute top-full left-1/2 -translate-x-1/2 mt-2 hidden group-hover:block bg-gray-800 text-white text-[10px] rounded py-1 px-2 whitespace-nowrap\">\n Show Route List\n <div\n class=\"absolute -top-[8px] left-1/2 -translate-x-1/2 border-4 border-transparent border-b-gray-800\">\n </div>\n </div>\n </button>}\n </div>\n </div>\n <div class=\"view-content\">\n <div class=\"full-view\">\n <div class=\"map-container\">\n <div class=\"mapbox-container\" #mapContainer></div>\n </div>\n </div>\n\n <!-- FOR JOBCODE -->\n @if (activeSection === 'Jobcode') {\n <lib-job-code \n listMode=\"floating\" \n [routes]=\"activeFilteredRoutes()\"\n [selectedRouteIds]=\"selectedRouteIds()\"\n [isLoading]=\"coolmapService.isLoading()\"\n (routeSelect)=\"toggleSelection($event)\">\n </lib-job-code>\n }\n\n <!-- FOR VIEWROUTE -->\n @if (activeSection === 'ViewRoute') {\n <lib-view-route-list \n listMode=\"floating\" \n [routes]=\"activeFilteredRoutes()\"\n [selectedRouteIds]=\"selectedRouteIds()\"\n (routeSelect)=\"toggleSelection($event)\"\n (editRoute)=\"openEditModal($event)\"\n ></lib-view-route-list>\n }\n </div>\n @if (config.repository === 'coolmap') {\n <div class=\"status-bar\">\n <div class=\"stats-row\">\n <div class=\"stat-item active\" title=\"Active routes (assigned + in progress)\">\n <span class=\"stat-count\">{{ masterStats().Done }}</span>\n <span class=\"stat-label\">Done</span>\n </div>\n\n <div class=\"stat-item completed\" title=\"Completed tasks\">\n <span class=\"stat-count\">{{ masterStats().Incomplete }}</span>\n <span class=\"stat-label\">Incomplete</span>\n </div>\n\n <div class=\"stat-item declined\" title=\"Declined routes - needs attention\">\n <span class=\"stat-count\">{{ masterStats().Ongoing }}</span>\n <span class=\"stat-label\">Ongoing</span>\n </div>\n\n <div class=\"stat-item scheduled\" title=\"Scheduled jobs for today\">\n <span class=\"stat-count\">{{ masterStats().Open }}</span>\n <span class=\"stat-label\">Open</span>\n </div>\n </div>\n </div>\n }\n</div>\n<lib-job-route-list \n [initialRoutes]=\"viewRoutesList()\"\n [(modalOpen)]=\"showRouteList\"\n [selectedRouteIds]=\"modalPlottedIds()\"\n (routeSelect)=\"toggleModalRoute($event)\"\n (masterToggleEvent)=\"modalMasterToggle($event)\"\n [customerRepoDetails]=\"customerRepoDetails\"\n></lib-job-route-list>\n<lib-add-route \n [modalOpen]=\"addRouteModal\"\n (modalOpenChange)=\"onAddRouteModalChange($event)\"\n (routeSaved)=\"onRouteSaved($event)\"\n (routeDeleted)=\"onRouteDeleted($event)\"\n [customerRepoDetails]=\"customerRepoDetails\"\n [routeData]=\"selectedRouteForEdit()\"\n></lib-add-route>\n", styles: [":host{display:block;width:100%;height:100%;min-height:400px}.center-area{height:100%;display:flex;flex-direction:column;overflow:hidden}.view-content{flex:1;position:relative;overflow:hidden;z-index:10}.full-view{position:absolute;inset:0}.map-container,.mapbox-container{width:100%;height:100%}.toolbar{align-items:center;justify-content:space-between;background:var(--bg-primary, white);border-bottom:1px solid var(--border-color, #e2e8f0);flex-shrink:0;gap:8px;position:relative;z-index:50}:host-context(.dark) .toolbar{background:var(--bg-dark-primary, #1e293b);border-color:var(--border-dark, #334155);box-shadow:0 1px 3px #0000004d}.toolbar-left{display:flex;align-items:center;gap:12px;flex-shrink:0}.toolbar-divider{width:1px;height:20px;background:var(--border-color, #e2e8f0);margin:0 2px;flex-shrink:0}:host-context(.dark) .toolbar-divider{background:#475569}.toolbar-right{display:flex;align-items:center;gap:10px;flex-shrink:0}.status-bar{display:flex;align-items:center;height:100%;padding:0 16px;background:var(--status-bar-bg, #1e293b);color:#fff;font-size:12px;gap:20px;flex-shrink:0;height:36px}.stats-row{display:flex;align-items:center;gap:20px}.stat-item{display:flex;align-items:center;gap:6px;cursor:default}.stat-count{font-size:15px;font-weight:700;line-height:1}.stat-item.active .stat-count{color:#60a5fa}.stat-item.completed .stat-count{color:#4ade80}.stat-item.declined .stat-count{color:#f87171}.stat-item.scheduled .stat-count{color:#a78bfa}.stat-item.pending .stat-count{color:#fbbf24}.route-list-wrapper{position:absolute;top:10px;left:10px;bottom:10px;width:380px;z-index:20;pointer-events:none;display:flex;flex-direction:column}@media (max-width: 1024px){.route-list-wrapper{width:320px}}.route-list-wrapper>*{pointer-events:auto}.list-loader{position:absolute;inset:0;background:#fff6;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:100;border-radius:12px;border:1px solid rgba(255,255,255,.2);box-shadow:0 8px 32px #1f268712}:host-context(.dark) .list-loader{background:#0f172a66;border-color:#ffffff1a}.animate-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
1416
+ }], ctorParameters: () => [], propDecorators: { mobileMode: [{
1417
+ type: Input
1418
+ }], activeSection: [{
1419
+ type: Input
1420
+ }], customerRepoDetails: [{
1421
+ type: Input
1422
+ }], darkMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "darkMode", required: false }] }], mapContainer: [{
1423
+ type: ViewChild,
1424
+ args: ['mapContainer']
1425
+ }] } });
1426
+
1427
+ /*
1428
+ * Public API Surface of coolmap
1429
+ */
1430
+
1431
+ /**
1432
+ * Generated bundle index. Do not edit.
1433
+ */
1434
+
1435
+ export { Coolmap, CoolmapComponent };
1436
+ //# sourceMappingURL=coolmap.mjs.map