Skip to content

Commit 5ae894c

Browse files
committed
✨ Show route on day click in trip table
1 parent 2ffbcbf commit 5ae894c

File tree

4 files changed

+111
-25
lines changed

4 files changed

+111
-25
lines changed

backend/trip/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.2.0"
1+
__version__ = "1.3.0"

src/src/app/components/trip/trip.component.html

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ <h1 class="font-medium tracking-tight text-2xl truncate">{{ trip?.name }}</h1>
4545
}
4646

4747
<section class="p-4 print:px-1 grid md:grid-cols-3 gap-4 print:block">
48-
<div class="p-4 shadow rounded-md md:col-span-2 max-w-screen print:col-span-full">
48+
<div class="p-4 shadow self-start rounded-md md:col-span-2 max-w-screen print:col-span-full">
4949
<div class="p-2 mb-2 flex justify-between items-center">
5050
<div>
5151
<h1 class="font-semibold tracking-tight text-xl">Plans</h1>
@@ -76,12 +76,13 @@ <h1 class="font-semibold tracking-tight text-xl">Plans</h1>
7676
<th class="w-12">Status</th>
7777
</tr>
7878
</ng-template>
79-
<ng-template #body let-tripitem let-rowIndex="rowIndex" let-rowgroup="rowgroup" let-rowspan="rowspan">
79+
<ng-template #body let-tripitem let-rowgroup="rowgroup" let-rowspan="rowspan">
8080
<tr class="h-12 cursor-pointer" [class.font-bold]="selectedItem?.id === tripitem.id"
8181
(click)="onRowClick(tripitem)">
8282
@if (rowgroup) {
83-
<td [attr.rowspan]="rowspan" class="font-normal! max-w-20 truncate cursor-default"
84-
(click)="$event.stopPropagation()">
83+
<td [attr.rowspan]="rowspan" class="font-normal! max-w-20 truncate cursor-pointer"
84+
[class.text-blue-500]="tripMapAntLayerDayID == tripitem.day_id"
85+
(click)="toggleTripDayHighlightPath(tripitem.day_id); $event.stopPropagation()">
8586
<div class="truncate">{{tripitem.td_label }}</div>
8687
</td>
8788
}
@@ -273,7 +274,7 @@ <h1 class="font-semibold tracking-tight text-xl">Places</h1>
273274
@defer {
274275
@for (p of places; track p.id) {
275276
<div class="flex items-center gap-4 py-2 px-4 hover:bg-gray-50 rounded-md overflow-auto"
276-
(mouseenter)="highlightMarker(p.lat, p.lng)" (mouseleave)="resetHighlightMarker()">
277+
(mouseenter)="placeHighlightMarker(p.lat, p.lng)" (mouseleave)="resetPlaceHighlightMarker()">
277278
<img [src]="p.image" class="w-12 rounded-full object-fit">
278279

279280
<div class="flex flex-col gap-1 truncate">
@@ -322,7 +323,7 @@ <h1 class="font-semibold tracking-tight text-xl">Map</h1>
322323
<span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} places</span>
323324
</div>
324325

325-
<p-button icon="pi pi-refresh" [disabled]="!places.length" (click)="setMapBounds()" text />
326+
<p-button icon="pi pi-refresh" [disabled]="!places.length" (click)="resetMapBounds()" text />
326327
</div>
327328

328329
<div id="map" class="w-full rounded-md min-h-96 h-1/3 max-h-full"></div>

src/src/app/components/trip/trip.component.ts

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { InputTextModule } from "primeng/inputtext";
66
import { SkeletonModule } from "primeng/skeleton";
77
import { FloatLabelModule } from "primeng/floatlabel";
88
import * as L from "leaflet";
9+
import { AntPath, antPath } from "leaflet-ant-path";
910
import { TableModule } from "primeng/table";
1011
import {
1112
Trip,
@@ -60,6 +61,9 @@ export class TripComponent implements AfterViewInit {
6061
currency$: Observable<string>;
6162

6263
trip: Trip | undefined;
64+
tripMapAntLayer: undefined;
65+
tripMapAntLayerDayID: number | undefined;
66+
6367
totalPrice: number = 0;
6468
dayStatsCache = new Map<number, { price: number; places: number }>();
6569

@@ -137,7 +141,7 @@ export class TripComponent implements AfterViewInit {
137141
this.setPlacesAndMarkers();
138142

139143
this.map.setView([48.107, -2.988]);
140-
this.setMapBounds();
144+
this.resetMapBounds();
141145
},
142146
});
143147
}
@@ -204,7 +208,7 @@ export class TripComponent implements AfterViewInit {
204208
});
205209
}
206210

