Dark mode

This commit is contained in:
itskovacs 2025-07-25 18:33:39 +02:00
parent 6997e765db
commit 6128678678
8 changed files with 128 additions and 73 deletions

View File

@ -1 +1 @@
__version__ = "1.7.3" __version__ = "1.8.0"

View File

@ -1,2 +1,2 @@
<router-outlet></router-outlet> <router-outlet></router-outlet>
<p-toast [preventDuplicates]="true" /> <p-toast [preventOpenDuplicates]="true" />

View File

@ -6,16 +6,17 @@
(closeEmitter)="closePlaceBox()"></app-place-box> (closeEmitter)="closePlaceBox()"></app-place-box>
} }
<div class="absolute z-30 top-2 right-2 p-2 bg-white shadow rounded"> <div class="absolute z-30 top-2 right-2 p-2 bg-white shadow rounded dark:bg-surface-900">
<p-button (click)="toggleMarkersList()" text severity="secondary" icon="pi pi-map-marker" /> <p-button (click)="toggleMarkersList()" text severity="secondary" icon="pi pi-map-marker" />
</div> </div>
<div class="absolute z-30 top-20 right-2 p-2 bg-white shadow rounded"> <div class="absolute z-30 top-20 right-2 p-2 bg-white shadow rounded dark:bg-surface-900">
<p-button (click)="toggleFilters()" text [severity]="viewFilters ? 'danger' : 'secondary'" <p-button (click)="toggleFilters()" text [severity]="viewFilters ? 'danger' : 'secondary'"
[icon]="viewFilters ? 'pi pi-times' : 'pi pi-filter'" /> [icon]="viewFilters ? 'pi pi-times' : 'pi pi-filter'" />
</div> </div>
<div [class.z-50]="viewSettings" class="absolute z-30 top-[9.5rem] right-2 p-2 bg-white shadow rounded"> <div [class.z-50]="viewSettings"
class="absolute z-30 top-[9.5rem] right-2 p-2 bg-white shadow rounded dark:bg-surface-900">
<p-button (click)="toggleSettings()" text [severity]="viewSettings ? 'danger' : 'secondary'" <p-button (click)="toggleSettings()" text [severity]="viewSettings ? 'danger' : 'secondary'"
[icon]="viewSettings ? 'pi pi-times' : 'pi pi-cog'" /> [icon]="viewSettings ? 'pi pi-times' : 'pi pi-cog'" />
</div> </div>
@ -23,23 +24,23 @@
<div class="absolute z-30 bottom-4 right-2"> <div class="absolute z-30 bottom-4 right-2">
<div class="relative group flex flex-col-reverse items-end h-28"> <div class="relative group flex flex-col-reverse items-end h-28">
<div <div
class="absolute right-0 bottom-20 p-2 bg-white shadow rounded transition-all duration-200 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto"> class="absolute right-0 bottom-20 p-2 bg-white shadow rounded transition-all duration-200 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto dark:bg-surface-900">
<p-button (click)="batchAddModal()" text severity="secondary" icon="pi pi-ellipsis-v" /> <p-button (click)="batchAddModal()" text severity="secondary" icon="pi pi-ellipsis-v" />
</div> </div>
<div class="p-2 bg-white shadow rounded"> <div class="p-2 bg-white shadow rounded dark:bg-surface-900">
<p-button (click)="addPlaceModal()" text severity="secondary" icon="pi pi-plus" /> <p-button (click)="addPlaceModal()" text severity="secondary" icon="pi pi-plus" />
</div> </div>
</div> </div>
</div> </div>
<div class="absolute z-30 top-2 left-2 p-2 bg-white shadow rounded"> <div class="absolute z-30 top-2 left-2 p-2 bg-white shadow rounded dark:bg-surface-900">
<p-button (click)="gotoTrips()" label="Trips" severity="secondary" text icon="pi pi-bars" /> <p-button (click)="gotoTrips()" label="Trips" severity="secondary" text icon="pi pi-bars" />
</div> </div>
@if (viewMarkersList) { @if (viewMarkersList) {
<section <section
class="absolute left-2 right-2 top-4 bottom-4 md:max-w-md bg-white z-40 rounded-xl shadow-2xl p-4 flex flex-col"> class="absolute left-2 right-2 top-4 bottom-4 md:max-w-md bg-white z-40 rounded-xl shadow-2xl p-4 flex flex-col dark:bg-surface-900">
<div class="mt-1 p-4 flex items-center justify-between"> <div class="mt-1 p-4 flex items-center justify-between">
<div> <div>
<h1 class="font-semibold tracking-tight text-xl">Points</h1> <h1 class="font-semibold tracking-tight text-xl">Points</h1>
@ -63,31 +64,32 @@
} }
@for (p of visiblePlaces; track p.id) { @for (p of visiblePlaces; track p.id) {
<div class="mt-4 flex items-center gap-4 hover:bg-gray-50 rounded-xl cursor-pointer py-2 px-4" <div
class="mt-4 flex items-center gap-4 hover:bg-gray-50 rounded-xl cursor-pointer py-2 px-4 dark:hover:bg-gray-800"
(click)="gotoPlace(p)" (mouseenter)="hoverPlace(p)" (mouseleave)="resetHoverPlace()"> (click)="gotoPlace(p)" (mouseenter)="hoverPlace(p)" (mouseleave)="resetHoverPlace()">
<img [src]="p.image || p.category.image" class="w-12 rounded-full object-fit"> <img [src]="p.image || p.category.image" class="w-12 rounded-full object-fit">
<div class="flex flex-col gap-1 truncate"> <div class="flex flex-col gap-1 truncate">
<h1 class="tracking-tight truncate">{{ p.name }}</h1> <h1 class="tracking-tight truncate dark:text-surface-300">{{ p.name }}</h1>
<span class="text-xs text-gray-500 truncate">{{ p.place }}</span> <span class="text-xs text-gray-500 truncate">{{ p.place }}</span>
<div class="flex gap-0.5"> <div class="flex gap-0.5">
@if (p.allowdog) { @if (p.allowdog) {
<span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded">🐶</span> <span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-green-100/85">🐶</span>
} @else { } @else {
<span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded">🐶</span> <span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-red-100/85">🐶</span>
} }
@if (p.visited) { @if (p.visited) {
<span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded"><i <span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-green-100/85"><i
class="pi pi-eye text-xs"></i></span> class="pi pi-eye text-xs"></i></span>
} @else { } @else {
<span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded"><i <span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-red-100/85"><i
class="pi pi-eye-slash text-xs"></i></span> class="pi pi-eye-slash text-xs"></i></span>
} }
<span <span
class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded flex gap-2 items-center truncate"><i class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded flex gap-2 items-center truncate dark:bg-blue-100/85"><i
class="pi pi-box text-xs"></i>{{ p.category.name }}</span> class="pi pi-box text-xs"></i>{{ p.category.name }}</span>
</div> </div>
</div> </div>
@ -103,7 +105,8 @@
} }
@if (viewFilters) { @if (viewFilters) {
<section class="absolute right-2 top-36 bg-white z-40 rounded-xl shadow-2xl p-8 max-w-screen md:max-w-md"> <section
class="absolute right-2 top-36 bg-white z-40 rounded-xl shadow-2xl p-8 max-w-screen md:max-w-md dark:bg-surface-900">
<div class="mt-1 flex justify-between items-center"> <div class="mt-1 flex justify-between items-center">
<div> <div>
@ -149,7 +152,8 @@
@if (viewSettings) { @if (viewSettings) {
<section class="absolute inset-0 flex items-center justify-center z-40 bg-black/30"> <section class="absolute inset-0 flex items-center justify-center z-40 bg-black/30">
<div class="w-10/12 max-w-screen md:max-w-3xl h-fit max-h-screen bg-white rounded-xl shadow-2xl p-8 z-50"> <div
class="w-10/12 max-w-screen md:max-w-3xl h-fit max-h-screen bg-white rounded-xl shadow-2xl p-8 z-50 dark:bg-surface-900">
<p-tabs value="0" scrollable> <p-tabs value="0" scrollable>
<p-tablist> <p-tablist>
<p-tab value="0" class="flex items-center gap-2"> <p-tab value="0" class="flex items-center gap-2">
@ -169,7 +173,8 @@
<p-tabpanel value="0"> <p-tabpanel value="0">
<div class="mt-4"> <div class="mt-4">
<h1 class="font-semibold tracking-tight text-xl">Low Network Mode</h1> <h1 class="font-semibold tracking-tight text-xl">Low Network Mode</h1>
<span class="text-xs text-gray-500">You can disable Low Network Mode. Default is true. Display Category <span class="text-xs text-gray-500 dark:text-gray-400">You can disable Low Network Mode. Default is true.
Display Category
image instead of Place image.</span> image instead of Place image.</span>
</div> </div>
<div class="mt-4 flex justify-between"> <div class="mt-4 flex justify-between">
@ -177,11 +182,20 @@
<p-toggleswitch [(ngModel)]="isLowNet" (onChange)="toggleLowNet()" /> <p-toggleswitch [(ngModel)]="isLowNet" (onChange)="toggleLowNet()" />
</div> </div>
<div class="mt-4">
<h1 class="font-semibold tracking-tight text-xl">Dark Mode</h1>
</div>
<div class="mt-4 flex justify-between">
<div>Enable Dark mode</div>
<p-toggleswitch [(ngModel)]="isDarkMode" (onChange)="toggleDarkMode()" />
</div>
<section [formGroup]="settingsForm"> <section [formGroup]="settingsForm">
<div class="mt-4 flex justify-between items-center"> <div class="mt-4 flex justify-between items-center">
<div> <div>
<h1 class="font-semibold tracking-tight text-xl">Map parameters</h1> <h1 class="font-semibold tracking-tight text-xl">Map parameters</h1>
<span class="text-xs text-gray-500">You can customize the default view on map loading</span> <span class="text-xs text-gray-500 dark:text-gray-400">You can customize the default view on map
loading</span>
</div> </div>
<p-button icon="pi pi-ethereum" pTooltip="Set current map center as default" <p-button icon="pi pi-ethereum" pTooltip="Set current map center as default"
@ -213,7 +227,8 @@
<div class="mt-4"> <div class="mt-4">
<h1 class="font-semibold tracking-tight text-xl">Filters</h1> <h1 class="font-semibold tracking-tight text-xl">Filters</h1>
<span class="text-xs text-gray-500">You can customize the categories to hide by default</span> <span class="text-xs text-gray-500 dark:text-gray-400">You can customize the categories to hide by
default</span>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<p-floatlabel variant="in" class="md:col-span-2"> <p-floatlabel variant="in" class="md:col-span-2">
@ -234,7 +249,7 @@
<div class="mt-1 p-2 mb-2 flex justify-between items-center"> <div class="mt-1 p-2 mb-2 flex justify-between items-center">
<div> <div>
<h1 class="font-semibold tracking-tight text-xl">Categories</h1> <h1 class="font-semibold tracking-tight text-xl">Categories</h1>
<span class="text-xs text-gray-500">You can modify the categories.</span> <span class="text-xs text-gray-500 dark:text-gray-400">You can modify the categories.</span>
<span class="ml-1 text-xs text-orange-500">You cannot delete a used category.</span> <span class="ml-1 text-xs text-orange-500">You cannot delete a used category.</span>
</div> </div>
@ -243,7 +258,7 @@
<div class="mt-4 flex flex-col"> <div class="mt-4 flex flex-col">
@for (category of categories; track category.id) { @for (category of categories; track category.id) {
<div class="p-3 flex items-center justify-between rounded-md hover:bg-gray-50"> <div class="p-3 flex items-center justify-between rounded-md hover:bg-gray-50 dark:hover:bg-gray-800">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<img [src]="category.image" class="size-8 rounded-full" />{{ category.name }} <img [src]="category.image" class="size-8 rounded-full" />{{ category.name }}
</div> </div>
@ -297,7 +312,7 @@
} }
</div> </div>
<div class="mt-4 text-center text-sm text-gray-500">Made with ❤️ in BZH</div> <div class="mt-4 text-center text-sm text-gray-500 dark:text-gray-400">Made with ❤️ in BZH</div>
</p-tabpanel> </p-tabpanel>
</p-tabpanels> </p-tabpanels>
</p-tabs> </p-tabs>

View File

@ -79,6 +79,7 @@ export class DashboardComponent implements AfterViewInit {
searchInput = new FormControl(""); searchInput = new FormControl("");
info: Info | undefined; info: Info | undefined;
isLowNet: boolean = false; isLowNet: boolean = false;
isDarkMode: boolean = false;
viewSettings = false; viewSettings = false;
viewFilters = false; viewFilters = false;
@ -113,6 +114,7 @@ export class DashboardComponent implements AfterViewInit {
) { ) {
this.currencySigns = this.utilsService.currencySigns(); this.currencySigns = this.utilsService.currencySigns();
this.isLowNet = this.utilsService.isLowNet; this.isLowNet = this.utilsService.isLowNet;
this.isDarkMode = this.utilsService.isDarkMode;
this.settingsForm = this.fb.group({ this.settingsForm = this.fb.group({
mapLat: [ mapLat: [
@ -253,6 +255,10 @@ export class DashboardComponent implements AfterViewInit {
}, 200); }, 200);
} }
toggleDarkMode() {
this.utilsService.toggleDarkMode();
}
get filteredPlaces(): Place[] { get filteredPlaces(): Place[] {
return this.places.filter((p) => { return this.places.filter((p) => {
if (!this.filter_display_visited && p.visited) return false; if (!this.filter_display_visited && p.visited) return false;
@ -571,6 +577,8 @@ export class DashboardComponent implements AfterViewInit {
} }
toggleMarkersList() { toggleMarkersList() {
this.searchInput.setValue("");
this.viewMarkersListSearch = false;
this.viewMarkersList = !this.viewMarkersList; this.viewMarkersList = !this.viewMarkersList;
if (this.viewMarkersList) this.setVisibleMarkers(); if (this.viewMarkersList) this.setVisibleMarkers();
} }

View File

@ -27,7 +27,8 @@
</div> </div>
} }
<span class="bg-gray-100 text-gray-800 text-xs md:text-sm font-medium me-2 px-2.5 py-0.5 rounded min-w-fit">{{ <span
class="bg-gray-100 text-gray-800 text-xs md:text-sm font-medium me-2 px-2.5 py-0.5 rounded min-w-fit dark:bg-gray-400">{{
totalPrice totalPrice
|| '-' }} {{ currency$ | async }}</span> || '-' }} {{ currency$ | async }}</span>
</div> </div>
@ -45,8 +46,9 @@
} }
<section class="p-4 print:px-1 grid md:grid-cols-3 gap-4 print:block"> <section class="p-4 print:px-1 grid md:grid-cols-3 gap-4 print:block">
<div class="p-4 shadow self-start rounded-md md:col-span-2 max-w-screen print:col-span-full">
<div [class.sticky]="!isMapFullscreen" <div [class.sticky]="!isMapFullscreen"
class="top-0 z-10 bg-white p-2 mb-2 flex justify-between items-center"> class="top-0 z-10 bg-white p-2 mb-2 flex justify-between items-center dark:bg-surface-900">
<div> <div>
<h1 class="font-semibold tracking-tight text-xl">Plans</h1> <h1 class="font-semibold tracking-tight text-xl">Plans</h1>
<span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} plans</span> <span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} plans</span>
@ -99,7 +101,7 @@
</div> </div>
} @else {-} } @else {-}
</td> </td>
<td class="max-w-20 truncate print:whitespace-normal">{{ tripitem.comment || '-' }}</td> <td class="max-w-20 truncate print:whitespace-pre-line">{{ tripitem.comment || '-' }}</td>
<td class="font-mono text-sm"> <td class="font-mono text-sm">
<div class="max-w-20 print:max-w-full truncate"> <div class="max-w-20 print:max-w-full truncate">
@if (tripitem.lat) { {{ tripitem.lat }}, {{ tripitem.lng }} } @if (tripitem.lat) { {{ tripitem.lat }}, {{ tripitem.lng }} }
@ -231,7 +233,7 @@
</div> </div>
</div> </div>
<div id="map" [class.fullscreen-map]="isMapFullscreen" class="w-full rounded-md min-h-96 h-1/3 max-h-full z-20"> <div id="map" [class.fullscreen-map]="isMapFullscreen" class="w-full rounded-md min-h-96 h-1/3 max-h-full">
</div> </div>
</div> </div>
@ -242,7 +244,8 @@
<h1 class="font-semibold tracking-tight text-xl">Places</h1> <h1 class="font-semibold tracking-tight text-xl">Places</h1>
<span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} places</span> <span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} places</span>
<div class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x"> <div
class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x dark:bg-surface-900">
<p-button [icon]="collapsedTripPlaces ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text <p-button [icon]="collapsedTripPlaces ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text
(click)="collapsedTripPlaces = !collapsedTripPlaces" /> (click)="collapsedTripPlaces = !collapsedTripPlaces" />
</div> </div>
@ -250,7 +253,8 @@
<div class="flex items-center"> <div class="flex items-center">
@defer { @defer {
<span class="bg-blue-100 text-blue-800 text-sm me-2 px-2.5 py-0.5 rounded-md">{{ places.length }}</span> <span class="bg-blue-100 text-blue-800 text-sm me-2 px-2.5 py-0.5 rounded-md dark:bg-blue-100/85">{{
places.length }}</span>
} @placeholder (minimum 0.4s) { } @placeholder (minimum 0.4s) {
<p-skeleton height="1.75rem" width="2.5rem" class="mr-1" /> <p-skeleton height="1.75rem" width="2.5rem" class="mr-1" />
} }
@ -262,28 +266,30 @@
<div class="max-h-[25vh] overflow-y-auto"> <div class="max-h-[25vh] overflow-y-auto">
@defer { @defer {
@for (p of places; track p.id) { @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" <div class="flex items-center gap-4 py-2 px-4 hover:bg-gray-50 rounded-md overflow-auto dark:hover:bg-gray-800"
(mouseenter)="placeHighlightMarker(p.lat, p.lng)" (mouseleave)="resetPlaceHighlightMarker()"> (mouseenter)="placeHighlightMarker(p.lat, p.lng)" (mouseleave)="resetPlaceHighlightMarker()">
<img [src]="p.image || p.category.image" class="w-12 rounded-full object-fit"> <img [src]="p.image || p.category.image" class="w-12 rounded-full object-fit">
<div class="flex flex-col gap-1 truncate"> <div class="flex flex-col gap-1 truncate">
<h1 class="tracking-tight truncate">{{ p.name }}</h1> <h1 class="tracking-tight truncate dark:text-surface-300">{{ p.name }}</h1>
<span class="text-xs text-gray-500 truncate">{{ p.place }}</span> <span class="text-xs text-gray-500 truncate">{{ p.place }}</span>
<div class="flex gap-0.5"> <div class="flex gap-0.5">
<span <span
class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded flex gap-2 items-center truncate"><i class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded flex gap-2 items-center truncate dark:bg-blue-100/85"><i
class="pi pi-box text-xs"></i>{{ p.category.name }}</span> class="pi pi-box text-xs"></i>{{ p.category.name }}</span>
@if (isPlaceUsed(p.id)) { @if (isPlaceUsed(p.id)) {
<span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded"><i <span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-green-100/85"><i
class="pi pi-check-square text-xs"></i></span> class="pi pi-check-square text-xs"></i></span>
} @else { } @else {
<span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded"><i <span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-red-100/85"><i
class="pi pi-map-marker text-xs"></i></span> class="pi pi-map-marker text-xs"></i></span>
} }
<span class="bg-gray-100 text-gray-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded">{{ p.price || '-' <span
class="bg-gray-100 text-gray-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-100/85">{{
p.price || '-'
}} {{ currency$ | async }}</span> }} {{ currency$ | async }}</span>
</div> </div>
@ -311,7 +317,8 @@
<h1 class="font-semibold tracking-tight text-xl">Days</h1> <h1 class="font-semibold tracking-tight text-xl">Days</h1>
<span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} days</span> <span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} days</span>
<div class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x"> <div
class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x dark:bg-surface-900">
<p-button [icon]="collapsedTripDays ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text <p-button [icon]="collapsedTripDays ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text
(click)="collapsedTripDays = !collapsedTripDays" /> (click)="collapsedTripDays = !collapsedTripDays" />
</div> </div>
@ -325,14 +332,16 @@
@defer { @defer {
@for (d of trip?.days; track d.id) { @for (d of trip?.days; track d.id) {
<div <div
class="group flex items-center gap-4 rounded-md justify-between h-10 px-4 py-2 hover:bg-gray-50 w-full max-w-full"> class="group flex items-center gap-4 rounded-md justify-between h-10 px-4 py-2 hover:bg-gray-50 w-full max-w-full dark:hover:bg-gray-800">
<div class="line-clamp-1"> <div class="line-clamp-1 dark:text-surface-300">
{{ d.label }} {{ d.label }}
</div> </div>
<div class="flex items-center gap-2 flex-none"> <div class="flex items-center gap-2 flex-none">
<span class="bg-gray-100 text-gray-800 text-sm px-2.5 py-0.5 rounded-md min-w-fit group-hover:hidden">{{ <span
class="bg-gray-100 text-gray-800 text-sm px-2.5 py-0.5 rounded-md min-w-fit group-hover:hidden dark:bg-gray-100/85">{{
getDayStats(d).price || '-' }} {{ currency$ | async }}</span> getDayStats(d).price || '-' }} {{ currency$ | async }}</span>
<span class="bg-blue-100 text-blue-800 text-sm px-2.5 py-0.5 rounded-md group-hover:hidden">{{ <span
class="bg-blue-100 text-blue-800 text-sm px-2.5 py-0.5 rounded-md group-hover:hidden dark:bg-blue-100/85">{{
getDayStats(d).places }}</span> getDayStats(d).places }}</span>
<div class="flex md:hidden"> <div class="flex md:hidden">
@ -364,7 +373,8 @@
<h1 class="font-semibold tracking-tight text-xl">Watchlist</h1> <h1 class="font-semibold tracking-tight text-xl">Watchlist</h1>
<span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} pending/constraints</span> <span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} pending/constraints</span>
<div class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x"> <div
class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x dark:bg-surface-900">
<p-button [icon]="collapsedTripStatuses ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text <p-button [icon]="collapsedTripStatuses ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text
(click)="collapsedTripStatuses = !collapsedTripStatuses" /> (click)="collapsedTripStatuses = !collapsedTripStatuses" />
</div> </div>
@ -401,12 +411,12 @@
<p-menu #menuTripDayActions [model]="menuTripDayActionsItems" appendTo="body" [popup]="true" /> <p-menu #menuTripDayActions [model]="menuTripDayActionsItems" appendTo="body" [popup]="true" />
@if (isMapFullscreen) { @if (isMapFullscreen) {
<div class="absolute z-30 top-2 right-2 p-2 bg-white shadow rounded"> <div class="fixed top-2 right-2 p-2 bg-white shadow rounded dark:bg-surface-900">
<p-button (click)="toggleMapFullscreen()" text icon="pi pi-window-minimize" /> <p-button (click)="toggleMapFullscreen()" severity="secondary" text icon="pi pi-window-minimize" />
</div> </div>
<div class="absolute z-30 top-20 right-2 p-2 bg-white shadow rounded"> <div class="fixed top-20 right-2 p-2 bg-white shadow rounded dark:bg-surface-900">
<p-button (click)="toggleTripDaysHighlight()" text icon="pi pi-directions" <p-button (click)="toggleTripDaysHighlight()" text icon="pi pi-directions"
[severity]="tripMapAntLayerDayID == -1 ? 'help' : 'primary'" /> [severity]="tripMapAntLayerDayID == -1 ? 'help' : 'secondary'" />
</div> </div>
} }

View File

@ -11,7 +11,8 @@
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h1 class="font-semibold tracking-tight text-xl">Selected</h1> <h1 class="font-semibold tracking-tight text-xl">Selected</h1>
<span class="bg-blue-100 text-blue-800 text-sm me-2 px-2.5 py-0.5 rounded"> {{ selectedPlaces.length }}</span> <span class="bg-blue-100 text-blue-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-blue-100/85"> {{
selectedPlaces.length }}</span>
</div> </div>
<span class="text-xs text-gray-500">Here are your selected place{{ selectedPlaces.length > 1 ? 's' : '' <span class="text-xs text-gray-500">Here are your selected place{{ selectedPlaces.length > 1 ? 's' : ''
}}</span> }}</span>
@ -22,31 +23,32 @@
</div> </div>
@if (showSelectedPlaces) { @if (showSelectedPlaces) {
@for (p of selectedPlaces; track p.id) { @for (p of selectedPlaces; track p.id) {
<div class="mt-4 flex items-center gap-4 hover:bg-gray-50 rounded-xl cursor-pointer py-2 px-4" <div
class="mt-4 flex items-center gap-4 hover:bg-gray-50 rounded-xl cursor-pointer py-2 px-4 dark:hover:bg-gray-800"
(click)="togglePlace(p)"> (click)="togglePlace(p)">
<img [src]="p.image || p.category.image" class="w-12 rounded-full object-fit"> <img [src]="p.image || p.category.image" class="w-12 rounded-full object-fit">
<div class="flex flex-col gap-1 truncate"> <div class="flex flex-col gap-1 truncate">
<h1 class="tracking-tight truncate">{{ p.name }}</h1> <h1 class="tracking-tight truncate dark:text-surface-300">{{ p.name }}</h1>
<span class="text-xs text-gray-500 truncate">{{ p.place }}</span> <span class="text-xs text-gray-500 truncate">{{ p.place }}</span>
<div class="flex gap-0.5"> <div class="flex gap-0.5">
@if (p.allowdog) { @if (p.allowdog) {
<span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded">🐶</span> <span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-green-100/85">🐶</span>
} @else { } @else {
<span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded">🐶</span> <span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-red-100/85">🐶</span>
} }
@if (p.visited) { @if (p.visited) {
<span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded"><i <span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-green-100/85"><i
class="pi pi-eye text-xs"></i></span> class="pi pi-eye text-xs"></i></span>
} @else { } @else {
<span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded"><i <span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-red-100/85"><i
class="pi pi-eye-slash text-xs"></i></span> class="pi pi-eye-slash text-xs"></i></span>
} }
<span <span
class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded flex gap-2 items-center truncate"><i class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded flex gap-2 items-center truncate dark:bg-blue-100/85"><i
class="pi pi-box text-xs"></i>{{ p.category.name }}</span> class="pi pi-box text-xs"></i>{{ p.category.name }}</span>
</div> </div>
</div> </div>
@ -60,29 +62,30 @@
@defer { @defer {
@for (p of displayedPlaces; track p.id) { @for (p of displayedPlaces; track p.id) {
<div class="mt-4 flex items-center gap-4 hover:bg-gray-50 rounded-xl cursor-pointer py-2 px-4" <div
class="mt-4 flex items-center gap-4 hover:bg-gray-50 rounded-xl cursor-pointer py-2 px-4 dark:hover:bg-gray-800"
[class.font-bold]="selectedPlacesID.includes(p.id)" (click)="togglePlace(p)"> [class.font-bold]="selectedPlacesID.includes(p.id)" (click)="togglePlace(p)">
<div class="flex flex-col gap-1 truncate"> <div class="flex flex-col gap-1 truncate">
<h1 class="tracking-tight truncate">{{ p.name }}</h1> <h1 class="tracking-tight truncate dark:text-surface-300">{{ p.name }}</h1>
<span class="text-xs text-gray-500 truncate">{{ p.place }}</span> <span class="text-xs text-gray-500 truncate">{{ p.place }}</span>
<div class="flex gap-0.5"> <div class="flex gap-0.5">
@if (p.allowdog) { @if (p.allowdog) {
<span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded">🐶</span> <span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-green-100/85">🐶</span>
} @else { } @else {
<span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded">🐶</span> <span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-red-100/85">🐶</span>
} }
@if (p.visited) { @if (p.visited) {
<span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded"><i <span class="bg-green-100 text-green-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-green-100/85"><i
class="pi pi-eye text-xs"></i></span> class="pi pi-eye text-xs"></i></span>
} @else { } @else {
<span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded"><i <span class="bg-red-100 text-red-800 text-sm me-2 px-2.5 py-0.5 rounded dark:bg-red-100/85"><i
class="pi pi-eye-slash text-xs"></i></span> class="pi pi-eye-slash text-xs"></i></span>
} }
<span <span
class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded flex gap-2 items-center truncate"><i class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded flex gap-2 items-center truncate dark:bg-blue-100/85"><i
class="pi pi-box text-xs"></i>{{ p.category.name }}</span> class="pi pi-box text-xs"></i>{{ p.category.name }}</span>
</div> </div>
</div> </div>
@ -93,8 +96,8 @@
</div> </div>
} }
<div class="z-50 absolute w-full bg-white shadow p-4 bottom-0 left-0 text-center"> <div class="z-50 absolute w-full bg-white shadow p-4 bottom-0 left-0 text-center dark:bg-surface-900">
<p-button (click)="closeDialog()" label="Confirm" /> <p-button (click)="closeDialog()" label="Confirm" severity="secondary" />
</div> </div>
} @placeholder (minimum 0.4s) { } @placeholder (minimum 0.4s) {
<div class="my-2"> <div class="my-2">

View File

@ -5,6 +5,7 @@ import { ApiService } from "./api.service";
import { map } from "rxjs"; import { map } from "rxjs";
const DISABLE_LOWNET = "TRIP_DISABLE_LOWNET"; const DISABLE_LOWNET = "TRIP_DISABLE_LOWNET";
const DARK = "DARKMODE";
@Injectable({ @Injectable({
providedIn: "root", providedIn: "root",
@ -13,9 +14,12 @@ export class UtilsService {
private apiService = inject(ApiService); private apiService = inject(ApiService);
currency$ = this.apiService.settings$.pipe(map((s) => s?.currency ?? "€")); currency$ = this.apiService.settings$.pipe(map((s) => s?.currency ?? "€"));
public isLowNet: boolean = true; public isLowNet: boolean = true;
public isDarkMode: boolean = false;
constructor(private ngMessageService: MessageService) { constructor(private ngMessageService: MessageService) {
this.isLowNet = !localStorage.getItem(DISABLE_LOWNET); this.isLowNet = !localStorage.getItem(DISABLE_LOWNET);
this.isDarkMode = !!localStorage.getItem(DARK);
if (this.isDarkMode) this.renderDarkMode();
} }
toGithubTRIP() { toGithubTRIP() {
@ -31,6 +35,22 @@ export class UtilsService {
this.isLowNet = !this.isLowNet; this.isLowNet = !this.isLowNet;
} }
renderDarkMode() {
const element = document.querySelector("html");
element?.classList.toggle("dark");
}
toggleDarkMode() {
if (this.isDarkMode) {
localStorage.removeItem(DARK);
this.isDarkMode = false;
} else {
localStorage.setItem(DARK, "1");
this.isDarkMode = true;
}
this.renderDarkMode();
}
get statuses(): TripStatus[] { get statuses(): TripStatus[] {
return [ return [
{ label: "pending", color: "#3258A8" }, { label: "pending", color: "#3258A8" },

View File

@ -1,10 +1,9 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: [ darkMode: "selector",
"./src/**/*.{html,ts}", content: ["./src/**/*.{html,ts}"],
],
theme: { theme: {
extend: {}, extend: {},
}, },
plugins: [require('tailwindcss-primeui')] plugins: [require("tailwindcss-primeui")],
} };