✨ Show route on day click in trip table
This commit is contained in:
parent
2ffbcbf57d
commit
5ae894c577
@ -1 +1 @@
|
||||
__version__ = "1.2.0"
|
||||
__version__ = "1.3.0"
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
}
|
||||
|
||||
<section class="p-4 print:px-1 grid md:grid-cols-3 gap-4 print:block">
|
||||
<div class="p-4 shadow rounded-md md:col-span-2 max-w-screen print:col-span-full">
|
||||
<div class="p-4 shadow self-start rounded-md md:col-span-2 max-w-screen print:col-span-full">
|
||||
<div class="p-2 mb-2 flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="font-semibold tracking-tight text-xl">Plans</h1>
|
||||
@ -76,12 +76,13 @@
|
||||
<th class="w-12">Status</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template #body let-tripitem let-rowIndex="rowIndex" let-rowgroup="rowgroup" let-rowspan="rowspan">
|
||||
<ng-template #body let-tripitem let-rowgroup="rowgroup" let-rowspan="rowspan">
|
||||
<tr class="h-12 cursor-pointer" [class.font-bold]="selectedItem?.id === tripitem.id"
|
||||
(click)="onRowClick(tripitem)">
|
||||
@if (rowgroup) {
|
||||
<td [attr.rowspan]="rowspan" class="font-normal! max-w-20 truncate cursor-default"
|
||||
(click)="$event.stopPropagation()">
|
||||
<td [attr.rowspan]="rowspan" class="font-normal! max-w-20 truncate cursor-pointer"
|
||||
[class.text-blue-500]="tripMapAntLayerDayID == tripitem.day_id"
|
||||
(click)="toggleTripDayHighlightPath(tripitem.day_id); $event.stopPropagation()">
|
||||
<div class="truncate">{{tripitem.td_label }}</div>
|
||||
</td>
|
||||
}
|
||||
@ -273,7 +274,7 @@
|
||||
@defer {
|
||||
@for (p of places; track p.id) {
|
||||
<div class="flex items-center gap-4 py-2 px-4 hover:bg-gray-50 rounded-md overflow-auto"
|
||||
(mouseenter)="highlightMarker(p.lat, p.lng)" (mouseleave)="resetHighlightMarker()">
|
||||
(mouseenter)="placeHighlightMarker(p.lat, p.lng)" (mouseleave)="resetPlaceHighlightMarker()">
|
||||
<img [src]="p.image" class="w-12 rounded-full object-fit">
|
||||
|
||||
<div class="flex flex-col gap-1 truncate">
|
||||
@ -322,7 +323,7 @@
|
||||
<span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} places</span>
|
||||
</div>
|
||||
|
||||
<p-button icon="pi pi-refresh" [disabled]="!places.length" (click)="setMapBounds()" text />
|
||||
<p-button icon="pi pi-refresh" [disabled]="!places.length" (click)="resetMapBounds()" text />
|
||||
</div>
|
||||
|
||||
<div id="map" class="w-full rounded-md min-h-96 h-1/3 max-h-full"></div>
|
||||
|
||||
@ -6,6 +6,7 @@ import { InputTextModule } from "primeng/inputtext";
|
||||
import { SkeletonModule } from "primeng/skeleton";
|
||||
import { FloatLabelModule } from "primeng/floatlabel";
|
||||
import * as L from "leaflet";
|
||||
import { AntPath, antPath } from "leaflet-ant-path";
|
||||
import { TableModule } from "primeng/table";
|
||||
import {
|
||||
Trip,
|
||||
@ -60,6 +61,9 @@ export class TripComponent implements AfterViewInit {
|
||||
currency$: Observable<string>;
|
||||
|
||||
trip: Trip | undefined;
|
||||
tripMapAntLayer: undefined;
|
||||
tripMapAntLayerDayID: number | undefined;
|
||||
|
||||
totalPrice: number = 0;
|
||||
dayStatsCache = new Map<number, { price: number; places: number }>();
|
||||
|
||||
@ -137,7 +141,7 @@ export class TripComponent implements AfterViewInit {
|
||||
this.setPlacesAndMarkers();
|
||||
|
||||
this.map.setView([48.107, -2.988]);
|
||||
this.setMapBounds();
|
||||
this.resetMapBounds();
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -204,7 +208,7 @@ export class TripComponent implements AfterViewInit {
|
||||
});
|
||||
}
|
||||
|
||||
setMapBounds() {
|
||||
resetMapBounds() {
|
||||
if (!this.places.length) return;
|
||||
this.map.fitBounds(
|
||||
this.places.map((p) => [p.lat, p.lng]),
|
||||
@ -224,14 +228,14 @@ export class TripComponent implements AfterViewInit {
|
||||
) ?? 0;
|
||||
}
|
||||
|
||||
resetHighlightMarker() {
|
||||
resetPlaceHighlightMarker() {
|
||||
if (this.hoveredElement) {
|
||||
this.hoveredElement.classList.remove("listHover");
|
||||
this.hoveredElement = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
highlightMarker(lat: number, lng: number) {
|
||||
placeHighlightMarker(lat: number, lng: number) {
|
||||
if (this.hoveredElement) {
|
||||
this.hoveredElement.classList.remove("listHover");
|
||||
this.hoveredElement = undefined;
|
||||
@ -266,13 +270,61 @@ export class TripComponent implements AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
toggleTripDayHighlightPath(day_id: number) {
|
||||
// Click on the currently displayed day: remove
|
||||
if (this.tripMapAntLayerDayID == day_id) {
|
||||
this.map.removeLayer(this.tripMapAntLayer);
|
||||
this.tripMapAntLayerDayID = undefined;
|
||||
this.resetMapBounds();
|
||||
return;
|
||||
}
|
||||
|
||||
let index = this.trip?.days.findIndex((d) => d.id === day_id);
|
||||
if (!this.trip || index == -1) return;
|
||||
|
||||
const data = this.trip.days[index as number].items;
|
||||
data.sort((a, b) => a.time.localeCompare(b.time));
|
||||
const coords = data
|
||||
.map((item) => {
|
||||
if (item.lat && item.lng) return [item.lat, item.lng];
|
||||
if (item.place && item.place) return [item.place.lat, item.place.lng];
|
||||
return undefined;
|
||||
})
|
||||
.filter((n): n is number[] => n !== undefined);
|
||||
this.map.fitBounds(coords, { padding: [30, 30] });
|
||||
|
||||
const path = antPath(coords, {
|
||||
delay: 400,
|
||||
dashArray: [10, 20],
|
||||
weight: 5,
|
||||
color: "#0000FF",
|
||||
pulseColor: "#FFFFFF",
|
||||
paused: false,
|
||||
reverse: false,
|
||||
hardwareAccelerated: true,
|
||||
});
|
||||
|
||||
if (this.tripMapAntLayer) {
|
||||
this.map.removeLayer(this.tripMapAntLayer);
|
||||
this.tripMapAntLayerDayID = undefined;
|
||||
}
|
||||
|
||||
// UX
|
||||
setTimeout(() => {
|
||||
this.map.addLayer(path);
|
||||
}, 200);
|
||||
|
||||
this.tripMapAntLayer = path;
|
||||
this.tripMapAntLayerDayID = day_id;
|
||||
}
|
||||
|
||||
onRowClick(item: FlattenedTripItem) {
|
||||
if (this.selectedItem && this.selectedItem.id === item.id) {
|
||||
this.selectedItem = undefined;
|
||||
this.resetHighlightMarker();
|
||||
this.resetPlaceHighlightMarker();
|
||||
} else {
|
||||
this.selectedItem = item;
|
||||
if (item.lat && item.lng) this.highlightMarker(item.lat, item.lng);
|
||||
if (item.lat && item.lng) this.placeHighlightMarker(item.lat, item.lng);
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,7 +532,7 @@ export class TripComponent implements AfterViewInit {
|
||||
next: (trip) => {
|
||||
this.trip = trip;
|
||||
this.setPlacesAndMarkers();
|
||||
this.setMapBounds();
|
||||
this.resetMapBounds();
|
||||
},
|
||||
});
|
||||
},
|
||||
@ -543,7 +595,10 @@ export class TripComponent implements AfterViewInit {
|
||||
data: {
|
||||
places: this.places,
|
||||
days: this.trip?.days,
|
||||
item: item,
|
||||
item: {
|
||||
...item,
|
||||
status: item.status ? (item.status as TripStatus).label : null,
|
||||
},
|
||||
},
|
||||
breakpoints: {
|
||||
"640px": "90vw",
|
||||
@ -582,6 +637,9 @@ export class TripComponent implements AfterViewInit {
|
||||
|
||||
const updatedPrice = -(item.price || 0) + (it.price || 0);
|
||||
this.updateTotalPrice(updatedPrice);
|
||||
|
||||
if (this.tripMapAntLayerDayID == item.day_id)
|
||||
this.toggleTripDayHighlightPath(item.day_id);
|
||||
},
|
||||
});
|
||||
},
|
||||
@ -621,7 +679,7 @@ export class TripComponent implements AfterViewInit {
|
||||
);
|
||||
this.dayStatsCache.delete(item.day_id);
|
||||
this.selectedItem = undefined;
|
||||
this.resetHighlightMarker();
|
||||
this.resetPlaceHighlightMarker();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import {
|
||||
FormBuilder,
|
||||
FormGroup,
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from "@angular/forms";
|
||||
import { ButtonModule } from "primeng/button";
|
||||
import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog";
|
||||
import { FloatLabelModule } from "primeng/floatlabel";
|
||||
@ -40,30 +45,52 @@ export class TripCreateDayItemModalComponent {
|
||||
private ref: DynamicDialogRef,
|
||||
private fb: FormBuilder,
|
||||
private config: DynamicDialogConfig,
|
||||
private utilsService: UtilsService
|
||||
private utilsService: UtilsService,
|
||||
) {
|
||||
this.statuses = this.utilsService.statuses;
|
||||
|
||||
this.itemForm = this.fb.group({
|
||||
id: -1,
|
||||
time: ["", { validators: [Validators.required, Validators.pattern(/^([01]\d|2[0-3])(:[0-5]\d)?$/)] }],
|
||||
time: [
|
||||
"",
|
||||
{
|
||||
validators: [
|
||||
Validators.required,
|
||||
Validators.pattern(/^([01]\d|2[0-3])(:[0-5]\d)?$/),
|
||||
],
|
||||
},
|
||||
],
|
||||
text: ["", Validators.required],
|
||||
comment: "",
|
||||
day_id: [null, Validators.required],
|
||||
place: null,
|
||||
status: null,
|
||||
price: 0,
|
||||
lat: ["", { validators: Validators.pattern("-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)") }],
|
||||
lng: ["", { validators: Validators.pattern("-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)") }],
|
||||
lat: [
|
||||
"",
|
||||
{
|
||||
validators: Validators.pattern("-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)"),
|
||||
},
|
||||
],
|
||||
lng: [
|
||||
"",
|
||||
{
|
||||
validators: Validators.pattern(
|
||||
"-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)",
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (this.config.data) {
|
||||
const item = this.config.data.item;
|
||||
if (item) this.itemForm.patchValue({ ...item, place: item.place?.id || null });
|
||||
if (item)
|
||||
this.itemForm.patchValue({ ...item, place: item.place?.id || null });
|
||||
|
||||
this.places = this.config.data.places;
|
||||
this.days = this.config.data.days;
|
||||
if (this.config.data.selectedDay) this.itemForm.get("day_id")?.setValue(this.config.data.selectedDay);
|
||||
if (this.config.data.selectedDay)
|
||||
this.itemForm.get("day_id")?.setValue(this.config.data.selectedDay);
|
||||
}
|
||||
|
||||
this.itemForm.get("place")?.valueChanges.subscribe({
|
||||
@ -96,8 +123,8 @@ export class TripCreateDayItemModalComponent {
|
||||
// Normalize data for API POST
|
||||
let ret = this.itemForm.value;
|
||||
if (!ret["lat"]) {
|
||||
delete ret["lat"];
|
||||
delete ret["lng"];
|
||||
ret["lat"] = null;
|
||||
ret["lng"] = null;
|
||||
}
|
||||
if (!ret["place"]) delete ret["place"];
|
||||
this.ref.close(ret);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user