@aggdirect/coolmap 5.0.5 → 5.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/aggdirect-coolmap.mjs +898 -107
- package/fesm2022/aggdirect-coolmap.mjs.map +1 -1
- package/index.d.ts +30 -5
- package/package.json +7 -3
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Component, input, EventEmitter, Output, signal, HostListener,
|
|
2
|
+
import { Component, inject, input, EventEmitter, Output, signal, HostListener, effect, Input, computed, DestroyRef, ChangeDetectorRef, viewChild, CUSTOM_ELEMENTS_SCHEMA, ViewChild, ElementRef } from '@angular/core';
|
|
3
3
|
import * as i2$2 from '@angular/forms';
|
|
4
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 '@aggdirect/coolmap-services';
|
|
7
5
|
import * as i2 from '@angular/common';
|
|
8
|
-
import {
|
|
6
|
+
import { CommonModule, NgClass, CurrencyPipe } from '@angular/common';
|
|
9
7
|
import * as i1 from 'lucide-angular';
|
|
10
|
-
import { X, LucideAngularModule, LoaderCircle,
|
|
8
|
+
import { CheckCircle, XCircle, AlertTriangle, Info, X, LucideAngularModule, LoaderCircle, CarFront, Search, Share2, ChevronLeft, ChevronRight, Check, ChevronDown, DollarSign, Mail, Phone, User, Hash, MapPin, Calendar, Route } from 'lucide-angular';
|
|
9
|
+
import { AgToastService, COOLMAP_CONFIG, UtilsService, CoolmapService, MapboxService } from '@aggdirect/coolmap-services';
|
|
10
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
11
11
|
import * as i2$1 from '@angular/cdk/scrolling';
|
|
12
12
|
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
13
13
|
import { pairwise, filter } from 'rxjs';
|
|
@@ -21,6 +21,127 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
21
21
|
args: [{ selector: 'lib-coolmap', imports: [], template: `` }]
|
|
22
22
|
}] });
|
|
23
23
|
|
|
24
|
+
class AgToastContainerComponent {
|
|
25
|
+
toastService = inject(AgToastService);
|
|
26
|
+
successIcon = CheckCircle;
|
|
27
|
+
errorIcon = XCircle;
|
|
28
|
+
warningIcon = AlertTriangle;
|
|
29
|
+
infoIcon = Info;
|
|
30
|
+
closeIcon = X;
|
|
31
|
+
get containerClasses() {
|
|
32
|
+
return `toast-container ${this.toastService.toastPosition()}`;
|
|
33
|
+
}
|
|
34
|
+
getToastClasses(toast) {
|
|
35
|
+
return `toast ${toast.variant}`;
|
|
36
|
+
}
|
|
37
|
+
getIconClasses(toast) {
|
|
38
|
+
return `toast-icon ${toast.variant}`;
|
|
39
|
+
}
|
|
40
|
+
dismiss(toast) {
|
|
41
|
+
this.toastService.dismiss(toast.id);
|
|
42
|
+
}
|
|
43
|
+
onActionClick(toast) {
|
|
44
|
+
if (toast.action?.onClick) {
|
|
45
|
+
toast.action.onClick();
|
|
46
|
+
}
|
|
47
|
+
this.dismiss(toast);
|
|
48
|
+
}
|
|
49
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AgToastContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
50
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: AgToastContainerComponent, isStandalone: true, selector: "ag-toast-container", ngImport: i0, template: `
|
|
51
|
+
<div [class]="containerClasses">
|
|
52
|
+
@for (toast of toastService.toasts(); track toast.id) {
|
|
53
|
+
<div [class]="getToastClasses(toast)" role="alert">
|
|
54
|
+
<!-- Icon -->
|
|
55
|
+
<div [class]="getIconClasses(toast)">
|
|
56
|
+
@switch (toast.variant) {
|
|
57
|
+
@case ('success') {
|
|
58
|
+
<lucide-icon [img]="successIcon" [size]="20"></lucide-icon>
|
|
59
|
+
}
|
|
60
|
+
@case ('error') {
|
|
61
|
+
<lucide-icon [img]="errorIcon" [size]="20"></lucide-icon>
|
|
62
|
+
}
|
|
63
|
+
@case ('warning') {
|
|
64
|
+
<lucide-icon [img]="warningIcon" [size]="20"></lucide-icon>
|
|
65
|
+
}
|
|
66
|
+
@default {
|
|
67
|
+
<lucide-icon [img]="infoIcon" [size]="20"></lucide-icon>
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- Content -->
|
|
73
|
+
<div class="toast-content">
|
|
74
|
+
@if (toast.title) {
|
|
75
|
+
<div class="toast-title">{{ toast.title }}</div>
|
|
76
|
+
}
|
|
77
|
+
<div class="toast-message">{{ toast.message }}</div>
|
|
78
|
+
@if (toast.action) {
|
|
79
|
+
<button class="toast-action" (click)="onActionClick(toast)">
|
|
80
|
+
{{ toast.action.label }}
|
|
81
|
+
</button>
|
|
82
|
+
}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Dismiss button -->
|
|
86
|
+
@if (toast.dismissible) {
|
|
87
|
+
<button class="toast-dismiss" (click)="dismiss(toast)" aria-label="Dismiss">
|
|
88
|
+
<lucide-icon [img]="closeIcon" [size]="16"></lucide-icon>
|
|
89
|
+
</button>
|
|
90
|
+
}
|
|
91
|
+
</div>
|
|
92
|
+
}
|
|
93
|
+
</div>
|
|
94
|
+
`, isInline: true, styles: [".toast-container{position:fixed;z-index:9999;display:flex;flex-direction:column;gap:8px;padding:16px;pointer-events:none;max-width:400px}.toast-container.top-right{top:0;right:0}.toast-container.top-left{top:0;left:0}.toast-container.bottom-right{bottom:0;right:0}.toast-container.bottom-left{bottom:0;left:0}.toast-container.top-center{top:0;left:50%;transform:translate(-50%)}.toast-container.bottom-center{bottom:0;left:50%;transform:translate(-50%)}.toast{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 4px 20px #0000001f;pointer-events:auto;animation:toastSlideIn .3s ease}:host-context(.dark) .toast{background:#1e293b;border-color:#334155;box-shadow:0 4px 20px #0006}@keyframes toastSlideIn{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}.toast-icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:1px}.toast-icon.success{color:#22c55e}.toast-icon.error{color:#ef4444}.toast-icon.warning{color:#f59e0b}.toast-icon.info{color:#3b82f6}.toast-content{flex:1;min-width:0}.toast-title{font-size:14px;font-weight:600;color:#1e293b;margin-bottom:2px}:host-context(.dark) .toast-title{color:#f1f5f9}.toast-message{font-size:13px;color:#64748b;line-height:1.4}:host-context(.dark) .toast-message{color:#94a3b8}.toast-action{margin-top:8px;padding:0;border:none;background:transparent;color:#3b82f6;font-size:13px;font-weight:500;cursor:pointer}.toast-action:hover{text-decoration:underline}.toast-dismiss{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:28px;height:28px;margin:-4px -4px -4px 0;border:none;border-radius:6px;background:transparent;color:#94a3b8;cursor:pointer;transition:all .15s ease}.toast-dismiss:hover{background:#f1f5f9;color:#64748b}:host-context(.dark) .toast-dismiss:hover{background:#334155;color:#f1f5f9}.toast.success{border-left:3px solid #22c55e}.toast.error{border-left:3px solid #ef4444}.toast.warning{border-left:3px solid #f59e0b}.toast.info{border-left:3px solid #3b82f6}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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"] }] });
|
|
95
|
+
}
|
|
96
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AgToastContainerComponent, decorators: [{
|
|
97
|
+
type: Component,
|
|
98
|
+
args: [{ selector: 'ag-toast-container', standalone: true, imports: [CommonModule, LucideAngularModule], template: `
|
|
99
|
+
<div [class]="containerClasses">
|
|
100
|
+
@for (toast of toastService.toasts(); track toast.id) {
|
|
101
|
+
<div [class]="getToastClasses(toast)" role="alert">
|
|
102
|
+
<!-- Icon -->
|
|
103
|
+
<div [class]="getIconClasses(toast)">
|
|
104
|
+
@switch (toast.variant) {
|
|
105
|
+
@case ('success') {
|
|
106
|
+
<lucide-icon [img]="successIcon" [size]="20"></lucide-icon>
|
|
107
|
+
}
|
|
108
|
+
@case ('error') {
|
|
109
|
+
<lucide-icon [img]="errorIcon" [size]="20"></lucide-icon>
|
|
110
|
+
}
|
|
111
|
+
@case ('warning') {
|
|
112
|
+
<lucide-icon [img]="warningIcon" [size]="20"></lucide-icon>
|
|
113
|
+
}
|
|
114
|
+
@default {
|
|
115
|
+
<lucide-icon [img]="infoIcon" [size]="20"></lucide-icon>
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<!-- Content -->
|
|
121
|
+
<div class="toast-content">
|
|
122
|
+
@if (toast.title) {
|
|
123
|
+
<div class="toast-title">{{ toast.title }}</div>
|
|
124
|
+
}
|
|
125
|
+
<div class="toast-message">{{ toast.message }}</div>
|
|
126
|
+
@if (toast.action) {
|
|
127
|
+
<button class="toast-action" (click)="onActionClick(toast)">
|
|
128
|
+
{{ toast.action.label }}
|
|
129
|
+
</button>
|
|
130
|
+
}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- Dismiss button -->
|
|
134
|
+
@if (toast.dismissible) {
|
|
135
|
+
<button class="toast-dismiss" (click)="dismiss(toast)" aria-label="Dismiss">
|
|
136
|
+
<lucide-icon [img]="closeIcon" [size]="16"></lucide-icon>
|
|
137
|
+
</button>
|
|
138
|
+
}
|
|
139
|
+
</div>
|
|
140
|
+
}
|
|
141
|
+
</div>
|
|
142
|
+
`, styles: [".toast-container{position:fixed;z-index:9999;display:flex;flex-direction:column;gap:8px;padding:16px;pointer-events:none;max-width:400px}.toast-container.top-right{top:0;right:0}.toast-container.top-left{top:0;left:0}.toast-container.bottom-right{bottom:0;right:0}.toast-container.bottom-left{bottom:0;left:0}.toast-container.top-center{top:0;left:50%;transform:translate(-50%)}.toast-container.bottom-center{bottom:0;left:50%;transform:translate(-50%)}.toast{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 4px 20px #0000001f;pointer-events:auto;animation:toastSlideIn .3s ease}:host-context(.dark) .toast{background:#1e293b;border-color:#334155;box-shadow:0 4px 20px #0006}@keyframes toastSlideIn{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}.toast-icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:1px}.toast-icon.success{color:#22c55e}.toast-icon.error{color:#ef4444}.toast-icon.warning{color:#f59e0b}.toast-icon.info{color:#3b82f6}.toast-content{flex:1;min-width:0}.toast-title{font-size:14px;font-weight:600;color:#1e293b;margin-bottom:2px}:host-context(.dark) .toast-title{color:#f1f5f9}.toast-message{font-size:13px;color:#64748b;line-height:1.4}:host-context(.dark) .toast-message{color:#94a3b8}.toast-action{margin-top:8px;padding:0;border:none;background:transparent;color:#3b82f6;font-size:13px;font-weight:500;cursor:pointer}.toast-action:hover{text-decoration:underline}.toast-dismiss{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:28px;height:28px;margin:-4px -4px -4px 0;border:none;border-radius:6px;background:transparent;color:#94a3b8;cursor:pointer;transition:all .15s ease}.toast-dismiss:hover{background:#f1f5f9;color:#64748b}:host-context(.dark) .toast-dismiss:hover{background:#334155;color:#f1f5f9}.toast.success{border-left:3px solid #22c55e}.toast.error{border-left:3px solid #ef4444}.toast.warning{border-left:3px solid #f59e0b}.toast.info{border-left:3px solid #3b82f6}\n"] }]
|
|
143
|
+
}] });
|
|
144
|
+
|
|
24
145
|
class SmsCardDetailsComponent {
|
|
25
146
|
modalOpen = input(false, ...(ngDevMode ? [{ debugName: "modalOpen" }] : []));
|
|
26
147
|
smsListcollapse = input(false, ...(ngDevMode ? [{ debugName: "smsListcollapse" }] : []));
|
|
@@ -71,6 +192,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
71
192
|
class JobDetailsComponent {
|
|
72
193
|
modalOpen = input(false, ...(ngDevMode ? [{ debugName: "modalOpen" }] : []));
|
|
73
194
|
routeData = input(null, ...(ngDevMode ? [{ debugName: "routeData" }] : []));
|
|
195
|
+
noBackdrop = input(false, ...(ngDevMode ? [{ debugName: "noBackdrop" }] : []));
|
|
74
196
|
modalOpenChange = new EventEmitter();
|
|
75
197
|
dragX = signal(0, ...(ngDevMode ? [{ debugName: "dragX" }] : []));
|
|
76
198
|
dragY = signal(0, ...(ngDevMode ? [{ debugName: "dragY" }] : []));
|
|
@@ -86,6 +208,10 @@ class JobDetailsComponent {
|
|
|
86
208
|
X,
|
|
87
209
|
};
|
|
88
210
|
startDrag(event) {
|
|
211
|
+
const target = event.target;
|
|
212
|
+
if (target.tagName.toLowerCase() === 'button' || target.closest('button')) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
89
215
|
this.isDragging = true;
|
|
90
216
|
this.dragStartX = event.clientX;
|
|
91
217
|
this.dragStartY = event.clientY;
|
|
@@ -103,12 +229,12 @@ class JobDetailsComponent {
|
|
|
103
229
|
this.isDragging = false;
|
|
104
230
|
}
|
|
105
231
|
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-[350px] 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
|
|
232
|
+
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 }, noBackdrop: { classPropertyName: "noBackdrop", publicName: "noBackdrop", 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 \n class=\"z-[111] xl:z-[80] bottom-0 lg:left-[350px] fixed lg:absolute inset-0 overflow-y-auto\"\n [class.pointer-events-none]=\"noBackdrop()\"\n>\n @if (!noBackdrop()) {\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n }\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 w-[350px] pointer-events-auto\"\n [style.transform]=\"'translate(' + dragX() + 'px, ' + dragY() + 'px)'\"\n [style.will-change]=\"'transform'\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"startDrag($event)\"\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 select-none\"\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 text-gray-500\"\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 completed\">\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 pending\">\n <span class=\"stat-count\">{{ routeData()?.values?.Ongoing || 0 }}</span>\n <span class=\"stat-label\">Ongoing</span>\n </div>\n\n <div class=\"stat-item active\">\n <span class=\"stat-count\">{{ routeData()?.values?.Open || 0 }}</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:30px;font-size:12px;margin:0 10px 0 60px}.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
233
|
}
|
|
108
234
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JobDetailsComponent, decorators: [{
|
|
109
235
|
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-[350px] 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
|
|
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: [{
|
|
236
|
+
args: [{ selector: 'lib-job-details', standalone: true, imports: [LucideAngularModule, NgClass], template: "@if (modalOpen()) {\n<div \n class=\"z-[111] xl:z-[80] bottom-0 lg:left-[350px] fixed lg:absolute inset-0 overflow-y-auto\"\n [class.pointer-events-none]=\"noBackdrop()\"\n>\n @if (!noBackdrop()) {\n <div class=\"fixed inset-0 bg-black/50\" (click)=\"close()\"></div>\n }\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 w-[350px] pointer-events-auto\"\n [style.transform]=\"'translate(' + dragX() + 'px, ' + dragY() + 'px)'\"\n [style.will-change]=\"'transform'\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"startDrag($event)\"\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 select-none\"\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 text-gray-500\"\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 completed\">\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 pending\">\n <span class=\"stat-count\">{{ routeData()?.values?.Ongoing || 0 }}</span>\n <span class=\"stat-label\">Ongoing</span>\n </div>\n\n <div class=\"stat-item active\">\n <span class=\"stat-count\">{{ routeData()?.values?.Open || 0 }}</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:30px;font-size:12px;margin:0 10px 0 60px}.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"] }]
|
|
237
|
+
}], propDecorators: { modalOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalOpen", required: false }] }], routeData: [{ type: i0.Input, args: [{ isSignal: true, alias: "routeData", required: false }] }], noBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "noBackdrop", required: false }] }], modalOpenChange: [{
|
|
112
238
|
type: Output
|
|
113
239
|
}], onMouseMove: [{
|
|
114
240
|
type: HostListener,
|
|
@@ -124,6 +250,14 @@ class JobCodeComponent {
|
|
|
124
250
|
coolmapService = inject(CoolmapService);
|
|
125
251
|
showDriverModal = false;
|
|
126
252
|
showDetailModal = false;
|
|
253
|
+
constructor() {
|
|
254
|
+
effect(() => {
|
|
255
|
+
if (this.coolmapService.resetModals() > 0) {
|
|
256
|
+
this.closeDetail(false);
|
|
257
|
+
this.showDriverModal = false;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
127
261
|
listMode = 'sidebar';
|
|
128
262
|
collapsible = true;
|
|
129
263
|
routes = [];
|
|
@@ -136,10 +270,12 @@ class JobCodeComponent {
|
|
|
136
270
|
contextMenuVisible = signal(false, ...(ngDevMode ? [{ debugName: "contextMenuVisible" }] : []));
|
|
137
271
|
driverListcollapse = signal(false, ...(ngDevMode ? [{ debugName: "driverListcollapse" }] : []));
|
|
138
272
|
contextMenuPosition = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "contextMenuPosition" }] : []));
|
|
273
|
+
isClickTriggered = false;
|
|
274
|
+
pinnedJobDetail = null;
|
|
139
275
|
icons = {
|
|
140
276
|
CarFront,
|
|
141
277
|
Info,
|
|
142
|
-
LoaderCircle
|
|
278
|
+
LoaderCircle,
|
|
143
279
|
};
|
|
144
280
|
toggleRouteSelection(route) {
|
|
145
281
|
const id = route.job_id || route.route_id || route.route_details_id;
|
|
@@ -166,13 +302,37 @@ class JobCodeComponent {
|
|
|
166
302
|
return Math.round((100 * (values.Open || 0)) / total);
|
|
167
303
|
return 0;
|
|
168
304
|
}
|
|
305
|
+
openDetail(route, isClick) {
|
|
306
|
+
this.selectedJobDetail.set(route);
|
|
307
|
+
this.showDetailModal = true;
|
|
308
|
+
if (isClick) {
|
|
309
|
+
this.isClickTriggered = true;
|
|
310
|
+
this.pinnedJobDetail = route;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
closeDetail(isMouseLeave) {
|
|
314
|
+
if (isMouseLeave) {
|
|
315
|
+
if (this.isClickTriggered && this.pinnedJobDetail) {
|
|
316
|
+
this.selectedJobDetail.set(this.pinnedJobDetail);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
this.showDetailModal = false;
|
|
320
|
+
this.selectedJobDetail.set(null);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
this.showDetailModal = false;
|
|
324
|
+
this.selectedJobDetail.set(null);
|
|
325
|
+
this.isClickTriggered = false;
|
|
326
|
+
this.pinnedJobDetail = null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
169
329
|
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"] }] });
|
|
330
|
+
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 @if(routes.length){\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)=\"openDetail(route, true); $event.stopPropagation()\"\n (mouseenter)=\"openDetail(route, false)\"\n (mouseleave)=\"closeDetail(true)\"\n >\n <lucide-icon [img]=\"icons.Info\" [size]=\"20\"></lucide-icon>\n </div>\n </div>\n </div>\n <div class=\"customer-name font-semibold truncate\">{{ route.customer_name }}</div>\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 } @else {\n <div class=\"empty-state\">\n <div class=\"text-gray-600 dark:text-gray-400 mt-2 text-center\">No Job code found.</div>\n </div>\n }\n </div>\n }\n</div>\n<lib-driver-list\n [(modalOpen)]=\"showDriverModal\"\n [driverListcollapse]=\"driverListcollapse()\"\n></lib-driver-list>\n<lib-job-details \n [modalOpen]=\"showDetailModal\" \n (modalOpenChange)=\"$event ? null : closeDetail(false)\" \n [routeData]=\"selectedJobDetail()\" \n [noBackdrop]=\"!isClickTriggered\"\n></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", "noBackdrop"], outputs: ["modalOpenChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
171
331
|
}
|
|
172
332
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JobCodeComponent, decorators: [{
|
|
173
333
|
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: [{
|
|
334
|
+
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 @if(routes.length){\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)=\"openDetail(route, true); $event.stopPropagation()\"\n (mouseenter)=\"openDetail(route, false)\"\n (mouseleave)=\"closeDetail(true)\"\n >\n <lucide-icon [img]=\"icons.Info\" [size]=\"20\"></lucide-icon>\n </div>\n </div>\n </div>\n <div class=\"customer-name font-semibold truncate\">{{ route.customer_name }}</div>\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 } @else {\n <div class=\"empty-state\">\n <div class=\"text-gray-600 dark:text-gray-400 mt-2 text-center\">No Job code found.</div>\n </div>\n }\n </div>\n }\n</div>\n<lib-driver-list\n [(modalOpen)]=\"showDriverModal\"\n [driverListcollapse]=\"driverListcollapse()\"\n></lib-driver-list>\n<lib-job-details \n [modalOpen]=\"showDetailModal\" \n (modalOpenChange)=\"$event ? null : closeDetail(false)\" \n [routeData]=\"selectedJobDetail()\" \n [noBackdrop]=\"!isClickTriggered\"\n></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"] }]
|
|
335
|
+
}], ctorParameters: () => [], propDecorators: { listMode: [{
|
|
176
336
|
type: Input
|
|
177
337
|
}], collapsible: [{
|
|
178
338
|
type: Input
|
|
@@ -194,6 +354,7 @@ class RouteInfoCardComponent {
|
|
|
194
354
|
routeData = input(null, ...(ngDevMode ? [{ debugName: "routeData" }] : []));
|
|
195
355
|
repository = input('coolmap', ...(ngDevMode ? [{ debugName: "repository" }] : []));
|
|
196
356
|
displayMode = input('dispatch', ...(ngDevMode ? [{ debugName: "displayMode" }] : []));
|
|
357
|
+
noBackdrop = input(false, ...(ngDevMode ? [{ debugName: "noBackdrop" }] : []));
|
|
197
358
|
modalOpenChange = new EventEmitter();
|
|
198
359
|
edit = new EventEmitter();
|
|
199
360
|
utils = inject(UtilsService);
|
|
@@ -211,12 +372,12 @@ class RouteInfoCardComponent {
|
|
|
211
372
|
X,
|
|
212
373
|
};
|
|
213
374
|
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-[350px]' : 'lg:right-[350px]'\"\n
|
|
375
|
+
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 }, noBackdrop: { classPropertyName: "noBackdrop", publicName: "noBackdrop", 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-[350px]' : 'lg:right-[350px]'\"\n [class.pointer-events-none]=\"noBackdrop()\"\n>\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] pointer-events-auto\"\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
376
|
}
|
|
216
377
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: RouteInfoCardComponent, decorators: [{
|
|
217
378
|
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-[350px]' : 'lg:right-[350px]'\"\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: [{
|
|
379
|
+
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-[350px]' : 'lg:right-[350px]'\"\n [class.pointer-events-none]=\"noBackdrop()\"\n>\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] pointer-events-auto\"\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"] }]
|
|
380
|
+
}], 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 }] }], noBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "noBackdrop", required: false }] }], modalOpenChange: [{
|
|
220
381
|
type: Output
|
|
221
382
|
}], edit: [{
|
|
222
383
|
type: Output
|
|
@@ -238,7 +399,7 @@ class JobRouteListComponent {
|
|
|
238
399
|
const term = this.searchTerm();
|
|
239
400
|
if (term.length < 1)
|
|
240
401
|
return [];
|
|
241
|
-
return this.utils.filter(term, this.filters());
|
|
402
|
+
return this.utils.filter(term, this.filters()).filter((opt) => opt.type !== 'material');
|
|
242
403
|
}, ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
|
|
243
404
|
filteredRoutes = computed(() => {
|
|
244
405
|
let list = this.routes();
|
|
@@ -279,9 +440,17 @@ class JobRouteListComponent {
|
|
|
279
440
|
}
|
|
280
441
|
}
|
|
281
442
|
selectedRoute = signal(null, ...(ngDevMode ? [{ debugName: "selectedRoute" }] : []));
|
|
443
|
+
constructor() {
|
|
444
|
+
effect(() => {
|
|
445
|
+
if (this.coolmapService.resetModals() > 0) {
|
|
446
|
+
this.closeDetail(false);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
282
450
|
modalOpenChange = new EventEmitter();
|
|
283
451
|
routeSelect = new EventEmitter();
|
|
284
452
|
masterToggleEvent = new EventEmitter();
|
|
453
|
+
shareRoute = new EventEmitter();
|
|
285
454
|
ngOnInit() {
|
|
286
455
|
this.checkAndCallRouteList();
|
|
287
456
|
}
|
|
@@ -334,6 +503,7 @@ class JobRouteListComponent {
|
|
|
334
503
|
return unitName || data.unit;
|
|
335
504
|
}
|
|
336
505
|
close() {
|
|
506
|
+
this.closeDetail(false);
|
|
337
507
|
this.modalOpenChange.emit(false);
|
|
338
508
|
}
|
|
339
509
|
isRouteSelected(route) {
|
|
@@ -380,20 +550,47 @@ class JobRouteListComponent {
|
|
|
380
550
|
getRouteId(route) {
|
|
381
551
|
return route.job_id || route.route_id || route.route_details_id || '';
|
|
382
552
|
}
|
|
553
|
+
isClickTriggered = false;
|
|
554
|
+
pinnedRouteDetail = null;
|
|
555
|
+
openDetail(route, isClick) {
|
|
556
|
+
this.selectedRoute.set(route);
|
|
557
|
+
this.showRouteModal = true;
|
|
558
|
+
if (isClick) {
|
|
559
|
+
this.isClickTriggered = true;
|
|
560
|
+
this.pinnedRouteDetail = route;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
closeDetail(isMouseLeave) {
|
|
564
|
+
if (isMouseLeave) {
|
|
565
|
+
if (this.isClickTriggered && this.pinnedRouteDetail) {
|
|
566
|
+
this.selectedRoute.set(this.pinnedRouteDetail);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
this.showRouteModal = false;
|
|
570
|
+
this.selectedRoute.set(null);
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
this.showRouteModal = false;
|
|
574
|
+
this.selectedRoute.set(null);
|
|
575
|
+
this.isClickTriggered = false;
|
|
576
|
+
this.pinnedRouteDetail = null;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
383
579
|
icons = {
|
|
384
580
|
X,
|
|
385
581
|
Info,
|
|
386
582
|
Share2,
|
|
583
|
+
Search,
|
|
387
584
|
CheckCircle,
|
|
388
585
|
LoaderCircle
|
|
389
586
|
};
|
|
390
587
|
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] route-list\">\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;height:100vh}: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"] }] });
|
|
588
|
+
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", shareRoute: "shareRoute" }, ngImport: i0, template: "@if (modalOpen()) {\n<div class=\"fixed z-[111] route-list\">\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.Search\" 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=\"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-black dark:text-white 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-230px)]\">\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\" \n (click)=\"$event.stopPropagation(); openDetail(route, true)\"\n (mouseenter)=\"openDetail(route, false)\"\n (mouseleave)=\"closeDetail(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 hover:opacity-80\" (click)=\"$event.stopPropagation(); shareRoute.emit(route)\">\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\" (modalOpenChange)=\"$event ? null : closeDetail(false)\" [routeData]=\"selectedRoute()\" [repository]=\"config.repository\" displayMode=\"dispatch\" [noBackdrop]=\"!isClickTriggered\"></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;height:100vh}:host-context(.dark) .detail-drawer{background-color:#1e293b;border-color:#334155}.drawer-listing-box{flex:1;overflow:hidden;padding:0!important}.routeList-viewport{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", "noBackdrop"], 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
589
|
}
|
|
393
590
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JobRouteListComponent, decorators: [{
|
|
394
591
|
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] route-list\">\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;height:100vh}: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: [{
|
|
592
|
+
args: [{ selector: 'lib-job-route-list', standalone: true, imports: [LucideAngularModule, RouteInfoCardComponent, NgClass, ScrollingModule], template: "@if (modalOpen()) {\n<div class=\"fixed z-[111] route-list\">\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.Search\" 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=\"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-black dark:text-white 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-230px)]\">\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\" \n (click)=\"$event.stopPropagation(); openDetail(route, true)\"\n (mouseenter)=\"openDetail(route, false)\"\n (mouseleave)=\"closeDetail(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 hover:opacity-80\" (click)=\"$event.stopPropagation(); shareRoute.emit(route)\">\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\" (modalOpenChange)=\"$event ? null : closeDetail(false)\" [routeData]=\"selectedRoute()\" [repository]=\"config.repository\" displayMode=\"dispatch\" [noBackdrop]=\"!isClickTriggered\"></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;height:100vh}:host-context(.dark) .detail-drawer{background-color:#1e293b;border-color:#334155}.drawer-listing-box{flex:1;overflow:hidden;padding:0!important}.routeList-viewport{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"] }]
|
|
593
|
+
}], ctorParameters: () => [], propDecorators: { customerRepoDetails: [{
|
|
397
594
|
type: Input
|
|
398
595
|
}], selectedRouteIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedRouteIds", required: false }] }], modalOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalOpen", required: false }] }], initialRoutes: [{
|
|
399
596
|
type: Input
|
|
@@ -403,12 +600,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
403
600
|
type: Output
|
|
404
601
|
}], masterToggleEvent: [{
|
|
405
602
|
type: Output
|
|
603
|
+
}], shareRoute: [{
|
|
604
|
+
type: Output
|
|
406
605
|
}] } });
|
|
407
606
|
|
|
408
607
|
class ViewRouteListComponent {
|
|
409
608
|
showRouteModal = false;
|
|
410
609
|
selectedRoute = signal(null, ...(ngDevMode ? [{ debugName: "selectedRoute" }] : []));
|
|
411
610
|
config = inject(COOLMAP_CONFIG);
|
|
611
|
+
coolmapService = inject(CoolmapService);
|
|
612
|
+
constructor() {
|
|
613
|
+
effect(() => {
|
|
614
|
+
if (this.coolmapService.resetModals() > 0) {
|
|
615
|
+
this.closeDetail(false);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
412
619
|
listMode = 'sidebar';
|
|
413
620
|
collapsible = true;
|
|
414
621
|
routes = [];
|
|
@@ -426,22 +633,42 @@ class ViewRouteListComponent {
|
|
|
426
633
|
this.collapsed = !this.collapsed;
|
|
427
634
|
}
|
|
428
635
|
}
|
|
429
|
-
toggleRouteSelection(route) {
|
|
430
|
-
const id = this.getRouteId(route);
|
|
431
|
-
if (!id)
|
|
432
|
-
return;
|
|
433
|
-
this.routeSelect.emit(id);
|
|
434
|
-
}
|
|
435
636
|
getRouteId(route) {
|
|
436
637
|
return route.job_id || route.route_id || route.route_details_id || '';
|
|
437
638
|
}
|
|
639
|
+
isClickTriggered = false;
|
|
640
|
+
pinnedRouteDetail = null;
|
|
641
|
+
openDetail(route, isClick) {
|
|
642
|
+
this.selectedRoute.set(route);
|
|
643
|
+
this.showRouteModal = true;
|
|
644
|
+
if (isClick) {
|
|
645
|
+
this.isClickTriggered = true;
|
|
646
|
+
this.pinnedRouteDetail = route;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
closeDetail(isMouseLeave) {
|
|
650
|
+
if (isMouseLeave) {
|
|
651
|
+
if (this.isClickTriggered && this.pinnedRouteDetail) {
|
|
652
|
+
this.selectedRoute.set(this.pinnedRouteDetail);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
this.showRouteModal = false;
|
|
656
|
+
this.selectedRoute.set(null);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
this.showRouteModal = false;
|
|
660
|
+
this.selectedRoute.set(null);
|
|
661
|
+
this.isClickTriggered = false;
|
|
662
|
+
this.pinnedRouteDetail = null;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
438
665
|
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\"
|
|
666
|
+
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\">\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 (mouseenter)=\"openDetail(route, false)\"\n (mouseleave)=\"closeDetail(true)\"\n (click)=\"$event.stopPropagation(); openDetail(route, true)\"\n >\n <lucide-icon [img]=\"icons.Info\" [size]=\"18\"></lucide-icon>\n </button>\n </div>\n </div>\n \n @if (config.repository === 'coolmap' && 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] flex items-start 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 bullet mt-1\"></div>\n {{ (route.pickup_location || '').split('|')[1] || route.pickup_location || 'N/A' }}\n </div>\n <div class=\"text-[11px] flex items-start 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 bullet mt-1\"></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 (modalOpenChange)=\"$event ? (showRouteModal = true) : closeDetail(false)\"\n [routeData]=\"selectedRoute()\" \n [repository]=\"config.repository\" \n displayMode=\"catalog\"\n [noBackdrop]=\"true\"\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:#ff7272}.statusunit.Load{background:#a3c52e}.statusunit.Hourly{background:#ae23d1}.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", "noBackdrop"], outputs: ["modalOpenChange", "edit"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
440
667
|
}
|
|
441
668
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ViewRouteListComponent, decorators: [{
|
|
442
669
|
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\"
|
|
444
|
-
}], propDecorators: { listMode: [{
|
|
670
|
+
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\">\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 (mouseenter)=\"openDetail(route, false)\"\n (mouseleave)=\"closeDetail(true)\"\n (click)=\"$event.stopPropagation(); openDetail(route, true)\"\n >\n <lucide-icon [img]=\"icons.Info\" [size]=\"18\"></lucide-icon>\n </button>\n </div>\n </div>\n \n @if (config.repository === 'coolmap' && 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] flex items-start 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 bullet mt-1\"></div>\n {{ (route.pickup_location || '').split('|')[1] || route.pickup_location || 'N/A' }}\n </div>\n <div class=\"text-[11px] flex items-start 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 bullet mt-1\"></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 (modalOpenChange)=\"$event ? (showRouteModal = true) : closeDetail(false)\"\n [routeData]=\"selectedRoute()\" \n [repository]=\"config.repository\" \n displayMode=\"catalog\"\n [noBackdrop]=\"true\"\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:#ff7272}.statusunit.Load{background:#a3c52e}.statusunit.Hourly{background:#ae23d1}.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"] }]
|
|
671
|
+
}], ctorParameters: () => [], propDecorators: { listMode: [{
|
|
445
672
|
type: Input
|
|
446
673
|
}], collapsible: [{
|
|
447
674
|
type: Input
|
|
@@ -501,6 +728,7 @@ class AddRouteComponent {
|
|
|
501
728
|
routeId = signal(null, ...(ngDevMode ? [{ debugName: "routeId" }] : []));
|
|
502
729
|
preventSave = signal(false, ...(ngDevMode ? [{ debugName: "preventSave" }] : []));
|
|
503
730
|
preventInitialSave = signal(false, ...(ngDevMode ? [{ debugName: "preventInitialSave" }] : []));
|
|
731
|
+
showLoader = signal(false, ...(ngDevMode ? [{ debugName: "showLoader" }] : []));
|
|
504
732
|
// Search options
|
|
505
733
|
customerOptions = signal([], ...(ngDevMode ? [{ debugName: "customerOptions" }] : []));
|
|
506
734
|
pickupOptions = signal([], ...(ngDevMode ? [{ debugName: "pickupOptions" }] : []));
|
|
@@ -511,6 +739,16 @@ class AddRouteComponent {
|
|
|
511
739
|
showDeliveryDropdown = signal(false, ...(ngDevMode ? [{ debugName: "showDeliveryDropdown" }] : []));
|
|
512
740
|
loadingLocations = signal(false, ...(ngDevMode ? [{ debugName: "loadingLocations" }] : []));
|
|
513
741
|
loadingCustomers = signal(false, ...(ngDevMode ? [{ debugName: "loadingCustomers" }] : []));
|
|
742
|
+
pickupSuggestions = signal([], ...(ngDevMode ? [{ debugName: "pickupSuggestions" }] : []));
|
|
743
|
+
deliverySuggestions = signal([], ...(ngDevMode ? [{ debugName: "deliverySuggestions" }] : []));
|
|
744
|
+
showPickupSuggestions = signal(false, ...(ngDevMode ? [{ debugName: "showPickupSuggestions" }] : []));
|
|
745
|
+
showDeliverySuggestions = signal(false, ...(ngDevMode ? [{ debugName: "showDeliverySuggestions" }] : []));
|
|
746
|
+
pickupSelected = signal(false, ...(ngDevMode ? [{ debugName: "pickupSelected" }] : []));
|
|
747
|
+
deliverySelected = signal(false, ...(ngDevMode ? [{ debugName: "deliverySelected" }] : []));
|
|
748
|
+
saveAttempted = signal(false, ...(ngDevMode ? [{ debugName: "saveAttempted" }] : []));
|
|
749
|
+
placesLibrary = null;
|
|
750
|
+
sessionToken = null;
|
|
751
|
+
debounceTimer = null;
|
|
514
752
|
hideDropdown(type) {
|
|
515
753
|
setTimeout(() => {
|
|
516
754
|
if (type === 'customer')
|
|
@@ -538,16 +776,15 @@ class AddRouteComponent {
|
|
|
538
776
|
return unit ? unit.type : 'Select Unit/Driver';
|
|
539
777
|
}
|
|
540
778
|
constructor() {
|
|
541
|
-
// Effect for handling routeData Input
|
|
542
779
|
effect(() => {
|
|
780
|
+
const isOpen = this.modalOpen();
|
|
543
781
|
const data = this.routeData();
|
|
544
|
-
|
|
545
|
-
this.inIt(data);
|
|
546
|
-
}, 0);
|
|
547
|
-
});
|
|
548
|
-
effect(() => {
|
|
549
|
-
if (this.modalOpen()) {
|
|
782
|
+
if (isOpen) {
|
|
550
783
|
this.coolMapService.clearAllRoutes();
|
|
784
|
+
// Initial patching
|
|
785
|
+
setTimeout(() => {
|
|
786
|
+
this.inIt(data);
|
|
787
|
+
}, 0);
|
|
551
788
|
this.utils.fetchUnitsList().then((res) => {
|
|
552
789
|
this.unitsList.set(res || []);
|
|
553
790
|
});
|
|
@@ -569,10 +806,16 @@ class AddRouteComponent {
|
|
|
569
806
|
this.customerOptions.set(res || []);
|
|
570
807
|
this.addRouteForm.controls['customer_name'].enable();
|
|
571
808
|
this.loadingCustomers.set(false);
|
|
809
|
+
if (this.config.repository === 'customer' && this.customerRepoDetails()) {
|
|
810
|
+
this.addRouteForm.patchValue({
|
|
811
|
+
customer_id: this.customerRepoDetails().customer?.id,
|
|
812
|
+
customer_name: this.customerRepoDetails().customer?.name,
|
|
813
|
+
}, { emitEvent: false });
|
|
814
|
+
}
|
|
572
815
|
});
|
|
573
816
|
}
|
|
574
817
|
setTimeout(() => {
|
|
575
|
-
this.
|
|
818
|
+
this.initializePlacesLibrary();
|
|
576
819
|
}, 150);
|
|
577
820
|
}
|
|
578
821
|
});
|
|
@@ -629,60 +872,122 @@ class AddRouteComponent {
|
|
|
629
872
|
// Listen for search mode changes
|
|
630
873
|
this.addRouteForm.get('pickUpSearchOption')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
|
|
631
874
|
this.addRouteForm.patchValue({ pickup_location: null, pickup_lat_lng: null });
|
|
875
|
+
this.coolMapService.removeRouteAndMarker(1, 'addroute');
|
|
632
876
|
});
|
|
633
877
|
this.addRouteForm.get('deliverySearchOtption')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
|
|
634
878
|
this.addRouteForm.patchValue({ delivery_location: null, delivery_lat_lng: null });
|
|
879
|
+
this.coolMapService.removeRouteAndMarker(1, 'addroute');
|
|
635
880
|
});
|
|
636
881
|
}
|
|
637
|
-
async
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
882
|
+
async initializePlacesLibrary() {
|
|
883
|
+
try {
|
|
884
|
+
this.placesLibrary = await window.google?.maps?.importLibrary('places');
|
|
885
|
+
if (this.placesLibrary) {
|
|
886
|
+
this.sessionToken = new this.placesLibrary.AutocompleteSessionToken();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
catch (error) {
|
|
890
|
+
console.error('Error loading Google Maps Places library:', error);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
onInputChange(event, type) {
|
|
894
|
+
const value = event.target.value;
|
|
895
|
+
const formKey = type === 'pickup' ? 'pickup_location' : 'delivery_location';
|
|
896
|
+
const latLngKey = type === 'pickup' ? 'pickup_lat_lng' : 'delivery_lat_lng';
|
|
897
|
+
this.addRouteForm.patchValue({
|
|
898
|
+
[formKey]: value,
|
|
899
|
+
[latLngKey]: ''
|
|
900
|
+
}, { emitEvent: false });
|
|
901
|
+
if (type === 'pickup')
|
|
902
|
+
this.pickupSelected.set(false);
|
|
903
|
+
else
|
|
904
|
+
this.deliverySelected.set(false);
|
|
905
|
+
this.checkAndFetchRouteInformation();
|
|
906
|
+
clearTimeout(this.debounceTimer);
|
|
907
|
+
this.debounceTimer = setTimeout(() => {
|
|
908
|
+
if (value.trim()) {
|
|
909
|
+
this.fetchAutocompleteSuggestions(value, type);
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
this.hideSuggestions(type);
|
|
913
|
+
}
|
|
914
|
+
}, 300);
|
|
915
|
+
}
|
|
916
|
+
onFocus(type) {
|
|
917
|
+
if (type === 'pickup') {
|
|
918
|
+
if (this.isSystemPickup) {
|
|
919
|
+
this.showPickupDropdown.set(true);
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
const val = this.addRouteForm.get('pickup_location')?.value;
|
|
923
|
+
if (val && val.trim())
|
|
924
|
+
this.fetchAutocompleteSuggestions(val, type);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
if (this.isSystemDelivery) {
|
|
929
|
+
this.showDeliveryDropdown.set(true);
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
const val = this.addRouteForm.get('delivery_location')?.value;
|
|
933
|
+
if (val && val.trim())
|
|
934
|
+
this.fetchAutocompleteSuggestions(val, type);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
async fetchAutocompleteSuggestions(input, type) {
|
|
939
|
+
if (!this.placesLibrary || !input.trim() || !this.sessionToken)
|
|
940
|
+
return;
|
|
941
|
+
try {
|
|
942
|
+
const request = {
|
|
943
|
+
input: input.trim(),
|
|
944
|
+
sessionToken: this.sessionToken,
|
|
945
|
+
includedRegionCodes: ['US']
|
|
946
|
+
};
|
|
947
|
+
const { suggestions } = await this.placesLibrary.AutocompleteSuggestion.fetchAutocompleteSuggestions(request);
|
|
948
|
+
if (type === 'pickup') {
|
|
949
|
+
this.pickupSuggestions.set(suggestions || []);
|
|
950
|
+
this.showPickupSuggestions.set(this.pickupSuggestions().length > 0);
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
this.deliverySuggestions.set(suggestions || []);
|
|
954
|
+
this.showDeliverySuggestions.set(this.deliverySuggestions().length > 0);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
console.error('Autocomplete error:', error);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
async onSuggestionClick(suggestion, type) {
|
|
962
|
+
if (!suggestion.placePrediction)
|
|
641
963
|
return;
|
|
642
|
-
// The google API wrapper may require raw DOM event access depending on module configuration
|
|
643
964
|
try {
|
|
644
|
-
|
|
965
|
+
const place = suggestion.placePrediction.toPlace();
|
|
966
|
+
await place.fetchFields({ fields: ['displayName', 'formattedAddress', 'location'] });
|
|
967
|
+
const placeData = place.toJSON();
|
|
645
968
|
const res = {
|
|
646
|
-
lat:
|
|
647
|
-
lng:
|
|
648
|
-
formatted_address:
|
|
969
|
+
lat: placeData.location.lat,
|
|
970
|
+
lng: placeData.location.lng,
|
|
971
|
+
formatted_address: placeData.formattedAddress || placeData.displayName
|
|
649
972
|
};
|
|
650
973
|
this.patchAddressValue(res, type);
|
|
974
|
+
this.hideSuggestions(type);
|
|
975
|
+
if (this.placesLibrary) {
|
|
976
|
+
this.sessionToken = new this.placesLibrary.AutocompleteSessionToken();
|
|
977
|
+
}
|
|
651
978
|
}
|
|
652
|
-
catch (
|
|
653
|
-
console.
|
|
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
|
-
});
|
|
979
|
+
catch (error) {
|
|
980
|
+
console.error('Error selecting suggestion:', error);
|
|
672
981
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
};
|
|
683
|
-
this.patchAddressValue(res, 'delivery');
|
|
684
|
-
}
|
|
685
|
-
});
|
|
982
|
+
}
|
|
983
|
+
hideSuggestions(type) {
|
|
984
|
+
if (type === 'pickup') {
|
|
985
|
+
this.showPickupSuggestions.set(false);
|
|
986
|
+
this.pickupSuggestions.set([]);
|
|
987
|
+
}
|
|
988
|
+
else {
|
|
989
|
+
this.showDeliverySuggestions.set(false);
|
|
990
|
+
this.deliverySuggestions.set([]);
|
|
686
991
|
}
|
|
687
992
|
}
|
|
688
993
|
inIt(changes) {
|
|
@@ -706,7 +1011,10 @@ class AddRouteComponent {
|
|
|
706
1011
|
const isCustomerRepo = this.config.repository === 'customer';
|
|
707
1012
|
if (data) {
|
|
708
1013
|
this.addRouteForm.patchValue(data, { emitEvent: false });
|
|
1014
|
+
this.pickupSelected.set(!!data.pickup_location);
|
|
1015
|
+
this.deliverySelected.set(!!data.delivery_location);
|
|
709
1016
|
}
|
|
1017
|
+
this.saveAttempted.set(false);
|
|
710
1018
|
if (isCustomerRepo && this.customerRepoDetails()) {
|
|
711
1019
|
this.addRouteForm.patchValue({
|
|
712
1020
|
customer_id: this.customerRepoDetails().customer?.id,
|
|
@@ -729,6 +1037,11 @@ class AddRouteComponent {
|
|
|
729
1037
|
[type + '_location']: data.formatted_address,
|
|
730
1038
|
[type + '_lat_lng']: `${data.lat},${data.lng}`
|
|
731
1039
|
});
|
|
1040
|
+
this.addRouteForm.markAsDirty();
|
|
1041
|
+
if (type === 'pickup')
|
|
1042
|
+
this.pickupSelected.set(true);
|
|
1043
|
+
else
|
|
1044
|
+
this.deliverySelected.set(true);
|
|
732
1045
|
this.checkAndFetchRouteInformation();
|
|
733
1046
|
}
|
|
734
1047
|
checkAndFetchRouteInformation(isinitial) {
|
|
@@ -761,7 +1074,7 @@ class AddRouteComponent {
|
|
|
761
1074
|
type: 'FeatureCollection',
|
|
762
1075
|
features: [{ type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: path } }]
|
|
763
1076
|
};
|
|
764
|
-
this.coolMapService.removeRouteAndMarker(1).then(() => {
|
|
1077
|
+
this.coolMapService.removeRouteAndMarker(1, 'addroute').then(() => {
|
|
765
1078
|
this.coolMapService.loadMapProperty(element, 1, unit?.type, {
|
|
766
1079
|
pickup_location: val.pickup_location,
|
|
767
1080
|
delivery_location: val.delivery_location,
|
|
@@ -791,13 +1104,24 @@ class AddRouteComponent {
|
|
|
791
1104
|
}
|
|
792
1105
|
}
|
|
793
1106
|
saveRoute() {
|
|
1107
|
+
this.saveAttempted.set(true);
|
|
1108
|
+
if (!this.pickupSelected()) {
|
|
1109
|
+
this.utils.openSnackBar('Please select a valid pickup location from suggestions', 'error');
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (!this.deliverySelected()) {
|
|
1113
|
+
this.utils.openSnackBar('Please select a valid delivery location from suggestions', 'error');
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
794
1116
|
if (this.addRouteForm.valid) {
|
|
795
1117
|
const data = this.addRouteForm.getRawValue();
|
|
796
1118
|
const endpoint = this.routeId() ? 'update/routes' : 'add/routes';
|
|
797
1119
|
if (this.routeId())
|
|
798
1120
|
data.id = this.routeId();
|
|
1121
|
+
this.showLoader.set(true);
|
|
799
1122
|
this.utils.postdata(endpoint, data).subscribe({
|
|
800
1123
|
next: (res) => {
|
|
1124
|
+
this.showLoader.set(false);
|
|
801
1125
|
if (res.success) {
|
|
802
1126
|
this.utils.openSnackBar(res.message, 'success');
|
|
803
1127
|
// Emit the saved data for local synchronization
|
|
@@ -807,10 +1131,21 @@ class AddRouteComponent {
|
|
|
807
1131
|
else {
|
|
808
1132
|
this.utils.openSnackBar(res.message, 'error');
|
|
809
1133
|
}
|
|
1134
|
+
},
|
|
1135
|
+
error: (err) => {
|
|
1136
|
+
this.showLoader.set(false);
|
|
1137
|
+
this.utils.openSnackBar('Something went wrong. Please try again.', 'error');
|
|
810
1138
|
}
|
|
811
1139
|
});
|
|
812
1140
|
}
|
|
813
1141
|
else {
|
|
1142
|
+
console.log('AddRoute Form invalid:', this.addRouteForm.errors, this.addRouteForm.value);
|
|
1143
|
+
Object.keys(this.addRouteForm.controls).forEach(key => {
|
|
1144
|
+
const controlErrors = this.addRouteForm.get(key)?.errors;
|
|
1145
|
+
if (controlErrors != null) {
|
|
1146
|
+
console.log('Key: ' + key + ', err:', controlErrors);
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
814
1149
|
this.addRouteForm.markAllAsTouched();
|
|
815
1150
|
}
|
|
816
1151
|
}
|
|
@@ -842,7 +1177,7 @@ class AddRouteComponent {
|
|
|
842
1177
|
this.modalOpenChange.emit(false);
|
|
843
1178
|
}
|
|
844
1179
|
icons = {
|
|
845
|
-
X, ChevronDown, Check, LoaderCircle
|
|
1180
|
+
X, ChevronDown, Check, LoaderCircle, Info
|
|
846
1181
|
};
|
|
847
1182
|
// Dynamic dropdown state logic
|
|
848
1183
|
dropdownOpen = signal(false, ...(ngDevMode ? [{ debugName: "dropdownOpen" }] : []));
|
|
@@ -855,10 +1190,25 @@ class AddRouteComponent {
|
|
|
855
1190
|
this.dropdownOpen.set(false);
|
|
856
1191
|
}
|
|
857
1192
|
closeDropdowns(event) {
|
|
858
|
-
// Basic click-outside wrapper
|
|
859
1193
|
const target = event.target;
|
|
1194
|
+
const isPickupArea = target === this.filterPickup()?.nativeElement ||
|
|
1195
|
+
target.closest('.pickup-suggestions') ||
|
|
1196
|
+
target.closest('.pickup-system-dropdown');
|
|
1197
|
+
const isDeliveryArea = target === this.filterDelivery()?.nativeElement ||
|
|
1198
|
+
target.closest('.delivery-suggestions') ||
|
|
1199
|
+
target.closest('.delivery-system-dropdown');
|
|
1200
|
+
if (!isPickupArea) {
|
|
1201
|
+
this.showPickupSuggestions.set(false);
|
|
1202
|
+
this.showPickupDropdown.set(false);
|
|
1203
|
+
}
|
|
1204
|
+
if (!isDeliveryArea) {
|
|
1205
|
+
this.showDeliverySuggestions.set(false);
|
|
1206
|
+
this.showDeliveryDropdown.set(false);
|
|
1207
|
+
}
|
|
1208
|
+
// Close other dropdowns if click is outside any relative container
|
|
860
1209
|
if (!target.closest('.relative')) {
|
|
861
1210
|
this.dropdownOpen.set(false);
|
|
1211
|
+
this.showCustomerDropdown.set(false);
|
|
862
1212
|
}
|
|
863
1213
|
}
|
|
864
1214
|
startDrag(event) {
|
|
@@ -883,11 +1233,11 @@ class AddRouteComponent {
|
|
|
883
1233
|
this.isDragging = false;
|
|
884
1234
|
}
|
|
885
1235
|
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] 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" }] });
|
|
1236
|
+
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\" id=\"add-route-modal\">\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 tabindex=\"-1\"\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 [disabled]=\"(routeId() ? (addRouteForm.invalid || !addRouteForm.dirty) : addRouteForm.invalid) || !pickupSelected() || !deliverySelected() || showLoader()\"\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 @if (showLoader()) {\n <lucide-icon [img]=\"icons.LoaderCircle\" class=\"animate-spin\" [size]=\"18\"></lucide-icon>\n } @else {\n {{ routeId() ? 'Update' : 'Save' }}\n }\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=\"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-300 dark:border-slate-700 bg-gray-100 dark:bg-slate-900 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}); addRouteForm.markAsDirty()\"\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 class=\"location-flow rounded-lg\">\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\"\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\"\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 autocomplete-field\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Pickup Location</label>\n <input\n #filterPickup\n type=\"text\"\n formControlName=\"pickup_location\"\n placeholder=\"Pickup\"\n (input)=\"!isSystemPickup ? onInputChange($event, 'pickup') : null\"\n (focus)=\"onFocus('pickup')\"\n (blur)=\"isSystemPickup ? hideDropdown('pickup') : null\"\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 \n <!-- System Suggestions -->\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 pickup-system-dropdown\">\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 border-b border-gray-50 dark:border-slate-700 last:border-0\">\n {{ loc.formatted_address }}\n </div>\n }\n </div>\n }\n\n <!-- Google Suggestions -->\n @if (!isSystemPickup && showPickupSuggestions() && pickupSuggestions().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 pickup-suggestions\">\n @for (suggestion of pickupSuggestions(); track suggestion.placePrediction?.text?.toString() || $index) {\n <div \n (click)=\"onSuggestionClick(suggestion, 'pickup')\"\n class=\"px-4 py-2.5 hover:bg-amber-50 dark:hover:bg-amber-900/20 cursor-pointer text-sm text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-slate-700 last:border-0 transition-colors\">\n {{ suggestion.placePrediction?.text?.toString() }}\n </div>\n }\n </div>\n }\n @if ((saveAttempted() || addRouteForm.get('pickup_location')?.dirty) && !pickupSelected() && addRouteForm.get('pickup_location')?.value) {\n <div class=\"text-red-500 text-[10px] mt-1 font-medium italic flex items-center gap-1\">\n Please select a valid pickup location from suggestions\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"grid grid-cols-1 mb-[1rem]\">\n <div class=\"location-flow rounded-lg\">\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\">\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 autocomplete-field\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Delivery Location</label>\n <input\n #filterDelivery\n type=\"text\"\n formControlName=\"delivery_location\"\n placeholder=\"Delivery\"\n (input)=\"!isSystemDelivery ? onInputChange($event, 'delivery') : null\"\n (focus)=\"onFocus('delivery')\"\n (blur)=\"isSystemDelivery ? hideDropdown('delivery') : null\"\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\n <!-- System Suggestions -->\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 delivery-system-dropdown\">\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 border-b border-gray-50 dark:border-slate-700 last:border-0\">\n {{ loc.formatted_address }}\n </div>\n }\n </div>\n }\n\n <!-- Google Suggestions -->\n @if (!isSystemDelivery && showDeliverySuggestions() && deliverySuggestions().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 delivery-suggestions\">\n @for (suggestion of deliverySuggestions(); track suggestion.placePrediction?.text?.toString() || $index) {\n <div \n (click)=\"onSuggestionClick(suggestion, 'delivery')\"\n class=\"px-4 py-2.5 hover:bg-amber-50 dark:hover:bg-amber-900/20 cursor-pointer text-sm text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-slate-700 last:border-0 transition-colors\">\n {{ suggestion.placePrediction?.text?.toString() }}\n </div>\n }\n </div>\n }\n @if ((saveAttempted() || addRouteForm.get('delivery_location')?.dirty) && !deliverySelected() && addRouteForm.get('delivery_location')?.value) {\n <div class=\"text-red-500 text-[10px] mt-1 font-medium italic flex items-center gap-1\">\n Please select a valid delivery location from suggestions\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}); addRouteForm.markAsDirty(); 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 @if(addRouteForm.value.estimated_distance){\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\">\n Estimation of miles - <b>{{ addRouteForm.value.estimated_distance }}</b>\n </span>\n }\n\n @if(addRouteForm.value.estimated_time){\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\">\n Estimation of time - <b>{{ addRouteForm.value.estimated_time }}</b>\n </span>\n }\n </div>\n <div class=\"flex flex-col lg:flex-row gap-2 justify-end\">\n @if (config.repository !== 'customer' && addRouteForm.value.trucker_pay_estimate) {\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\">\n Trucker Pay Estimate - <b>{{ addRouteForm.value.trucker_pay_estimate | currency }}</b>\n </span>\n }\n @if(addRouteForm.value.customer_price_estimate){\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\">\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 text-center\">Delete Route?</h3>\n <p class=\"text-gray-600 dark:text-gray-400 text-sm mb-6 text-center\">\n Are you sure you want to delete this route?\n </p>\n <div class=\"flex justify-center 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 </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
1237
|
}
|
|
888
1238
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AddRouteComponent, decorators: [{
|
|
889
1239
|
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] 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"] }]
|
|
1240
|
+
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\" id=\"add-route-modal\">\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 tabindex=\"-1\"\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 [disabled]=\"(routeId() ? (addRouteForm.invalid || !addRouteForm.dirty) : addRouteForm.invalid) || !pickupSelected() || !deliverySelected() || showLoader()\"\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 @if (showLoader()) {\n <lucide-icon [img]=\"icons.LoaderCircle\" class=\"animate-spin\" [size]=\"18\"></lucide-icon>\n } @else {\n {{ routeId() ? 'Update' : 'Save' }}\n }\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=\"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-300 dark:border-slate-700 bg-gray-100 dark:bg-slate-900 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}); addRouteForm.markAsDirty()\"\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 class=\"location-flow rounded-lg\">\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\"\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\"\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 autocomplete-field\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Pickup Location</label>\n <input\n #filterPickup\n type=\"text\"\n formControlName=\"pickup_location\"\n placeholder=\"Pickup\"\n (input)=\"!isSystemPickup ? onInputChange($event, 'pickup') : null\"\n (focus)=\"onFocus('pickup')\"\n (blur)=\"isSystemPickup ? hideDropdown('pickup') : null\"\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 \n <!-- System Suggestions -->\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 pickup-system-dropdown\">\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 border-b border-gray-50 dark:border-slate-700 last:border-0\">\n {{ loc.formatted_address }}\n </div>\n }\n </div>\n }\n\n <!-- Google Suggestions -->\n @if (!isSystemPickup && showPickupSuggestions() && pickupSuggestions().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 pickup-suggestions\">\n @for (suggestion of pickupSuggestions(); track suggestion.placePrediction?.text?.toString() || $index) {\n <div \n (click)=\"onSuggestionClick(suggestion, 'pickup')\"\n class=\"px-4 py-2.5 hover:bg-amber-50 dark:hover:bg-amber-900/20 cursor-pointer text-sm text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-slate-700 last:border-0 transition-colors\">\n {{ suggestion.placePrediction?.text?.toString() }}\n </div>\n }\n </div>\n }\n @if ((saveAttempted() || addRouteForm.get('pickup_location')?.dirty) && !pickupSelected() && addRouteForm.get('pickup_location')?.value) {\n <div class=\"text-red-500 text-[10px] mt-1 font-medium italic flex items-center gap-1\">\n Please select a valid pickup location from suggestions\n </div>\n }\n </div>\n </div>\n </div>\n <div class=\"grid grid-cols-1 mb-[1rem]\">\n <div class=\"location-flow rounded-lg\">\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\">\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 autocomplete-field\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Delivery Location</label>\n <input\n #filterDelivery\n type=\"text\"\n formControlName=\"delivery_location\"\n placeholder=\"Delivery\"\n (input)=\"!isSystemDelivery ? onInputChange($event, 'delivery') : null\"\n (focus)=\"onFocus('delivery')\"\n (blur)=\"isSystemDelivery ? hideDropdown('delivery') : null\"\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\n <!-- System Suggestions -->\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 delivery-system-dropdown\">\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 border-b border-gray-50 dark:border-slate-700 last:border-0\">\n {{ loc.formatted_address }}\n </div>\n }\n </div>\n }\n\n <!-- Google Suggestions -->\n @if (!isSystemDelivery && showDeliverySuggestions() && deliverySuggestions().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 delivery-suggestions\">\n @for (suggestion of deliverySuggestions(); track suggestion.placePrediction?.text?.toString() || $index) {\n <div \n (click)=\"onSuggestionClick(suggestion, 'delivery')\"\n class=\"px-4 py-2.5 hover:bg-amber-50 dark:hover:bg-amber-900/20 cursor-pointer text-sm text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-slate-700 last:border-0 transition-colors\">\n {{ suggestion.placePrediction?.text?.toString() }}\n </div>\n }\n </div>\n }\n @if ((saveAttempted() || addRouteForm.get('delivery_location')?.dirty) && !deliverySelected() && addRouteForm.get('delivery_location')?.value) {\n <div class=\"text-red-500 text-[10px] mt-1 font-medium italic flex items-center gap-1\">\n Please select a valid delivery location from suggestions\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}); addRouteForm.markAsDirty(); 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 @if(addRouteForm.value.estimated_distance){\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\">\n Estimation of miles - <b>{{ addRouteForm.value.estimated_distance }}</b>\n </span>\n }\n\n @if(addRouteForm.value.estimated_time){\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\">\n Estimation of time - <b>{{ addRouteForm.value.estimated_time }}</b>\n </span>\n }\n </div>\n <div class=\"flex flex-col lg:flex-row gap-2 justify-end\">\n @if (config.repository !== 'customer' && addRouteForm.value.trucker_pay_estimate) {\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\">\n Trucker Pay Estimate - <b>{{ addRouteForm.value.trucker_pay_estimate | currency }}</b>\n </span>\n }\n @if(addRouteForm.value.customer_price_estimate){\n <span\n class=\"text-[10px] text-gray-500 dark:text-slate-400 mb-1 whitespace-nowrap italic\">\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 text-center\">Delete Route?</h3>\n <p class=\"text-gray-600 dark:text-gray-400 text-sm mb-6 text-center\">\n Are you sure you want to delete this route?\n </p>\n <div class=\"flex justify-center 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 </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
1241
|
}], 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
1242
|
type: Output
|
|
893
1243
|
}], routeDeleted: [{
|
|
@@ -905,18 +1255,403 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
905
1255
|
args: ['document:mouseup']
|
|
906
1256
|
}] } });
|
|
907
1257
|
|
|
1258
|
+
class ShareRouteComponent {
|
|
1259
|
+
config = inject(COOLMAP_CONFIG);
|
|
1260
|
+
utils = inject(UtilsService);
|
|
1261
|
+
openShareRouteDetails = input(null, ...(ngDevMode ? [{ debugName: "openShareRouteDetails" }] : []));
|
|
1262
|
+
userDetails = input(null, ...(ngDevMode ? [{ debugName: "userDetails" }] : []));
|
|
1263
|
+
closePopupEmit = new EventEmitter();
|
|
1264
|
+
shareRouteForm;
|
|
1265
|
+
pickupDeliveryLatLongObject = signal({ pickup_lat_lng: '', delivery_lat_lng: '' }, ...(ngDevMode ? [{ debugName: "pickupDeliveryLatLongObject" }] : []));
|
|
1266
|
+
unitsList = signal([], ...(ngDevMode ? [{ debugName: "unitsList" }] : []));
|
|
1267
|
+
showLoader = signal(false, ...(ngDevMode ? [{ debugName: "showLoader" }] : []));
|
|
1268
|
+
dragX = signal(0, ...(ngDevMode ? [{ debugName: "dragX" }] : []));
|
|
1269
|
+
dragY = signal(0, ...(ngDevMode ? [{ debugName: "dragY" }] : []));
|
|
1270
|
+
isDragging = false;
|
|
1271
|
+
dragStartX = 0;
|
|
1272
|
+
dragStartY = 0;
|
|
1273
|
+
dragInitialX = 0;
|
|
1274
|
+
dragInitialY = 0;
|
|
1275
|
+
filterPickup;
|
|
1276
|
+
filterDelivery;
|
|
1277
|
+
pickupSuggestions = signal([], ...(ngDevMode ? [{ debugName: "pickupSuggestions" }] : []));
|
|
1278
|
+
deliverySuggestions = signal([], ...(ngDevMode ? [{ debugName: "deliverySuggestions" }] : []));
|
|
1279
|
+
showPickupSuggestions = signal(false, ...(ngDevMode ? [{ debugName: "showPickupSuggestions" }] : []));
|
|
1280
|
+
showDeliverySuggestions = signal(false, ...(ngDevMode ? [{ debugName: "showDeliverySuggestions" }] : []));
|
|
1281
|
+
pickupSelected = signal(false, ...(ngDevMode ? [{ debugName: "pickupSelected" }] : []));
|
|
1282
|
+
deliverySelected = signal(false, ...(ngDevMode ? [{ debugName: "deliverySelected" }] : []));
|
|
1283
|
+
saveAttempted = signal(false, ...(ngDevMode ? [{ debugName: "saveAttempted" }] : []));
|
|
1284
|
+
placesLibrary = null;
|
|
1285
|
+
sessionToken = null;
|
|
1286
|
+
debounceTimer = null;
|
|
1287
|
+
dateValue = signal('', ...(ngDevMode ? [{ debugName: "dateValue" }] : []));
|
|
1288
|
+
displayDate = computed(() => {
|
|
1289
|
+
const val = this.dateValue();
|
|
1290
|
+
if (!val)
|
|
1291
|
+
return '';
|
|
1292
|
+
const parts = val.split('-');
|
|
1293
|
+
if (parts.length !== 3)
|
|
1294
|
+
return val;
|
|
1295
|
+
const [y, m, d] = parts;
|
|
1296
|
+
return `${m}/${d}/${y}`;
|
|
1297
|
+
}, ...(ngDevMode ? [{ debugName: "displayDate" }] : []));
|
|
1298
|
+
icons = {
|
|
1299
|
+
X,
|
|
1300
|
+
Share2,
|
|
1301
|
+
Calendar,
|
|
1302
|
+
MapPin,
|
|
1303
|
+
Hash,
|
|
1304
|
+
User,
|
|
1305
|
+
Phone,
|
|
1306
|
+
Mail,
|
|
1307
|
+
DollarSign,
|
|
1308
|
+
Info,
|
|
1309
|
+
LoaderCircle,
|
|
1310
|
+
Check
|
|
1311
|
+
};
|
|
1312
|
+
constructor() {
|
|
1313
|
+
this.shareRouteForm = new FormGroup({
|
|
1314
|
+
uuid: new FormControl('', [Validators.required]),
|
|
1315
|
+
routeName: new FormControl('', [Validators.required]),
|
|
1316
|
+
customerName: new FormControl('', [Validators.required]),
|
|
1317
|
+
customerId: new FormControl('', [Validators.required]),
|
|
1318
|
+
special_price: new FormControl(false),
|
|
1319
|
+
pickupLocation: new FormControl('', [Validators.required]),
|
|
1320
|
+
deliveryLocation: new FormControl('', [Validators.required]),
|
|
1321
|
+
routeNotes: new FormControl('', [Validators.maxLength(2048)]),
|
|
1322
|
+
distance: new FormControl('', [Validators.required]),
|
|
1323
|
+
time: new FormControl('', [Validators.required]),
|
|
1324
|
+
date: new FormControl(''),
|
|
1325
|
+
quantity: new FormControl(''),
|
|
1326
|
+
units: new FormControl('', [Validators.required]),
|
|
1327
|
+
contactName: new FormControl('', [Validators.required]),
|
|
1328
|
+
contactPhone: new FormControl(null, [Validators.required]),
|
|
1329
|
+
contactEmail: new FormControl(null, [Validators.required]),
|
|
1330
|
+
price: new FormControl(0, [Validators.required]),
|
|
1331
|
+
customerEstimatedPrice: new FormControl(0),
|
|
1332
|
+
});
|
|
1333
|
+
// Handle input changes via effect
|
|
1334
|
+
effect(() => {
|
|
1335
|
+
const details = this.openShareRouteDetails();
|
|
1336
|
+
if (details) {
|
|
1337
|
+
this.patchData(details);
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
effect(() => {
|
|
1341
|
+
const user = this.userDetails();
|
|
1342
|
+
if (user) {
|
|
1343
|
+
this.patchUserData(user);
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
this.utils.fetchUnitsList().then((res) => {
|
|
1347
|
+
this.unitsList.set(res || []);
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
ngAfterViewInit() {
|
|
1351
|
+
this.initializePlacesLibrary();
|
|
1352
|
+
}
|
|
1353
|
+
async initializePlacesLibrary() {
|
|
1354
|
+
try {
|
|
1355
|
+
// Assuming google script is already loaded by the app
|
|
1356
|
+
this.placesLibrary = await window.google?.maps?.importLibrary('places');
|
|
1357
|
+
if (this.placesLibrary) {
|
|
1358
|
+
this.sessionToken = new this.placesLibrary.AutocompleteSessionToken();
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
catch (error) {
|
|
1362
|
+
console.error('Error loading Google Maps Places library:', error);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
onInputChange(event, type) {
|
|
1366
|
+
const value = event.target.value;
|
|
1367
|
+
const formKey = type === 'pickup' ? 'pickupLocation' : 'deliveryLocation';
|
|
1368
|
+
this.shareRouteForm.patchValue({ [formKey]: value });
|
|
1369
|
+
if (type === 'pickup')
|
|
1370
|
+
this.pickupSelected.set(false);
|
|
1371
|
+
else
|
|
1372
|
+
this.deliverySelected.set(false);
|
|
1373
|
+
clearTimeout(this.debounceTimer);
|
|
1374
|
+
this.debounceTimer = setTimeout(() => {
|
|
1375
|
+
if (value.trim()) {
|
|
1376
|
+
this.fetchAutocompleteSuggestions(value, type);
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
this.hideSuggestions(type);
|
|
1380
|
+
}
|
|
1381
|
+
}, 300);
|
|
1382
|
+
}
|
|
1383
|
+
onFocus(type) {
|
|
1384
|
+
const value = type === 'pickup' ?
|
|
1385
|
+
this.shareRouteForm.get('pickupLocation')?.value :
|
|
1386
|
+
this.shareRouteForm.get('deliveryLocation')?.value;
|
|
1387
|
+
if (value && value.trim()) {
|
|
1388
|
+
this.fetchAutocompleteSuggestions(value, type);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
onDocumentClick(event) {
|
|
1392
|
+
const target = event.target;
|
|
1393
|
+
const isPickupArea = target === this.filterPickup.nativeElement || target.closest('.pickup-suggestions');
|
|
1394
|
+
const isDeliveryArea = target === this.filterDelivery.nativeElement || target.closest('.delivery-suggestions');
|
|
1395
|
+
if (!isPickupArea) {
|
|
1396
|
+
this.showPickupSuggestions.set(false);
|
|
1397
|
+
}
|
|
1398
|
+
if (!isDeliveryArea) {
|
|
1399
|
+
this.showDeliverySuggestions.set(false);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
async fetchAutocompleteSuggestions(input, type) {
|
|
1403
|
+
if (!this.placesLibrary || !input.trim() || !this.sessionToken)
|
|
1404
|
+
return;
|
|
1405
|
+
try {
|
|
1406
|
+
const request = {
|
|
1407
|
+
input: input.trim(),
|
|
1408
|
+
sessionToken: this.sessionToken,
|
|
1409
|
+
includedRegionCodes: ['US']
|
|
1410
|
+
};
|
|
1411
|
+
const { suggestions } = await this.placesLibrary.AutocompleteSuggestion.fetchAutocompleteSuggestions(request);
|
|
1412
|
+
if (type === 'pickup') {
|
|
1413
|
+
this.pickupSuggestions.set(suggestions || []);
|
|
1414
|
+
this.showPickupSuggestions.set(this.pickupSuggestions().length > 0);
|
|
1415
|
+
}
|
|
1416
|
+
else {
|
|
1417
|
+
this.deliverySuggestions.set(suggestions || []);
|
|
1418
|
+
this.showDeliverySuggestions.set(this.deliverySuggestions().length > 0);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
catch (error) {
|
|
1422
|
+
console.error('Autocomplete error:', error);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
async onSuggestionClick(suggestion, type) {
|
|
1426
|
+
if (!suggestion.placePrediction)
|
|
1427
|
+
return;
|
|
1428
|
+
try {
|
|
1429
|
+
const place = suggestion.placePrediction.toPlace();
|
|
1430
|
+
await place.fetchFields({ fields: ['displayName', 'formattedAddress', 'location'] });
|
|
1431
|
+
const placeData = place.toJSON();
|
|
1432
|
+
const res = {
|
|
1433
|
+
lat: placeData.location.lat,
|
|
1434
|
+
lng: placeData.location.lng,
|
|
1435
|
+
formatted_address: placeData.formattedAddress || placeData.displayName
|
|
1436
|
+
};
|
|
1437
|
+
this.patchAddressValue(res, type);
|
|
1438
|
+
if (type === 'pickup')
|
|
1439
|
+
this.pickupSelected.set(true);
|
|
1440
|
+
else
|
|
1441
|
+
this.deliverySelected.set(true);
|
|
1442
|
+
this.hideSuggestions(type);
|
|
1443
|
+
// Refresh session token for next search
|
|
1444
|
+
if (this.placesLibrary) {
|
|
1445
|
+
this.sessionToken = new this.placesLibrary.AutocompleteSessionToken();
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
catch (error) {
|
|
1449
|
+
console.error('Error selecting suggestion:', error);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
hideSuggestions(type) {
|
|
1453
|
+
if (type === 'pickup') {
|
|
1454
|
+
this.showPickupSuggestions.set(false);
|
|
1455
|
+
this.pickupSuggestions.set([]);
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
this.showDeliverySuggestions.set(false);
|
|
1459
|
+
this.deliverySuggestions.set([]);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
patchAddressValue(data, type) {
|
|
1463
|
+
this.shareRouteForm.patchValue({
|
|
1464
|
+
[type + 'Location']: data.formatted_address
|
|
1465
|
+
});
|
|
1466
|
+
this.pickupDeliveryLatLongObject.update(prev => ({
|
|
1467
|
+
...prev,
|
|
1468
|
+
[type + '_lat_lng']: `${data.lat},${data.lng}`
|
|
1469
|
+
}));
|
|
1470
|
+
this.checkAndFetchRouteInformation();
|
|
1471
|
+
}
|
|
1472
|
+
patchData(details) {
|
|
1473
|
+
this.shareRouteForm.patchValue({
|
|
1474
|
+
uuid: details.route_id,
|
|
1475
|
+
routeName: details.route_name,
|
|
1476
|
+
customerName: details.customer_name,
|
|
1477
|
+
pickupLocation: details.pickup_location,
|
|
1478
|
+
deliveryLocation: details.delivery_location,
|
|
1479
|
+
routeNotes: details.notes || null,
|
|
1480
|
+
distance: details.estimated_distance?.split(' ')[0],
|
|
1481
|
+
time: details.estimated_time?.split(' ')[0],
|
|
1482
|
+
units: details.unit,
|
|
1483
|
+
price: details.customer_price_estimate,
|
|
1484
|
+
customerEstimatedPrice: details.customer_price_estimate
|
|
1485
|
+
});
|
|
1486
|
+
this.pickupDeliveryLatLongObject.set({
|
|
1487
|
+
pickup_lat_lng: details.pickup_lat_lng,
|
|
1488
|
+
delivery_lat_lng: details.delivery_lat_lng
|
|
1489
|
+
});
|
|
1490
|
+
if (details.date) {
|
|
1491
|
+
this.dateValue.set(details.date);
|
|
1492
|
+
}
|
|
1493
|
+
this.pickupSelected.set(!!details.pickup_location);
|
|
1494
|
+
this.deliverySelected.set(!!details.delivery_location);
|
|
1495
|
+
this.saveAttempted.set(false);
|
|
1496
|
+
}
|
|
1497
|
+
patchUserData(user) {
|
|
1498
|
+
this.shareRouteForm.patchValue({
|
|
1499
|
+
contactName: user.firstname + ' ' + user.lastname,
|
|
1500
|
+
contactPhone: user.phone,
|
|
1501
|
+
contactEmail: user.email,
|
|
1502
|
+
customerId: user.customerId
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
checkAndFetchRouteInformation() {
|
|
1506
|
+
const latLongs = this.pickupDeliveryLatLongObject();
|
|
1507
|
+
if (latLongs.delivery_lat_lng?.length > 0 && latLongs.pickup_lat_lng?.length > 0) {
|
|
1508
|
+
this.showLoader.set(true);
|
|
1509
|
+
const param = {
|
|
1510
|
+
delivery_lat_lng: latLongs.delivery_lat_lng,
|
|
1511
|
+
pickup_lat_lng: latLongs.pickup_lat_lng,
|
|
1512
|
+
unit: this.shareRouteForm.value?.units
|
|
1513
|
+
};
|
|
1514
|
+
this.utils.postdata('calculate/routes/estimation', param).subscribe({
|
|
1515
|
+
next: (res) => {
|
|
1516
|
+
if (res && res['path']) {
|
|
1517
|
+
this.shareRouteForm.patchValue({
|
|
1518
|
+
distance: res['dist']?.split(' ')[0],
|
|
1519
|
+
time: res['time']?.split(' ')[0],
|
|
1520
|
+
price: res['customer_price_estimate'],
|
|
1521
|
+
customerEstimatedPrice: res['customer_price_estimate']
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
else {
|
|
1525
|
+
this.utils.openSnackBar(res['status'] || 'Estimation failed', 'error');
|
|
1526
|
+
}
|
|
1527
|
+
this.showLoader.set(false);
|
|
1528
|
+
},
|
|
1529
|
+
error: () => {
|
|
1530
|
+
this.showLoader.set(false);
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
onDateChange(event) {
|
|
1536
|
+
const target = event.target;
|
|
1537
|
+
if (target && target.value) {
|
|
1538
|
+
this.dateValue.set(target.value);
|
|
1539
|
+
this.shareRouteForm.patchValue({ date: target.value });
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
saveShareRoute() {
|
|
1543
|
+
this.saveAttempted.set(true);
|
|
1544
|
+
if (!this.pickupSelected()) {
|
|
1545
|
+
this.utils.openSnackBar('Please select a valid pickup location from suggestions', 'error');
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
if (!this.deliverySelected()) {
|
|
1549
|
+
this.utils.openSnackBar('Please select a valid delivery location from suggestions', 'error');
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
if (this.shareRouteForm.valid) {
|
|
1553
|
+
this.showLoader.set(true);
|
|
1554
|
+
const formData = { ...this.shareRouteForm.value };
|
|
1555
|
+
if (formData.date) {
|
|
1556
|
+
const d = new Date(formData.date);
|
|
1557
|
+
formData.date = `${(d.getMonth() + 1).toString().padStart(2, '0')}/${d.getDate().toString().padStart(2, '0')}/${d.getFullYear()}`;
|
|
1558
|
+
}
|
|
1559
|
+
else {
|
|
1560
|
+
formData.date = 'N/A';
|
|
1561
|
+
}
|
|
1562
|
+
formData.quantity = formData.quantity || 0;
|
|
1563
|
+
this.utils.postdata('share/routes', formData).subscribe({
|
|
1564
|
+
next: (res) => {
|
|
1565
|
+
if (res.data?.success) {
|
|
1566
|
+
this.utils.openSnackBar(res.data.message, 'success');
|
|
1567
|
+
this.closePopup();
|
|
1568
|
+
}
|
|
1569
|
+
else {
|
|
1570
|
+
this.utils.openSnackBar(res.data?.message || 'Failed to share route', 'error');
|
|
1571
|
+
}
|
|
1572
|
+
this.showLoader.set(false);
|
|
1573
|
+
},
|
|
1574
|
+
error: () => {
|
|
1575
|
+
this.showLoader.set(false);
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
this.shareRouteForm.markAllAsTouched();
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
setValidators(checked) {
|
|
1584
|
+
const priceControl = this.shareRouteForm.get('customerEstimatedPrice');
|
|
1585
|
+
if (checked) {
|
|
1586
|
+
priceControl?.setValidators(Validators.required);
|
|
1587
|
+
}
|
|
1588
|
+
else {
|
|
1589
|
+
priceControl?.clearValidators();
|
|
1590
|
+
}
|
|
1591
|
+
priceControl?.updateValueAndValidity();
|
|
1592
|
+
}
|
|
1593
|
+
closePopup() {
|
|
1594
|
+
this.closePopupEmit.emit();
|
|
1595
|
+
}
|
|
1596
|
+
startDrag(event) {
|
|
1597
|
+
this.isDragging = true;
|
|
1598
|
+
this.dragStartX = event.clientX;
|
|
1599
|
+
this.dragStartY = event.clientY;
|
|
1600
|
+
this.dragInitialX = this.dragX();
|
|
1601
|
+
this.dragInitialY = this.dragY();
|
|
1602
|
+
event.preventDefault();
|
|
1603
|
+
}
|
|
1604
|
+
onMouseMove(event) {
|
|
1605
|
+
if (!this.isDragging)
|
|
1606
|
+
return;
|
|
1607
|
+
this.dragX.set(this.dragInitialX + (event.clientX - this.dragStartX));
|
|
1608
|
+
this.dragY.set(this.dragInitialY + (event.clientY - this.dragStartY));
|
|
1609
|
+
}
|
|
1610
|
+
onMouseUp() {
|
|
1611
|
+
this.isDragging = false;
|
|
1612
|
+
}
|
|
1613
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ShareRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1614
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: ShareRouteComponent, isStandalone: true, selector: "lib-share-route", inputs: { openShareRouteDetails: { classPropertyName: "openShareRouteDetails", publicName: "openShareRouteDetails", isSignal: true, isRequired: false, transformFunction: null }, userDetails: { classPropertyName: "userDetails", publicName: "userDetails", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closePopupEmit: "closePopupEmit" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp()" } }, viewQueries: [{ propertyName: "filterPickup", first: true, predicate: ["filterPickup"], descendants: true }, { propertyName: "filterDelivery", first: true, predicate: ["filterDelivery"], descendants: true }], ngImport: i0, template: "<div\n class=\"absolute inset-0 z-[120] flex items-center justify-center p-4 w-[800px] bottom-[50%] left-[50%]\"\n style=\"transform: translate(-50%)\"\n>\n <div class=\"flex lg:items-start lg:justify-start items-center justify-center p-3 h-[1200px]\">\n <form\n [formGroup]=\"shareRouteForm\"\n (ngSubmit)=\"saveShareRoute()\"\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl w-[800px] pointer-events-auto\"\n [style.transform]=\"'translate(' + dragX() + 'px, ' + dragY() + 'px)'\"\n [style.will-change]=\"'transform'\"\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 Share Route\n </h3>\n <div class=\"flex items-center gap-4\">\n <button\n type=\"submit\"\n [disabled]=\"shareRouteForm.invalid || showLoader()\"\n class=\"flex items-center gap-2 px-8 py-2.5 rounded-lg bg-amber-500 hover:bg-amber-600 disabled:opacity-50 disabled:cursor-not-allowed text-white font-bold text-sm shadow-lg shadow-amber-500/20 transition-all active:scale-95\"\n >\n @if (showLoader()) {\n <lucide-icon [img]=\"icons.LoaderCircle\" class=\"animate-spin\" [size]=\"18\"></lucide-icon> } @else { Send }\n </button>\n <button\n type=\"button\"\n class=\"inline-flex items-center gap-2 px-4 py-2.5 bg-gray-600 text-white hover:bg-gray-700 font-medium rounded-lg transition-colors text-sm\"\n (click)=\"closePopup()\"\n >\n Close\n </button>\n </div>\n </div>\n\n <div class=\"p-4 overflow-y-auto max-h-[60vh]\">\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <!-- Route Name -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Route Name\n </label>\n <input\n formControlName=\"routeName\"\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 placeholder=\"Enter route name\"\n />\n </div>\n\n <!-- Unit Type -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Unit Type\n </label>\n <select\n formControlName=\"units\"\n (change)=\"checkAndFetchRouteInformation()\"\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 <option value=\"\" disabled>Select Unit</option>\n @for (unit of unitsList(); track unit.type) {\n <option [value]=\"unit.type\">{{ unit.type }}</option>\n }\n </select>\n </div>\n\n <!-- Pickup Location -->\n <div class=\"relative autocomplete-field\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Pickup\n </label>\n <input\n formControlName=\"pickupLocation\"\n #filterPickup\n (input)=\"onInputChange($event, '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\"\n placeholder=\"Search pickup location...\"\n autocomplete=\"off\"\n />\n @if (showPickupSuggestions() && pickupSuggestions().length > 0) {\n <div\n class=\"absolute z-[110] w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-xl max-h-48 overflow-y-auto\"\n >\n @for (suggestion of pickupSuggestions(); track\n suggestion.placePrediction?.text?.toString() || $index) {\n <div\n class=\"px-4 py-2.5 hover:bg-amber-50 dark:hover:bg-amber-900/20 cursor-pointer text-sm text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-slate-700 last:border-0 transition-colors\"\n (click)=\"onSuggestionClick(suggestion, 'pickup')\"\n >\n {{ suggestion.placePrediction?.text?.toString() }}\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Delivery Location -->\n <div class=\"relative autocomplete-field\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Delivery\n </label>\n <input\n formControlName=\"deliveryLocation\"\n #filterDelivery\n (input)=\"onInputChange($event, '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\"\n placeholder=\"Search delivery location...\"\n autocomplete=\"off\"\n />\n @if (showDeliverySuggestions() && deliverySuggestions().length > 0) {\n <div\n class=\"absolute z-[110] w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-xl max-h-48 overflow-y-auto\"\n >\n @for (suggestion of deliverySuggestions(); track\n suggestion.placePrediction?.text?.toString() || $index) {\n <div\n class=\"px-4 py-2.5 hover:bg-amber-50 dark:hover:bg-amber-900/20 cursor-pointer text-sm text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-slate-700 last:border-0 transition-colors\"\n (click)=\"onSuggestionClick(suggestion, 'delivery')\"\n >\n {{ suggestion.placePrediction?.text?.toString() }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n <div class=\"grid grid-cols-1 md:grid-cols-1 gap-4 mb-[1rem] mt-[1rem]\">\n <!-- Notes -->\n <div>\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=\"routeNotes\"\n rows=\"2\"\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 resize-none\"\n placeholder=\"List Material and Details\"\n ></textarea>\n </div>\n </div>\n\n <div class=\"grid grid-cols-2 gap-4 mb-[1rem]\">\n <!-- Date Requested -->\n <div class=\"relative\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Date Requested\n </label>\n <div class=\"relative group\">\n <input\n type=\"text\"\n readonly\n [value]=\"displayDate()\"\n (click)=\"dateInput.showPicker()\"\n placeholder=\"MM/DD/YYYY\"\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 <input\n #dateInput\n type=\"date\"\n [value]=\"dateValue()\"\n (change)=\"onDateChange($event)\"\n class=\"absolute inset-0 w-0 h-0 opacity-0 pointer-events-none\"\n />\n </div>\n </div>\n\n <!-- Quantity Requested -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Quantity Requested\n </label>\n <input\n type=\"number\"\n formControlName=\"quantity\"\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 placeholder=\"0\"\n />\n </div>\n\n <!-- Price -->\n <div class=\"relative -top-[20px]\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Customer Haul Price\n </label>\n <div class=\"relative\">\n <input\n formControlName=\"price\"\n readonly\n class=\"w-full px-3 py-3 pl-[47px] 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 <lucide-icon\n [img]=\"icons.DollarSign\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 text-gray-400\"\n [size]=\"16\"\n ></lucide-icon>\n </div>\n </div>\n\n <!-- Unit Type Display -->\n <div class=\"relative -top-[20px]\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Unit type\n </label>\n <div\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 {{ shareRouteForm.value.units || 'N/A' }}\n </div>\n </div>\n\n <!-- Contact Info Section Title -->\n <div class=\"md:col-span-2 border-t border-gray-100 dark:border-slate-800\">\n <h3 class=\"flex items-center gap-1 text-md font-semibold text-gray-900 dark:text-white\">\n Customer Information\n </h3>\n </div>\n\n <!-- Contact Name -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Contact Name\n </label>\n <input\n formControlName=\"contactName\"\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\n <!-- Contact Phone -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Contact Phone\n </label>\n <input\n formControlName=\"contactPhone\"\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\n <!-- Contact Email -->\n <div class=\"space-y-1.5 md:col-span-2\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Contact Email\n </label>\n <input\n formControlName=\"contactEmail\"\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\n <!-- Special Pricing Checkbox -->\n <div class=\"md:col-span-2 pt-2 flex items-center gap-3\">\n <label class=\"relative flex h-5 w-5 cursor-pointer\">\n <input\n type=\"checkbox\"\n formControlName=\"special_price\"\n (change)=\"setValidators(shareRouteForm.value.special_price)\"\n class=\"h-full w-full appearance-none rounded-md border border-gray-300 dark:border-slate-700 transition-all checked:bg-amber-500 checked:border-amber-500 outline-none cursor-pointer\"\n />\n <lucide-icon\n [img]=\"icons.Check\"\n [size]=\"14\"\n [class.opacity-100]=\"shareRouteForm.get('special_price')?.value\"\n [class.opacity-0]=\"!shareRouteForm.get('special_price')?.value\"\n class=\"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-white transition-opacity pointer-events-none\"\n ></lucide-icon>\n </label>\n <span class=\"text-sm font-medium text-gray-700 dark:text-gray-300\"\n >Request special pricing</span\n >\n </div>\n\n <!-- Special Price Input (Conditional) -->\n @if (shareRouteForm.value.special_price) {\n <div class=\"space-y-1.5 animate-fade-in md:col-span-1\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Requested Price\n </label>\n <div class=\"relative\">\n <input\n formControlName=\"customerEstimatedPrice\"\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 <lucide-icon\n [img]=\"icons.DollarSign\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 text-amber-500\"\n [size]=\"16\"\n ></lucide-icon>\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n </div>\n</div>\n", styles: ["@keyframes slide-up{0%{opacity:0;transform:translateY(20px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.animate-slide-up{animation:slide-up .3s cubic-bezier(.16,1,.3,1) forwards}.animate-fade-in{animation:fade-in .2s ease-out forwards}:host{display:block}input[type=date]::-webkit-calendar-picker-indicator{filter:invert(.5);cursor:pointer}.dark input[type=date]::-webkit-calendar-picker-indicator{filter:invert(1)}form::-webkit-scrollbar{width:6px}form::-webkit-scrollbar-track{background:transparent}form::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:10px}.dark form::-webkit-scrollbar-thumb{background:#334155}\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.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { 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"] }] });
|
|
1615
|
+
}
|
|
1616
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ShareRouteComponent, decorators: [{
|
|
1617
|
+
type: Component,
|
|
1618
|
+
args: [{ selector: 'lib-share-route', standalone: true, imports: [LucideAngularModule, ReactiveFormsModule, NgClass, CurrencyPipe], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<div\n class=\"absolute inset-0 z-[120] flex items-center justify-center p-4 w-[800px] bottom-[50%] left-[50%]\"\n style=\"transform: translate(-50%)\"\n>\n <div class=\"flex lg:items-start lg:justify-start items-center justify-center p-3 h-[1200px]\">\n <form\n [formGroup]=\"shareRouteForm\"\n (ngSubmit)=\"saveShareRoute()\"\n class=\"relative bg-white dark:bg-slate-800 rounded-lg shadow-xl w-[800px] pointer-events-auto\"\n [style.transform]=\"'translate(' + dragX() + 'px, ' + dragY() + 'px)'\"\n [style.will-change]=\"'transform'\"\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 Share Route\n </h3>\n <div class=\"flex items-center gap-4\">\n <button\n type=\"submit\"\n [disabled]=\"shareRouteForm.invalid || showLoader()\"\n class=\"flex items-center gap-2 px-8 py-2.5 rounded-lg bg-amber-500 hover:bg-amber-600 disabled:opacity-50 disabled:cursor-not-allowed text-white font-bold text-sm shadow-lg shadow-amber-500/20 transition-all active:scale-95\"\n >\n @if (showLoader()) {\n <lucide-icon [img]=\"icons.LoaderCircle\" class=\"animate-spin\" [size]=\"18\"></lucide-icon> } @else { Send }\n </button>\n <button\n type=\"button\"\n class=\"inline-flex items-center gap-2 px-4 py-2.5 bg-gray-600 text-white hover:bg-gray-700 font-medium rounded-lg transition-colors text-sm\"\n (click)=\"closePopup()\"\n >\n Close\n </button>\n </div>\n </div>\n\n <div class=\"p-4 overflow-y-auto max-h-[60vh]\">\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <!-- Route Name -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Route Name\n </label>\n <input\n formControlName=\"routeName\"\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 placeholder=\"Enter route name\"\n />\n </div>\n\n <!-- Unit Type -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Unit Type\n </label>\n <select\n formControlName=\"units\"\n (change)=\"checkAndFetchRouteInformation()\"\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 <option value=\"\" disabled>Select Unit</option>\n @for (unit of unitsList(); track unit.type) {\n <option [value]=\"unit.type\">{{ unit.type }}</option>\n }\n </select>\n </div>\n\n <!-- Pickup Location -->\n <div class=\"relative autocomplete-field\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Pickup\n </label>\n <input\n formControlName=\"pickupLocation\"\n #filterPickup\n (input)=\"onInputChange($event, '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\"\n placeholder=\"Search pickup location...\"\n autocomplete=\"off\"\n />\n @if (showPickupSuggestions() && pickupSuggestions().length > 0) {\n <div\n class=\"absolute z-[110] w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-xl max-h-48 overflow-y-auto\"\n >\n @for (suggestion of pickupSuggestions(); track\n suggestion.placePrediction?.text?.toString() || $index) {\n <div\n class=\"px-4 py-2.5 hover:bg-amber-50 dark:hover:bg-amber-900/20 cursor-pointer text-sm text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-slate-700 last:border-0 transition-colors\"\n (click)=\"onSuggestionClick(suggestion, 'pickup')\"\n >\n {{ suggestion.placePrediction?.text?.toString() }}\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Delivery Location -->\n <div class=\"relative autocomplete-field\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Delivery\n </label>\n <input\n formControlName=\"deliveryLocation\"\n #filterDelivery\n (input)=\"onInputChange($event, '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\"\n placeholder=\"Search delivery location...\"\n autocomplete=\"off\"\n />\n @if (showDeliverySuggestions() && deliverySuggestions().length > 0) {\n <div\n class=\"absolute z-[110] w-full mt-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg shadow-xl max-h-48 overflow-y-auto\"\n >\n @for (suggestion of deliverySuggestions(); track\n suggestion.placePrediction?.text?.toString() || $index) {\n <div\n class=\"px-4 py-2.5 hover:bg-amber-50 dark:hover:bg-amber-900/20 cursor-pointer text-sm text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-slate-700 last:border-0 transition-colors\"\n (click)=\"onSuggestionClick(suggestion, 'delivery')\"\n >\n {{ suggestion.placePrediction?.text?.toString() }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n <div class=\"grid grid-cols-1 md:grid-cols-1 gap-4 mb-[1rem] mt-[1rem]\">\n <!-- Notes -->\n <div>\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=\"routeNotes\"\n rows=\"2\"\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 resize-none\"\n placeholder=\"List Material and Details\"\n ></textarea>\n </div>\n </div>\n\n <div class=\"grid grid-cols-2 gap-4 mb-[1rem]\">\n <!-- Date Requested -->\n <div class=\"relative\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Date Requested\n </label>\n <div class=\"relative group\">\n <input\n type=\"text\"\n readonly\n [value]=\"displayDate()\"\n (click)=\"dateInput.showPicker()\"\n placeholder=\"MM/DD/YYYY\"\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 <input\n #dateInput\n type=\"date\"\n [value]=\"dateValue()\"\n (change)=\"onDateChange($event)\"\n class=\"absolute inset-0 w-0 h-0 opacity-0 pointer-events-none\"\n />\n </div>\n </div>\n\n <!-- Quantity Requested -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Quantity Requested\n </label>\n <input\n type=\"number\"\n formControlName=\"quantity\"\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 placeholder=\"0\"\n />\n </div>\n\n <!-- Price -->\n <div class=\"relative -top-[20px]\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Customer Haul Price\n </label>\n <div class=\"relative\">\n <input\n formControlName=\"price\"\n readonly\n class=\"w-full px-3 py-3 pl-[47px] 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 <lucide-icon\n [img]=\"icons.DollarSign\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 text-gray-400\"\n [size]=\"16\"\n ></lucide-icon>\n </div>\n </div>\n\n <!-- Unit Type Display -->\n <div class=\"relative -top-[20px]\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Unit type\n </label>\n <div\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 {{ shareRouteForm.value.units || 'N/A' }}\n </div>\n </div>\n\n <!-- Contact Info Section Title -->\n <div class=\"md:col-span-2 border-t border-gray-100 dark:border-slate-800\">\n <h3 class=\"flex items-center gap-1 text-md font-semibold text-gray-900 dark:text-white\">\n Customer Information\n </h3>\n </div>\n\n <!-- Contact Name -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Contact Name\n </label>\n <input\n formControlName=\"contactName\"\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\n <!-- Contact Phone -->\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Contact Phone\n </label>\n <input\n formControlName=\"contactPhone\"\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\n <!-- Contact Email -->\n <div class=\"space-y-1.5 md:col-span-2\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Contact Email\n </label>\n <input\n formControlName=\"contactEmail\"\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\n <!-- Special Pricing Checkbox -->\n <div class=\"md:col-span-2 pt-2 flex items-center gap-3\">\n <label class=\"relative flex h-5 w-5 cursor-pointer\">\n <input\n type=\"checkbox\"\n formControlName=\"special_price\"\n (change)=\"setValidators(shareRouteForm.value.special_price)\"\n class=\"h-full w-full appearance-none rounded-md border border-gray-300 dark:border-slate-700 transition-all checked:bg-amber-500 checked:border-amber-500 outline-none cursor-pointer\"\n />\n <lucide-icon\n [img]=\"icons.Check\"\n [size]=\"14\"\n [class.opacity-100]=\"shareRouteForm.get('special_price')?.value\"\n [class.opacity-0]=\"!shareRouteForm.get('special_price')?.value\"\n class=\"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-white transition-opacity pointer-events-none\"\n ></lucide-icon>\n </label>\n <span class=\"text-sm font-medium text-gray-700 dark:text-gray-300\"\n >Request special pricing</span\n >\n </div>\n\n <!-- Special Price Input (Conditional) -->\n @if (shareRouteForm.value.special_price) {\n <div class=\"space-y-1.5 animate-fade-in md:col-span-1\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">\n Requested Price\n </label>\n <div class=\"relative\">\n <input\n formControlName=\"customerEstimatedPrice\"\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 <lucide-icon\n [img]=\"icons.DollarSign\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 text-amber-500\"\n [size]=\"16\"\n ></lucide-icon>\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n </div>\n</div>\n", styles: ["@keyframes slide-up{0%{opacity:0;transform:translateY(20px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.animate-slide-up{animation:slide-up .3s cubic-bezier(.16,1,.3,1) forwards}.animate-fade-in{animation:fade-in .2s ease-out forwards}:host{display:block}input[type=date]::-webkit-calendar-picker-indicator{filter:invert(.5);cursor:pointer}.dark input[type=date]::-webkit-calendar-picker-indicator{filter:invert(1)}form::-webkit-scrollbar{width:6px}form::-webkit-scrollbar-track{background:transparent}form::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:10px}.dark form::-webkit-scrollbar-thumb{background:#334155}\n"] }]
|
|
1619
|
+
}], ctorParameters: () => [], propDecorators: { openShareRouteDetails: [{ type: i0.Input, args: [{ isSignal: true, alias: "openShareRouteDetails", required: false }] }], userDetails: [{ type: i0.Input, args: [{ isSignal: true, alias: "userDetails", required: false }] }], closePopupEmit: [{
|
|
1620
|
+
type: Output
|
|
1621
|
+
}], filterPickup: [{
|
|
1622
|
+
type: ViewChild,
|
|
1623
|
+
args: ['filterPickup']
|
|
1624
|
+
}], filterDelivery: [{
|
|
1625
|
+
type: ViewChild,
|
|
1626
|
+
args: ['filterDelivery']
|
|
1627
|
+
}], onDocumentClick: [{
|
|
1628
|
+
type: HostListener,
|
|
1629
|
+
args: ['document:click', ['$event']]
|
|
1630
|
+
}], onMouseMove: [{
|
|
1631
|
+
type: HostListener,
|
|
1632
|
+
args: ['document:mousemove', ['$event']]
|
|
1633
|
+
}], onMouseUp: [{
|
|
1634
|
+
type: HostListener,
|
|
1635
|
+
args: ['document:mouseup']
|
|
1636
|
+
}] } });
|
|
1637
|
+
|
|
908
1638
|
class CoolmapComponent {
|
|
909
1639
|
mobileMode = false;
|
|
910
|
-
activeSection = 'Jobcode';
|
|
1640
|
+
activeSection = input('Jobcode', ...(ngDevMode ? [{ debugName: "activeSection" }] : []));
|
|
911
1641
|
customerRepoDetails;
|
|
1642
|
+
userDetails;
|
|
912
1643
|
darkMode = input(false, ...(ngDevMode ? [{ debugName: "darkMode" }] : []));
|
|
913
1644
|
mapContainer;
|
|
1645
|
+
searchContainer;
|
|
914
1646
|
coolmapService = inject(CoolmapService);
|
|
915
1647
|
utils = inject(UtilsService);
|
|
916
1648
|
config = inject(COOLMAP_CONFIG);
|
|
917
1649
|
mapService = inject(MapboxService);
|
|
1650
|
+
elRef = inject(ElementRef);
|
|
918
1651
|
showRouteList = false;
|
|
919
1652
|
addRouteModal = false;
|
|
1653
|
+
showShareModal = signal(false, ...(ngDevMode ? [{ debugName: "showShareModal" }] : []));
|
|
1654
|
+
shareRouteData = signal(null, ...(ngDevMode ? [{ debugName: "shareRouteData" }] : []));
|
|
920
1655
|
// Filtering Signals (Lifted from JobCode)
|
|
921
1656
|
routesList = signal([], ...(ngDevMode ? [{ debugName: "routesList" }] : []));
|
|
922
1657
|
viewRoutesList = signal([], ...(ngDevMode ? [{ debugName: "viewRoutesList" }] : []));
|
|
@@ -924,17 +1659,26 @@ class CoolmapComponent {
|
|
|
924
1659
|
filteredOptions = signal([], ...(ngDevMode ? [{ debugName: "filteredOptions" }] : []));
|
|
925
1660
|
dropdownOpen = signal(false, ...(ngDevMode ? [{ debugName: "dropdownOpen" }] : []));
|
|
926
1661
|
dateValue = signal(this.utils.getDateFormat(new Date()), ...(ngDevMode ? [{ debugName: "dateValue" }] : []));
|
|
1662
|
+
displayDate = computed(() => {
|
|
1663
|
+
const val = this.dateValue();
|
|
1664
|
+
if (!val)
|
|
1665
|
+
return '';
|
|
1666
|
+
const parts = val.split('-');
|
|
1667
|
+
if (parts.length !== 3)
|
|
1668
|
+
return val;
|
|
1669
|
+
const [y, m, d] = parts;
|
|
1670
|
+
return `${m}/${d}/${y}`;
|
|
1671
|
+
}, ...(ngDevMode ? [{ debugName: "displayDate" }] : []));
|
|
927
1672
|
selectedRouteIds = signal([], ...(ngDevMode ? [{ debugName: "selectedRouteIds" }] : []));
|
|
928
1673
|
modalPlottedIds = signal([], ...(ngDevMode ? [{ debugName: "modalPlottedIds" }] : []));
|
|
929
1674
|
modalPlottedRoutes = signal([], ...(ngDevMode ? [{ debugName: "modalPlottedRoutes" }] : []));
|
|
930
1675
|
selectedRouteForEdit = signal(null, ...(ngDevMode ? [{ debugName: "selectedRouteForEdit" }] : []));
|
|
931
1676
|
unitList = signal([], ...(ngDevMode ? [{ debugName: "unitList" }] : []));
|
|
932
1677
|
materialsList = signal([], ...(ngDevMode ? [{ debugName: "materialsList" }] : []));
|
|
933
|
-
currentlyPlottedIds = [];
|
|
934
1678
|
skipNextRefresh = false;
|
|
935
1679
|
mapOpQueue = Promise.resolve();
|
|
936
1680
|
activeFilteredRoutes = computed(() => {
|
|
937
|
-
const list = this.activeSection === 'Jobcode' ? this.routesList() : this.viewRoutesList();
|
|
1681
|
+
const list = this.activeSection() === 'Jobcode' ? this.routesList() : this.viewRoutesList();
|
|
938
1682
|
const filtersArray = this.filters();
|
|
939
1683
|
if (filtersArray.length === 0)
|
|
940
1684
|
return list;
|
|
@@ -999,12 +1743,16 @@ class CoolmapComponent {
|
|
|
999
1743
|
Route,
|
|
1000
1744
|
Search,
|
|
1001
1745
|
X,
|
|
1002
|
-
LoaderCircle
|
|
1746
|
+
LoaderCircle,
|
|
1747
|
+
Calendar
|
|
1003
1748
|
};
|
|
1004
1749
|
constructor() {
|
|
1005
1750
|
this.filterForm.get('search')?.valueChanges.pipe(takeUntilDestroyed()).subscribe(value => {
|
|
1006
1751
|
if (value && typeof value === 'string') {
|
|
1007
|
-
|
|
1752
|
+
let options = this.utils.filter(value, this.filters());
|
|
1753
|
+
if (this.activeSection() === 'ViewRoute') {
|
|
1754
|
+
options = options.filter((opt) => opt.type !== 'material');
|
|
1755
|
+
}
|
|
1008
1756
|
this.filteredOptions.set(options);
|
|
1009
1757
|
this.dropdownOpen.set(options.length > 0);
|
|
1010
1758
|
}
|
|
@@ -1019,10 +1767,16 @@ class CoolmapComponent {
|
|
|
1019
1767
|
});
|
|
1020
1768
|
// Effect 1: Manage Floating List (JobCode)
|
|
1021
1769
|
effect(async () => {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1770
|
+
if (this.activeSection() === 'Jobcode') {
|
|
1771
|
+
const routes = this.routesList();
|
|
1772
|
+
const selectedIds = this.selectedRouteIds();
|
|
1773
|
+
// Queue the sync operation
|
|
1774
|
+
this.mapOpQueue = this.mapOpQueue.then(() => this.syncSpecificPrefix('jobcode', routes, selectedIds));
|
|
1775
|
+
}
|
|
1776
|
+
else {
|
|
1777
|
+
// Clear jobcode prefix if not in the section
|
|
1778
|
+
this.mapOpQueue = this.mapOpQueue.then(() => this.syncSpecificPrefix('jobcode', [], []));
|
|
1779
|
+
}
|
|
1026
1780
|
});
|
|
1027
1781
|
// Effect 2: Manage Modal List (JobRouteList)
|
|
1028
1782
|
effect(async () => {
|
|
@@ -1032,8 +1786,8 @@ class CoolmapComponent {
|
|
|
1032
1786
|
});
|
|
1033
1787
|
// Effect 3: Manage View Route Section
|
|
1034
1788
|
effect(async () => {
|
|
1035
|
-
if (this.activeSection === 'ViewRoute') {
|
|
1036
|
-
const routes = this.
|
|
1789
|
+
if (this.activeSection() === 'ViewRoute') {
|
|
1790
|
+
const routes = this.viewRoutesList();
|
|
1037
1791
|
const selectedIds = this.selectedRouteIds();
|
|
1038
1792
|
// Queue the sync operation
|
|
1039
1793
|
this.mapOpQueue = this.mapOpQueue.then(() => this.syncSpecificPrefix('viewroute', routes, selectedIds));
|
|
@@ -1044,6 +1798,11 @@ class CoolmapComponent {
|
|
|
1044
1798
|
}
|
|
1045
1799
|
});
|
|
1046
1800
|
}
|
|
1801
|
+
onClick(event) {
|
|
1802
|
+
if (this.searchContainer && !this.searchContainer.nativeElement.contains(event.target)) {
|
|
1803
|
+
this.dropdownOpen.set(false);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1047
1806
|
ngOnInit() {
|
|
1048
1807
|
this.loadData(this.dateValue());
|
|
1049
1808
|
}
|
|
@@ -1064,7 +1823,7 @@ class CoolmapComponent {
|
|
|
1064
1823
|
ngOnChanges(changes) {
|
|
1065
1824
|
if (changes['customerRepoDetails'] && !changes['customerRepoDetails'].firstChange) {
|
|
1066
1825
|
if (this.config.repository === 'customer') {
|
|
1067
|
-
if (this.activeSection === 'Jobcode') {
|
|
1826
|
+
if (this.activeSection() === 'Jobcode') {
|
|
1068
1827
|
this.loadData(this.dateValue());
|
|
1069
1828
|
}
|
|
1070
1829
|
else {
|
|
@@ -1074,8 +1833,14 @@ class CoolmapComponent {
|
|
|
1074
1833
|
}
|
|
1075
1834
|
if (changes['activeSection'] && !changes['activeSection'].firstChange) {
|
|
1076
1835
|
this.selectedRouteIds.set([]);
|
|
1836
|
+
this.modalPlottedIds.set([]);
|
|
1837
|
+
this.modalPlottedRoutes.set([]);
|
|
1077
1838
|
this.filters.set([]);
|
|
1078
|
-
|
|
1839
|
+
this.addRouteModal = false;
|
|
1840
|
+
this.showRouteList = false;
|
|
1841
|
+
this.showShareModal.set(false);
|
|
1842
|
+
this.selectedRouteForEdit.set(null);
|
|
1843
|
+
if (this.activeSection() === 'Jobcode') {
|
|
1079
1844
|
this.loadData(this.dateValue());
|
|
1080
1845
|
}
|
|
1081
1846
|
else {
|
|
@@ -1089,6 +1854,8 @@ class CoolmapComponent {
|
|
|
1089
1854
|
// Clear selection when the base list changes (date/repo switch)
|
|
1090
1855
|
if (!isRefresh) {
|
|
1091
1856
|
this.selectedRouteIds.set([]);
|
|
1857
|
+
this.filters.set([]);
|
|
1858
|
+
this.filterForm.controls['search'].setValue('');
|
|
1092
1859
|
}
|
|
1093
1860
|
if (this.config.repository === 'customer') {
|
|
1094
1861
|
this.utils.fetchUnitsList().then((unitsRes) => {
|
|
@@ -1142,6 +1909,8 @@ class CoolmapComponent {
|
|
|
1142
1909
|
this.coolmapService.isLoading.set(true);
|
|
1143
1910
|
if (!isRefresh) {
|
|
1144
1911
|
this.selectedRouteIds.set([]);
|
|
1912
|
+
this.filters.set([]);
|
|
1913
|
+
this.filterForm.controls['search'].setValue('');
|
|
1145
1914
|
}
|
|
1146
1915
|
if (this.config.repository === 'customer') {
|
|
1147
1916
|
this.utils.fetchUnitsList().then((unitsRes) => {
|
|
@@ -1219,9 +1988,16 @@ class CoolmapComponent {
|
|
|
1219
1988
|
onDateChange(event) {
|
|
1220
1989
|
const target = event.target;
|
|
1221
1990
|
if (target && target.value) {
|
|
1991
|
+
this.coolmapService.resetModals.update(v => v + 1);
|
|
1222
1992
|
this.loadData(target.value);
|
|
1223
1993
|
}
|
|
1224
1994
|
}
|
|
1995
|
+
onSearchFocus() {
|
|
1996
|
+
const value = this.filterForm.get('search')?.value;
|
|
1997
|
+
if (value && this.filteredOptions().length > 0) {
|
|
1998
|
+
this.dropdownOpen.set(true);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
1225
2001
|
selectFilter(option) {
|
|
1226
2002
|
this.filterForm.controls['search'].setValue('');
|
|
1227
2003
|
this.selectedRouteIds.set([]); // Clear selection when switching filters
|
|
@@ -1292,7 +2068,7 @@ class CoolmapComponent {
|
|
|
1292
2068
|
// 6. Update Visibility for THIS prefix only
|
|
1293
2069
|
// This allows jobcode and jobrouteList to have independent visibility states.
|
|
1294
2070
|
const compositeVisible = selectedIds.map(id => `${prefix}-${id}`);
|
|
1295
|
-
const showAllForThisPrefix = (
|
|
2071
|
+
const showAllForThisPrefix = (['jobcode', 'viewroute'].includes(prefix) && selectedIds.length === 0);
|
|
1296
2072
|
this.mapService.setRoutesVisibility(compositeVisible, showAllForThisPrefix, prefix);
|
|
1297
2073
|
this.coolmapService.isLoading.set(false);
|
|
1298
2074
|
}
|
|
@@ -1334,7 +2110,7 @@ class CoolmapComponent {
|
|
|
1334
2110
|
}
|
|
1335
2111
|
}
|
|
1336
2112
|
async refreshMapState() {
|
|
1337
|
-
if (this.activeSection === 'Jobcode') {
|
|
2113
|
+
if (this.activeSection() === 'Jobcode') {
|
|
1338
2114
|
await this.loadData(this.dateValue(), true);
|
|
1339
2115
|
}
|
|
1340
2116
|
else {
|
|
@@ -1343,7 +2119,9 @@ class CoolmapComponent {
|
|
|
1343
2119
|
}
|
|
1344
2120
|
onRouteSaved(data) {
|
|
1345
2121
|
if (data.id) {
|
|
1346
|
-
|
|
2122
|
+
if (data.unit_id) {
|
|
2123
|
+
data.unit = this.getUnitName(data);
|
|
2124
|
+
}
|
|
1347
2125
|
this.skipNextRefresh = true;
|
|
1348
2126
|
const updater = (list) => list.map(r => this.getRouteId(r) === data.id ? { ...r, ...data } : r);
|
|
1349
2127
|
this.routesList.update(updater);
|
|
@@ -1400,28 +2178,41 @@ class CoolmapComponent {
|
|
|
1400
2178
|
}, 200);
|
|
1401
2179
|
}, 0);
|
|
1402
2180
|
}
|
|
2181
|
+
handleShareRoute(data) {
|
|
2182
|
+
this.shareRouteData.set(data);
|
|
2183
|
+
this.showShareModal.set(true);
|
|
2184
|
+
}
|
|
1403
2185
|
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:12}: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"] }] });
|
|
2186
|
+
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: true, isRequired: false, transformFunction: null }, customerRepoDetails: { classPropertyName: "customerRepoDetails", publicName: "customerRepoDetails", isSignal: false, isRequired: false, transformFunction: null }, userDetails: { classPropertyName: "userDetails", publicName: "userDetails", isSignal: false, isRequired: false, transformFunction: null }, darkMode: { classPropertyName: "darkMode", publicName: "darkMode", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onClick($event)" } }, viewQueries: [{ propertyName: "mapContainer", first: true, predicate: ["mapContainer"], descendants: true }, { propertyName: "searchContainer", first: true, predicate: ["searchContainer"], 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 flex-1\">\n @if (activeSection() === 'Jobcode') {\n <div class=\"relative w-full sm:w-auto group\">\n <input\n type=\"text\"\n [value]=\"displayDate()\"\n readonly\n [disabled]=\"coolmapService.isLoading()\"\n (click)=\"dateInput.showPicker()\"\n class=\"w-full lg:w-[200px] pl-3 pr-10 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 cursor-pointer\"\n />\n <lucide-icon \n [img]=\"icons.Calendar\" \n [size]=\"18\" \n (click)=\"!coolmapService.isLoading() && dateInput.showPicker()\"\n [class.pointer-events-none]=\"coolmapService.isLoading()\"\n [class.opacity-50]=\"coolmapService.isLoading()\"\n class=\"absolute right-3 top-1/2 -translate-y-1/2 text-white-400 group-hover:text-brand-blue transition-colors cursor-pointer\"\n ></lucide-icon>\n <input\n #dateInput\n type=\"date\"\n [value]=\"dateValue()\"\n (change)=\"onDateChange($event)\"\n [disabled]=\"coolmapService.isLoading()\"\n class=\"absolute inset-0 w-0 h-0 opacity-0 pointer-events-none\"\n />\n </div>\n <span class=\"toolbar-divider hidden sm:flex h-6 w-[1px] bg-gray-200 dark:bg-slate-700\"></span>\n }\n <div #searchContainer class=\"relative items-center flex-1\">\n <lucide-icon [img]=\"icons.Search\" [size]=\"18\" class=\"absolute left-[0.55rem] top-[50%] -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-full 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 (focus)=\"onSearchFocus()\"\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-70 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-xs font-medium border-b border-gray-100 dark:border-slate-700 last:border-0 dark:text-white\">\n <span class=\"capitalize\">{{ 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 routes ({{ 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 right-0 mt-2 group-hover:block hidden bg-gray-800 dark:bg-white text-white dark:text-black text-[10px] rounded py-1 px-2 whitespace-nowrap\">\n Show Route List\n <div\n class=\"absolute -top-[8px] left-[60px] border-4 border-transparent border-b-gray-800 dark:border-b-white\">\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 (shareRoute)=\"handleShareRoute($event)\"\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\n@if (showShareModal()) {\n <lib-share-route\n [openShareRouteDetails]=\"shareRouteData()\"\n [userDetails]=\"userDetails\"\n (closePopupEmit)=\"showShareModal.set(false)\" class=\"relative\"\n ></lib-share-route>\n}\n\n<ag-toast-container></ag-toast-container>\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:12}: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: "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"] }, { kind: "ngmodule", type: CommonModule }, { 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: JobCodeComponent, selector: "lib-job-code", inputs: ["listMode", "collapsible", "routes", "selectedRouteIds", "isLoading"], outputs: ["routeSelect"] }, { kind: "component", type: ViewRouteListComponent, selector: "lib-view-route-list", inputs: ["listMode", "collapsible", "routes", "selectedRouteIds"], outputs: ["routeSelect", "editRoute"] }, { kind: "component", type: JobRouteListComponent, selector: "lib-job-route-list", inputs: ["customerRepoDetails", "selectedRouteIds", "modalOpen", "initialRoutes"], outputs: ["modalOpenChange", "routeSelect", "masterToggleEvent", "shareRoute"] }, { kind: "component", type: AddRouteComponent, selector: "lib-add-route", inputs: ["modalOpen", "routeData", "customerRepoDetails"], outputs: ["modalOpenChange", "routeDeleted", "routeSaved"] }, { kind: "component", type: ShareRouteComponent, selector: "lib-share-route", inputs: ["openShareRouteDetails", "userDetails"], outputs: ["closePopupEmit"] }, { kind: "component", type: AgToastContainerComponent, selector: "ag-toast-container" }] });
|
|
1405
2187
|
}
|
|
1406
2188
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapComponent, decorators: [{
|
|
1407
2189
|
type: Component,
|
|
1408
2190
|
args: [{ selector: 'lib-coolmap', standalone: true, imports: [
|
|
1409
|
-
|
|
2191
|
+
ReactiveFormsModule,
|
|
2192
|
+
CommonModule,
|
|
1410
2193
|
LucideAngularModule,
|
|
1411
|
-
|
|
2194
|
+
JobCodeComponent,
|
|
1412
2195
|
ViewRouteListComponent,
|
|
2196
|
+
JobRouteListComponent,
|
|
1413
2197
|
AddRouteComponent,
|
|
1414
|
-
|
|
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:12}: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"] }]
|
|
2198
|
+
ShareRouteComponent,
|
|
2199
|
+
AgToastContainerComponent,
|
|
2200
|
+
], 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 flex-1\">\n @if (activeSection() === 'Jobcode') {\n <div class=\"relative w-full sm:w-auto group\">\n <input\n type=\"text\"\n [value]=\"displayDate()\"\n readonly\n [disabled]=\"coolmapService.isLoading()\"\n (click)=\"dateInput.showPicker()\"\n class=\"w-full lg:w-[200px] pl-3 pr-10 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 cursor-pointer\"\n />\n <lucide-icon \n [img]=\"icons.Calendar\" \n [size]=\"18\" \n (click)=\"!coolmapService.isLoading() && dateInput.showPicker()\"\n [class.pointer-events-none]=\"coolmapService.isLoading()\"\n [class.opacity-50]=\"coolmapService.isLoading()\"\n class=\"absolute right-3 top-1/2 -translate-y-1/2 text-white-400 group-hover:text-brand-blue transition-colors cursor-pointer\"\n ></lucide-icon>\n <input\n #dateInput\n type=\"date\"\n [value]=\"dateValue()\"\n (change)=\"onDateChange($event)\"\n [disabled]=\"coolmapService.isLoading()\"\n class=\"absolute inset-0 w-0 h-0 opacity-0 pointer-events-none\"\n />\n </div>\n <span class=\"toolbar-divider hidden sm:flex h-6 w-[1px] bg-gray-200 dark:bg-slate-700\"></span>\n }\n <div #searchContainer class=\"relative items-center flex-1\">\n <lucide-icon [img]=\"icons.Search\" [size]=\"18\" class=\"absolute left-[0.55rem] top-[50%] -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-full 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 (focus)=\"onSearchFocus()\"\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-70 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-xs font-medium border-b border-gray-100 dark:border-slate-700 last:border-0 dark:text-white\">\n <span class=\"capitalize\">{{ 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 routes ({{ 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 right-0 mt-2 group-hover:block hidden bg-gray-800 dark:bg-white text-white dark:text-black text-[10px] rounded py-1 px-2 whitespace-nowrap\">\n Show Route List\n <div\n class=\"absolute -top-[8px] left-[60px] border-4 border-transparent border-b-gray-800 dark:border-b-white\">\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 (shareRoute)=\"handleShareRoute($event)\"\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\n@if (showShareModal()) {\n <lib-share-route\n [openShareRouteDetails]=\"shareRouteData()\"\n [userDetails]=\"userDetails\"\n (closePopupEmit)=\"showShareModal.set(false)\" class=\"relative\"\n ></lib-share-route>\n}\n\n<ag-toast-container></ag-toast-container>\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:12}: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
2201
|
}], ctorParameters: () => [], propDecorators: { mobileMode: [{
|
|
1417
2202
|
type: Input
|
|
1418
|
-
}], activeSection: [{
|
|
2203
|
+
}], activeSection: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeSection", required: false }] }], customerRepoDetails: [{
|
|
1419
2204
|
type: Input
|
|
1420
|
-
}],
|
|
2205
|
+
}], userDetails: [{
|
|
1421
2206
|
type: Input
|
|
1422
2207
|
}], darkMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "darkMode", required: false }] }], mapContainer: [{
|
|
1423
2208
|
type: ViewChild,
|
|
1424
2209
|
args: ['mapContainer']
|
|
2210
|
+
}], searchContainer: [{
|
|
2211
|
+
type: ViewChild,
|
|
2212
|
+
args: ['searchContainer']
|
|
2213
|
+
}], onClick: [{
|
|
2214
|
+
type: HostListener,
|
|
2215
|
+
args: ['document:click', ['$event']]
|
|
1425
2216
|
}] } });
|
|
1426
2217
|
|
|
1427
2218
|
/*
|
|
@@ -1432,5 +2223,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
1432
2223
|
* Generated bundle index. Do not edit.
|
|
1433
2224
|
*/
|
|
1434
2225
|
|
|
1435
|
-
export { Coolmap, CoolmapComponent };
|
|
2226
|
+
export { AgToastContainerComponent, Coolmap, CoolmapComponent };
|
|
1436
2227
|
//# sourceMappingURL=aggdirect-coolmap.mjs.map
|