207-
setMapBounds() {
211+
resetMapBounds() {
208212
if (!this.places.length) return;
209213
this.map.fitBounds(
210214
this.places.map((p) => [p.lat, p.lng]),
@@ -224,14 +228,14 @@ export class TripComponent implements AfterViewInit {
224228
) ?? 0;
225229
}
226230

227-
resetHighlightMarker() {
231+
resetPlaceHighlightMarker() {
228232
if (this.hoveredElement) {
229233
this.hoveredElement.classList.remove("listHover");
230234
this.hoveredElement = undefined;
231235
}
232236
}
233237

234-
highlightMarker(lat: number, lng: number) {
238+
placeHighlightMarker(lat: number, lng: number) {
235239
if (this.hoveredElement) {
236240
this.hoveredElement.classList.remove("listHover");
237241
this.hoveredElement = undefined;
@@ -266,13 +270,61 @@ export class TripComponent implements AfterViewInit {
266270
}
267271
}
268272

273+
toggleTripDayHighlightPath(day_id: number) {
274+
// Click on the currently displayed day: remove
275+
if (this.tripMapAntLayerDayID == day_id) {
276+
this.map.removeLayer(this.tripMapAntLayer);
277+
this.tripMapAntLayerDayID = undefined;
278+
this.resetMapBounds();
279+
return;
280+
}
281+
282+
let index = this.trip?.days.findIndex((d) => d.id === day_id);
283+
if (!this.trip || index == -1) return;
284+
285+
const data = this.trip.days[index as number].items;
286+
data.sort((a, b) => a.time.localeCompare(b.time));
287+
const coords = data
288+
.map((item) => {
289+
if (item.lat && item.lng) return [item.lat, item.lng];
290+
if (item.place && item.place) return [item.place.lat, item.place.lng];
291+
return undefined;
292+
})
293+
.filter((n): n is number[] => n !== undefined);
294+
this.map.fitBounds(coords, { padding: [30, 30] });
295+
296+
const path = antPath(coords, {
297+
delay: 400,
298+
dashArray: [10, 20],
299+
weight: 5,
300+
color: "#0000FF",
301+
pulseColor: "#FFFFFF",
302+
paused: false,
303+
reverse: false,
304+
hardwareAccelerated: true,
305+
});
306+
307+
if (this.tripMapAntLayer) {
308+
this.map.removeLayer(this.tripMapAntLayer);
309+
this.tripMapAntLayerDayID = undefined;
310+
}
311+
312+
// UX
313+
setTimeout(() => {
314+
this.map.addLayer(path);
315+
}, 200);
316+
317+
this.tripMapAntLayer = path;
318+
this.tripMapAntLayerDayID = day_id;
319+
}
320+
269321
onRowClick(item: FlattenedTripItem) {
270322
if (this.selectedItem && this.selectedItem.id === item.id) {
271323
this.selectedItem = undefined;
272-
this.resetHighlightMarker();
324+
this.resetPlaceHighlightMarker();
273325
} else {
274326
this.selectedItem = item;
275-
if (item.lat && item.lng) this.highlightMarker(item.lat, item.lng);
327+
if (item.lat && item.lng) this.placeHighlightMarker(item.lat, item.lng);
276328
}
277329
}
278330

@@ -480,7 +532,7 @@ export class TripComponent implements AfterViewInit {
480532
next: (trip) => {
481533
this.trip = trip;
482534
this.setPlacesAndMarkers();
483-
this.setMapBounds();
535+
this.resetMapBounds();
484536
},
485537
});
486538
},
@@ -543,7 +595,10 @@ export class TripComponent implements AfterViewInit {
543595
data: {
544596
places: this.places,
545597
days: this.trip?.days,
546-
item: item,
598+
item: {
599+
...item,
600+
status: item.status ? (item.status as TripStatus).label : null,
601+
},
547602
},
548603
breakpoints: {
549604
"640px": "90vw",
@@ -582,6 +637,9 @@ export class TripComponent implements AfterViewInit {
582637

583638
const updatedPrice = -(item.price || 0) + (it.price || 0);
584639
this.updateTotalPrice(updatedPrice);
640+
641+
if (this.tripMapAntLayerDayID == item.day_id)
642+
this.toggleTripDayHighlightPath(item.day_id);
585643
},
586644
});
587645
},
@@ -621,7 +679,7 @@ export class TripComponent implements AfterViewInit {
621679
);
622680
this.dayStatsCache.delete(item.day_id);
623681
this.selectedItem = undefined;
624-
this.resetHighlightMarker();
682+
this.resetPlaceHighlightMarker();
625683
}
626684
},
627685
});

