Skip to content

Commit 0fdd149

Browse files
committed
✨ Trip: table filtering
1 parent adc7b80 commit 0fdd149

File tree

2 files changed

+118
-46
lines changed

2 files changed

+118
-46
lines changed

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

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ <h1 class="font-semibold tracking-tight text-xl">Plans</h1>
6060
<div class="hidden md:flex items-center gap-2">
6161
<p-button pTooltip="Expand table" class="hidden lg:flex" icon="pi pi-arrows-h"
6262
(click)="isExpanded = !isExpanded" text />
63+
<p-button pTooltip="Show filters" icon="pi pi-filter" (click)="toggleFiltering()" text />
6364
<p-button [pTooltip]="tableExpandableMode ? 'f' : 'Switch table mode, allow column resizing'"
6465
[icon]="tableExpandableMode ? 'pi pi-arrow-up-right-and-arrow-down-left-from-center' : 'pi pi-arrow-down-left-and-arrow-up-right-to-center'"
6566
(click)="tableExpandableMode = !tableExpandableMode" text />
@@ -79,20 +80,30 @@ <h1 class="font-semibold tracking-tight text-xl">Plans</h1>
7980
</div>
8081
</div>
8182

83+
@if (isFilteringMode) {
84+
<div class="grid md:grid-cols-2 gap-2 mb-2">
85+
<p-multiselect display="chip" [options]="tripTableColumns" [(ngModel)]="tripTableSelectedColumns"
86+
styleClass="capitalize" selectedItemsLabel="{0} columns selected" placeholder="Choose Columns" />
87+
88+
<input [formControl]="tripTableSearchInput" pInputText placeholder="Search..." />
89+
</div>
90+
}
91+
8292
@defer {
8393
@if (flattenedTripItems.length) {
8494
<p-table [value]="flattenedTripItems" class="print-striped-rows" styleClass="max-w-[85vw] md:max-w-full"
8595
[rowGroupMode]="tableExpandableMode ? 'subheader': 'rowspan'" groupRowsBy="td_label">
8696
<ng-template #header>
8797
<tr>
88-
<th>Day</th>
89-
<th class="w-10">Time</th>
90-
<th>Text</th>
91-
<th class="w-24">Place</th>
92-
<th>Comment</th>
93-
<th class="w-20">LatLng</th>
94-
<th class="w-12">Price</th>
95-
<th class="w-12">Status</th>
98+
@if (!tableExpandableMode && tripTableSelectedColumns.includes('day')) {<th class="w-24" pResizableColumn>Day
99+
</th>}
100+
@if (tripTableSelectedColumns.includes('time')) {<th class="w-12" pResizableColumn>Time</th>}
101+
@if (tripTableSelectedColumns.includes('text')) {<th pResizableColumn>Text</th>}
102+
@if (tripTableSelectedColumns.includes('place')) {<th pResizableColumn>Place</th>}
103+
@if (tripTableSelectedColumns.includes('comment')) {<th pResizableColumn>Comment</th>}
104+
@if (tripTableSelectedColumns.includes('LatLng')) {<th class="w-12" pResizableColumn>LatLng</th>}
105+
@if (tripTableSelectedColumns.includes('price')) {<th class="w-12" pResizableColumn>Price</th>}
106+
@if (tripTableSelectedColumns.includes('status')) {<th class="w-12" pResizableColumn>Status</th>}
96107
</tr>
97108
</ng-template>
98109
@if (tableExpandableMode) {
@@ -113,81 +124,90 @@ <h1 class="font-semibold tracking-tight text-xl">Plans</h1>
113124
<ng-template #expandedrow let-tripitem>
114125
<tr class="h-12 cursor-pointer" [class.font-bold]="selectedItem?.id === tripitem.id"
115126
(click)="onRowClick(tripitem)">
116-
<td class="font-mono text-sm max-w-20 truncate">{{ tripitem.td_label }}</td>
117-
<td class="font-mono text-sm">{{ tripitem.time }}</td>
118-
<td class="relative max-w-60 truncate">
119-
<div class="relative">
120-
@if (tripitem.status) {<div class="block xl:hidden absolute top-0 -left-1.5 size-1.5 rounded-full"
127+
@if (tripTableSelectedColumns.includes('time')) {<td class="font-mono text-sm">{{ tripitem.time }}</td>}
128+
@if (tripTableSelectedColumns.includes('text')) {<td class="relative">
129+
<div class="truncate">
130+
@if (tripitem.status) {<div class="block absolute top-3 left-1.5 size-2 rounded-full"
121131
[style.background]="tripitem.status.color"></div>}
122132
{{ tripitem.text }}
123133
</div>
124-
</td>
125-
<td class="relative">
134+
</td>}
135+
@if (tripTableSelectedColumns.includes('place')) {<td class="relative">
126136
@if (tripitem.place) {
127-
<div class="ml-7 print:ml-0 max-w-24 truncate print:whitespace-normal">
137+
<div class="ml-7 print:ml-0 truncate print:whitespace-normal">
128138
<img [src]="tripitem.place.image || tripitem.place.category.image"
129139
class="absolute left-0 top-1/2 -translate-y-1/2 w-9 rounded-full object-cover print:hidden" /> {{
130140
tripitem.place.name }}
131141
</div>
132142
} @else {-}
133-
</td>
134-
<td class="max-w-20 truncate print:whitespace-pre-line">{{ tripitem.comment || '-' }}</td>
135-
<td class="font-mono text-sm">
136-
<div class="max-w-20 print:max-w-full truncate">
143+
</td>}
144+
@if (tripTableSelectedColumns.includes('comment')) {<td>
145+
<div class="line-clamp-1 whitespace-pre-line print:line-clamp-none">
146+
{{ tripitem.comment || '-' }}
147+
</div>
148+
</td>}
149+
@if (tripTableSelectedColumns.includes('LatLng')) {<td class="font-mono text-sm">
150+
<div class="print:max-w-full truncate">
137151
@if (tripitem.lat) { {{ tripitem.lat }}, {{ tripitem.lng }} }
138152
@else {-}
139153
</div>
140-
</td>
141-
<td class="truncate">@if (tripitem.price) {<span
154+
</td>}
155+
@if (tripTableSelectedColumns.includes('price')) {<td class="truncate">@if (tripitem.price) {<span
142156
class="bg-gray-100 text-gray-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded">{{
143-
tripitem.price }} {{ currency$ | async }}</span>}</td>
144-
<td class="truncate">@if (tripitem.status) {<span [style.background]="tripitem.status.color+'1A'"
145-
[style.color]="tripitem.status.color" class="text-xs font-medium me-2 px-2.5 py-0.5 rounded">{{
146-
tripitem.status.label }}</span>}</td>
157+
tripitem.price }} {{ currency$ | async }}</span>}</td>}
158+
@if (tripTableSelectedColumns.includes('status')) {<td class="truncate">@if (tripitem.status) {<span
159+
[style.background]="tripitem.status.color+'1A'" [style.color]="tripitem.status.color"
160+
class="text-xs font-medium me-2 px-2.5 py-0.5 rounded">{{
161+
tripitem.status.label }}</span>}</td>}
147162
</tr>
148163
</ng-template>
149164
}
150165
@else {
151166
<ng-template #body let-tripitem let-rowgroup="rowgroup" let-rowspan="rowspan">
152167
<tr class="h-12 cursor-pointer" [class.font-bold]="selectedItem?.id === tripitem.id"
153168
(click)="onRowClick(tripitem)">
154-
@if (rowgroup) {
169+
@if (tripTableSelectedColumns.includes('day') && rowgroup) {
155170
<td [attr.rowspan]="rowspan" class="font-normal! max-w-20 truncate cursor-pointer"
156171
[class.text-blue-500]="tripMapAntLayerDayID == tripitem.day_id"
157172
(click)="toggleTripDayHighlightPathDay(tripitem.day_id); $event.stopPropagation()">
158173
<div class="truncate">{{tripitem.td_label }}</div>
159174
</td>
160175
}
161-
<td class="font-mono text-sm">{{ tripitem.time }}</td>
162-
<td class="relative max-w-60 truncate">
163-
<div class="relative">
164-
{{ tripitem.text }}
165-
@if (tripitem.status) {<div class="block xl:hidden absolute top-0 -left-1.5 size-1.5 rounded-full"
176+
@if (tripTableSelectedColumns.includes('time')) {<td class="font-mono text-sm">{{ tripitem.time }}</td>}
177+
@if (tripTableSelectedColumns.includes('text')) {<td class="relative max-w-60">
178+
<div class="truncate">
179+
@if (tripitem.status) {<div class="block absolute top-3 left-1.5 size-2 rounded-full"
166180
[style.background]="tripitem.status.color"></div>}
181+
{{ tripitem.text }}
167182
</div>
168-
</td>
169-
<td class="relative">
183+
</td>}
184+
@if (tripTableSelectedColumns.includes('place')) {<td class="relative">
170185
@if (tripitem.place) {
171186
<div class="ml-7 print:ml-0 max-w-24 truncate print:whitespace-normal">
172187
<img [src]="tripitem.place.image || tripitem.place.category.image"
173188
class="absolute left-0 top-1/2 -translate-y-1/2 w-9 rounded-full object-cover print:hidden" /> {{
174189
tripitem.place.name }}
175190
</div>
176191
} @else {-}
177-
</td>
178-
<td class="max-w-20 truncate print:whitespace-pre-line">{{ tripitem.comment || '-' }}</td>
179-
<td class="font-mono text-sm">
192+
</td>}
193+
@if (tripTableSelectedColumns.includes('comment')) {<td>
194+
<div class="line-clamp-1 whitespace-pre-line print:line-clamp-none">
195+
{{ tripitem.comment || '-' }}
196+
</div>
197+
</td>}
198+
@if (tripTableSelectedColumns.includes('LatLng')) {<td class="font-mono text-sm">
180199
<div class="max-w-20 print:max-w-full truncate">
181200
@if (tripitem.lat) { {{ tripitem.lat }}, {{ tripitem.lng }} }
182201
@else {-}
183202
</div>
184-
</td>
185-
<td class="truncate">@if (tripitem.price) {<span
203+
</td>}
204+
@if (tripTableSelectedColumns.includes('price')) {<td class="truncate">@if (tripitem.price) {<span
186205
class="bg-gray-100 text-gray-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded">{{
187-
tripitem.price }} {{ currency$ | async }}</span>}</td>
188-
<td class="truncate">@if (tripitem.status) {<span [style.background]="tripitem.status.color+'1A'"
189-
[style.color]="tripitem.status.color" class="text-xs font-medium me-2 px-2.5 py-0.5 rounded">{{
190-
tripitem.status.label }}</span>}</td>
206+
tripitem.price }} {{ currency$ | async }}</span>}</td>}
207+
@if (tripTableSelectedColumns.includes('status')) {<td class="truncate">@if (tripitem.status) {<span
208+
[style.background]="tripitem.status.color+'1A'" [style.color]="tripitem.status.color"
209+
class="text-xs font-medium me-2 px-2.5 py-0.5 rounded">{{
210+
tripitem.status.label }}</span>}</td>}
191211
</tr>
192212
</ng-template>
193213
}

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

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AfterViewInit, Component } from "@angular/core";
22
import { ApiService } from "../../services/api.service";
3-
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
3+
import { FormControl, FormsModule, ReactiveFormsModule } from "@angular/forms";
44
import { ButtonModule } from "primeng/button";
55
import { InputTextModule } from "primeng/inputtext";
66
import { SkeletonModule } from "primeng/skeleton";
@@ -30,6 +30,7 @@ import { TripCreateDayItemModalComponent } from "../../modals/trip-create-day-it
3030
import { TripCreateItemsModalComponent } from "../../modals/trip-create-items-modal/trip-create-items-modal.component";
3131
import {
3232
combineLatest,
33+
debounceTime,
3334
forkJoin,
3435
Observable,
3536
of,
@@ -48,16 +49,19 @@ import { PlaceCreateModalComponent } from "../../modals/place-create-modal/place
4849
import { Settings } from "../../types/settings";
4950
import { DialogModule } from "primeng/dialog";
5051
import { ClipboardModule } from "@angular/cdk/clipboard";
52+
import { TooltipModule } from "primeng/tooltip";
53+
import { MultiSelectModule } from "primeng/multiselect";
54+
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
5155

5256
@Component({
5357
selector: "app-trip",
5458
standalone: true,
5559
imports: [
5660
CommonModule,
61+
ReactiveFormsModule,
5762
FormsModule,
5863
SkeletonModule,
5964
MenuModule,
60-
ReactiveFormsModule,
6165
InputTextModule,
6266
AsyncPipe,
6367
LinkifyPipe,
@@ -66,7 +70,9 @@ import { ClipboardModule } from "@angular/cdk/clipboard";
6670
ButtonModule,
6771
DecimalPipe,
6872
DialogModule,
73+
TooltipModule,
6974
ClipboardModule,
75+
MultiSelectModule,
7076
],
7177
templateUrl: "./trip.component.html",
7278
styleUrls: ["./trip.component.scss"],
@@ -88,6 +94,7 @@ export class TripComponent implements AfterViewInit {
8894
collapsedTripStatuses = false;
8995
shareDialogVisible = false;
9096
isExpanded = false;
97+
isFilteringMode = false;
9198

9299
map?: L.Map;
93100
markerClusterGroup?: L.MarkerClusterGroup;
@@ -152,6 +159,13 @@ export class TripComponent implements AfterViewInit {
152159
this.tripToNavigation();
153160
},
154161
},
162+
{
163+
label: "Filter",
164+
icon: "pi pi-filter",
165+
command: () => {
166+
this.toggleFiltering();
167+
},
168+
},
155169
{
156170
label: "Expand / Group",
157171
icon: "pi pi-arrow-down-left-and-arrow-up-right-to-center",
@@ -201,6 +215,24 @@ export class TripComponent implements AfterViewInit {
201215
],
202216
},
203217
];
218+
readonly tripTableColumns: string[] = [
219+
"day",
220+
"time",
221+
"text",
222+
"place",
223+
"comment",
224+
"LatLng",
225+
"price",
226+
"status",
227+
];
228+
tripTableSelectedColumns: string[] = [
229+
"day",
230+
"time",
231+
"text",
232+
"place",
233+
"comment",
234+
];
235+
tripTableSearchInput = new FormControl("");
204236
selectedTripDayForMenu?: TripDay;
205237

206238
dayStatsCache = new Map<number, { price: number; places: number }>();
@@ -215,6 +247,14 @@ export class TripComponent implements AfterViewInit {
215247
) {
216248
this.currency$ = this.utilsService.currency$;
217249
this.statuses = this.utilsService.statuses;
250+
this.tripTableSearchInput.valueChanges
251+
.pipe(takeUntilDestroyed(), debounceTime(300))
252+
.subscribe({
253+
next: (value) => {
254+
if (value) this.flattenTripDayItems(value.toLowerCase());
255+
else this.flattenTripDayItems();
256+
},
257+
});
218258
}
219259

220260
ngAfterViewInit(): void {
@@ -284,6 +324,11 @@ export class TripComponent implements AfterViewInit {
284324
this.trip?.days.sort((a, b) => a.label.localeCompare(b.label));
285325
}
286326

327+
toggleFiltering() {
328+
this.isFilteringMode = !this.isFilteringMode;
329+
if (!this.isFilteringMode) this.flattenTripDayItems();
330+
}
331+
287332
getDayStats(day: TripDay): { price: number; places: number } {
288333
if (this.dayStatsCache.has(day.id)) return this.dayStatsCache.get(day.id)!;
289334

@@ -323,10 +368,17 @@ export class TripComponent implements AfterViewInit {
323368
return this.statuses.find((s) => s.label == status);
324369
}
325370

326-
flattenTripDayItems() {
371+
flattenTripDayItems(searchValue?: string) {
327372
this.sortTripDays();
328373
this.flattenedTripItems = this.trip!.days.flatMap((day) =>
329374
[...day.items]
375+
.filter((item) =>
376+
searchValue
377+
? item.text.toLowerCase().includes(searchValue) ||
378+
item.place?.name.toLowerCase().includes(searchValue) ||
379+
item.comment?.toLowerCase().includes(searchValue)
380+
: true,
381+
)
330382
.sort((a, b) => a.time.localeCompare(b.time))
331383
.map((item) => ({
332384
td_id: day.id,

0 commit comments

Comments
 (0)