src/src/app/modals/trip-create-day-item-modal/trip-create-day-item-modal.component.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { Component } from "@angular/core";
2-
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
2+
import {
3+
FormBuilder,
4+
FormGroup,
5+
ReactiveFormsModule,
6+
Validators,
7+
} from "@angular/forms";
38
import { ButtonModule } from "primeng/button";
49
import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog";
510
import { FloatLabelModule } from "primeng/floatlabel";
@@ -40,30 +45,52 @@ export class TripCreateDayItemModalComponent {
4045
private ref: DynamicDialogRef,
4146
private fb: FormBuilder,
4247
private config: DynamicDialogConfig,
43-
private utilsService: UtilsService
48+
private utilsService: UtilsService,
4449
) {
4550
this.statuses = this.utilsService.statuses;
4651

4752
this.itemForm = this.fb.group({
4853
id: -1,
49-
time: ["", { validators: [Validators.required, Validators.pattern(/^([01]\d|2[0-3])(:[0-5]\d)?$/)] }],
54+
time: [
55+
"",
56+
{
57+
validators: [
58+
Validators.required,
59+
Validators.pattern(/^([01]\d|2[0-3])(:[0-5]\d)?$/),
60+
],
61+
},
62+
],
5063
text: ["", Validators.required],
5164
comment: "",
5265
day_id: [null, Validators.required],
5366
place: null,
5467
status: null,
5568
price: 0,
56-
lat: ["", { validators: Validators.pattern("-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)") }],
57-
lng: ["", { validators: Validators.pattern("-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)") }],
69+
lat: [
70+
"",
71+
{
72+
validators: Validators.pattern("-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)"),
73+
},
74+
],
75+
lng: [
76+
"",
77+
{
78+
validators: Validators.pattern(
79+
"-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)",
80+
),
81+
},
82+
],
5883
});
5984

6085
if (this.config.data) {
6186
const item = this.config.data.item;
62-
if (item) this.itemForm.patchValue({ ...item, place: item.place?.id || null });
87+
if (item)
88+
this.itemForm.patchValue({ ...item, place: item.place?.id || null });
6389

6490
this.places = this.config.data.places;
6591
this.days = this.config.data.days;
66-
if (this.config.data.selectedDay) this.itemForm.get("day_id")?.setValue(this.config.data.selectedDay);
92+
if (this.config.data.selectedDay)
93+
this.itemForm.get("day_id")?.setValue(this.config.data.selectedDay);
6794
}
6895

6996
this.itemForm.get("place")?.valueChanges.subscribe({
@@ -96,8 +123,8 @@ export class TripCreateDayItemModalComponent {
96123
// Normalize data for API POST
97124
let ret = this.itemForm.value;
98125
if (!ret["lat"]) {
99-
delete ret["lat"];
100-
delete ret["lng"];
126+
ret["lat"] = null;
127+
ret["lng"] = null;
101128
}
102129
if (!ret["place"]) delete ret["place"];
103130
this.ref.close(ret);

0 commit comments

Comments
 (0)