From 42654fb9a10ac6a0ecc273f82f73253938a8784f Mon Sep 17 00:00:00 2001 From: itskovacs Date: Fri, 10 Oct 2025 18:02:26 +0200 Subject: [PATCH] :art: Prettier setup --- src/.prettierrc | 10 +- src/src/app/app.config.ts | 36 +- src/src/app/app.routes.ts | 48 +- .../app/components/auth/auth.component.scss | 2 +- src/src/app/components/auth/auth.component.ts | 57 +- .../dashboard/dashboard.component.ts | 414 ++++----- .../shared-trip/shared-trip.component.html | 347 +++++--- .../shared-trip/shared-trip.component.ts | 313 +++---- src/src/app/components/trip/trip.component.ts | 810 ++++++++---------- .../app/components/trips/trips.component.ts | 86 +- src/src/app/decs.d.ts | 2 +- .../batch-create-modal.component.ts | 20 +- .../category-create-modal.component.ts | 57 +- .../place-create-modal.component.ts | 140 ++- .../trip-archive-modal.component.ts | 40 +- .../trip-create-checklist-modal.component.ts | 35 +- .../trip-create-day-item-modal.component.ts | 148 ++-- .../trip-create-day-modal.component.ts | 36 +- .../trip-create-items-modal.component.ts | 36 +- .../trip-create-modal.component.ts | 68 +- .../trip-create-packing-modal.component.ts | 43 +- .../trip-invite-member-modal.component.ts | 30 +- .../trip-notes-modal.component.ts | 27 +- .../trip-place-select-modal.component.ts | 46 +- src/src/app/services/api.service.ts | 283 ++---- src/src/app/services/auth.guard.ts | 19 +- src/src/app/services/auth.service.ts | 135 ++- src/src/app/services/interceptor.service.ts | 77 +- src/src/app/services/utils.service.ts | 49 +- src/src/app/shared/haversine.ts | 7 +- src/src/app/shared/latlng-parser.ts | 20 +- src/src/app/shared/linkify.pipe.ts | 18 +- src/src/app/shared/map.ts | 68 +- src/src/app/shared/order-by.pipe.ts | 8 +- .../shared/place-box/place-box.component.ts | 63 +- .../shared/place-gpx/place-gpx.component.ts | 20 +- src/src/app/types/settings.ts | 2 +- src/src/app/types/trip.ts | 2 +- src/src/main.ts | 4 +- src/src/mytheme.ts | 12 +- src/src/styles.scss | 16 +- 41 files changed, 1551 insertions(+), 2103 deletions(-) diff --git a/src/.prettierrc b/src/.prettierrc index 9e26dfe..d8c626f 100644 --- a/src/.prettierrc +++ b/src/.prettierrc @@ -1 +1,9 @@ -{} \ No newline at end of file +{ + "printWidth": 120, + "singleQuote": true, + "trailingComma": "all", + "bracketSameLine": true, + "arrowParens": "always", + "semi": true, + "endOfLine": "lf" +} diff --git a/src/src/app/app.config.ts b/src/src/app/app.config.ts index c65b5cb..3878149 100644 --- a/src/src/app/app.config.ts +++ b/src/src/app/app.config.ts @@ -1,18 +1,14 @@ -import { - ApplicationConfig, - provideZoneChangeDetection, - isDevMode, -} from "@angular/core"; -import { provideAnimationsAsync } from "@angular/platform-browser/animations/async"; -import { provideRouter } from "@angular/router"; -import { routes } from "./app.routes"; -import { providePrimeNG } from "primeng/config"; -import { TripThemePreset } from "../mytheme"; -import { MessageService } from "primeng/api"; -import { provideHttpClient, withInterceptors } from "@angular/common/http"; -import { Interceptor } from "./services/interceptor.service"; -import { DialogService } from "primeng/dynamicdialog"; -import { provideServiceWorker } from "@angular/service-worker"; +import { ApplicationConfig, provideZoneChangeDetection, isDevMode } from '@angular/core'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; +import { provideRouter } from '@angular/router'; +import { routes } from './app.routes'; +import { providePrimeNG } from 'primeng/config'; +import { TripThemePreset } from '../mytheme'; +import { MessageService } from 'primeng/api'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { Interceptor } from './services/interceptor.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { provideServiceWorker } from '@angular/service-worker'; export const appConfig: ApplicationConfig = { providers: [ @@ -27,19 +23,19 @@ export const appConfig: ApplicationConfig = { theme: { preset: TripThemePreset, options: { - darkModeSelector: ".dark", + darkModeSelector: '.dark', cssLayer: { - name: "primeng", - order: "tailwind, primeng", + name: 'primeng', + order: 'tailwind, primeng', }, }, }, }), MessageService, DialogService, - provideServiceWorker("ngsw-worker.js", { + provideServiceWorker('ngsw-worker.js', { enabled: !isDevMode(), - registrationStrategy: "registerWhenStable:30000", + registrationStrategy: 'registerWhenStable:30000', }), ], }; diff --git a/src/src/app/app.routes.ts b/src/src/app/app.routes.ts index 091c4f8..badbc4f 100644 --- a/src/src/app/app.routes.ts +++ b/src/src/app/app.routes.ts @@ -1,62 +1,62 @@ -import { Routes } from "@angular/router"; +import { Routes } from '@angular/router'; -import { AuthComponent } from "./components/auth/auth.component"; +import { AuthComponent } from './components/auth/auth.component'; -import { DashboardComponent } from "./components/dashboard/dashboard.component"; -import { AuthGuard } from "./services/auth.guard"; -import { TripComponent } from "./components/trip/trip.component"; -import { TripsComponent } from "./components/trips/trips.component"; -import { SharedTripComponent } from "./components/shared-trip/shared-trip.component"; +import { DashboardComponent } from './components/dashboard/dashboard.component'; +import { AuthGuard } from './services/auth.guard'; +import { TripComponent } from './components/trip/trip.component'; +import { TripsComponent } from './components/trips/trips.component'; +import { SharedTripComponent } from './components/shared-trip/shared-trip.component'; export const routes: Routes = [ { - path: "auth", - pathMatch: "full", + path: 'auth', + pathMatch: 'full', component: AuthComponent, - title: "TRIP - Authentication", + title: 'TRIP - Authentication', }, { - path: "s", + path: 's', children: [ { - path: "t/:token", + path: 't/:token', component: SharedTripComponent, - title: "TRIP - Shared Trip", + title: 'TRIP - Shared Trip', }, - { path: "**", redirectTo: "/home", pathMatch: "full" }, + { path: '**', redirectTo: '/home', pathMatch: 'full' }, ], }, { - path: "", + path: '', canActivate: [AuthGuard], children: [ { - path: "home", + path: 'home', component: DashboardComponent, - title: "TRIP - Map", + title: 'TRIP - Map', }, { - path: "trips", + path: 'trips', children: [ { - path: "", + path: '', component: TripsComponent, - title: "TRIP - Trips", + title: 'TRIP - Trips', }, { - path: ":id", + path: ':id', component: TripComponent, - title: "TRIP - Trip", + title: 'TRIP - Trip', }, ], }, - { path: "**", redirectTo: "/home", pathMatch: "full" }, + { path: '**', redirectTo: '/home', pathMatch: 'full' }, ], }, - { path: "**", redirectTo: "/", pathMatch: "full" }, + { path: '**', redirectTo: '/', pathMatch: 'full' }, ]; diff --git a/src/src/app/components/auth/auth.component.scss b/src/src/app/components/auth/auth.component.scss index 85ca5ac..3ab39b4 100644 --- a/src/src/app/components/auth/auth.component.scss +++ b/src/src/app/components/auth/auth.component.scss @@ -1,4 +1,4 @@ .cover-auth { - background: url("/cover.webp"); + background: url('/cover.webp'); background-size: cover; } diff --git a/src/src/app/components/auth/auth.component.ts b/src/src/app/components/auth/auth.component.ts index 178a067..2adf5a5 100644 --- a/src/src/app/components/auth/auth.component.ts +++ b/src/src/app/components/auth/auth.component.ts @@ -1,24 +1,19 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, OnInit } from '@angular/core'; -import { FloatLabelModule } from "primeng/floatlabel"; -import { - FormBuilder, - FormGroup, - ReactiveFormsModule, - Validators, -} from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { InputTextModule } from "primeng/inputtext"; -import { ButtonModule } from "primeng/button"; -import { FocusTrapModule } from "primeng/focustrap"; -import { AuthParams, AuthService } from "../../services/auth.service"; -import { MessageModule } from "primeng/message"; -import { HttpErrorResponse } from "@angular/common/http"; -import { SkeletonModule } from "primeng/skeleton"; -import { take } from "rxjs"; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { InputTextModule } from 'primeng/inputtext'; +import { ButtonModule } from 'primeng/button'; +import { FocusTrapModule } from 'primeng/focustrap'; +import { AuthParams, AuthService } from '../../services/auth.service'; +import { MessageModule } from 'primeng/message'; +import { HttpErrorResponse } from '@angular/common/http'; +import { SkeletonModule } from 'primeng/skeleton'; +import { take } from 'rxjs'; @Component({ - selector: "app-auth", + selector: 'app-auth', standalone: true, imports: [ FloatLabelModule, @@ -29,8 +24,8 @@ import { take } from "rxjs"; FocusTrapModule, MessageModule, ], - templateUrl: "./auth.component.html", - styleUrl: "./auth.component.scss", + templateUrl: './auth.component.html', + styleUrl: './auth.component.scss', }) export class AuthComponent implements OnInit { readonly redirectURL: string; @@ -45,31 +40,29 @@ export class AuthComponent implements OnInit { private route: ActivatedRoute, private fb: FormBuilder, ) { - this.redirectURL = - this.route.snapshot.queryParams["redirectURL"] || "/home"; + this.redirectURL = this.route.snapshot.queryParams['redirectURL'] || '/home'; this.authForm = this.fb.group({ - username: ["", { validators: Validators.required }], - password: ["", { validators: Validators.required }], + username: ['', { validators: Validators.required }], + password: ['', { validators: Validators.required }], }); } ngOnInit(): void { this.route.queryParams.pipe(take(1)).subscribe((params) => { - const code = params["code"]; - const state = params["state"]; + const code = params['code']; + const state = params['state']; if (code && state) { this.authService.oidcLogin(code, state).subscribe({ next: (data) => { if (!data.access_token) { - this.error = "Authentication failed"; + this.error = 'Authentication failed'; return; } this.router.navigateByUrl(this.redirectURL); }, error: (err: HttpErrorResponse) => { - this.error = - err.error.detail || "Login failed, check console for details"; + this.error = err.error.detail || 'Login failed, check console for details'; }, }); } else { @@ -92,9 +85,7 @@ export class AuthComponent implements OnInit { }, error: (err: HttpErrorResponse) => { this.authForm.reset(); - this.error = - err.error.detail || - "Registration failed, check console for details"; + this.error = err.error.detail || 'Registration failed, check console for details'; }, }); } @@ -109,7 +100,7 @@ export class AuthComponent implements OnInit { this.authService.login(this.authForm.value).subscribe({ next: (data) => { if (!data.access_token) { - this.error = "Authentication failed"; + this.error = 'Authentication failed'; return; } this.router.navigateByUrl(this.redirectURL); diff --git a/src/src/app/components/dashboard/dashboard.component.ts b/src/src/app/components/dashboard/dashboard.component.ts index 440d705..c0e5f71 100644 --- a/src/src/app/components/dashboard/dashboard.component.ts +++ b/src/src/app/components/dashboard/dashboard.component.ts @@ -1,48 +1,36 @@ -import { AfterViewInit, Component, OnInit } from "@angular/core"; -import { combineLatest, debounceTime, take, tap } from "rxjs"; -import { Place, Category } from "../../types/poi"; -import { ApiService } from "../../services/api.service"; -import { PlaceBoxComponent } from "../../shared/place-box/place-box.component"; -import * as L from "leaflet"; -import "leaflet.markercluster"; -import "leaflet-contextmenu"; -import { - FormBuilder, - FormControl, - FormGroup, - FormsModule, - ReactiveFormsModule, - Validators, -} from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DialogService, DynamicDialogRef } from "primeng/dynamicdialog"; -import { PlaceCreateModalComponent } from "../../modals/place-create-modal/place-create-modal.component"; -import { InputTextModule } from "primeng/inputtext"; -import { SkeletonModule } from "primeng/skeleton"; -import { TabsModule } from "primeng/tabs"; -import { ToggleSwitchModule } from "primeng/toggleswitch"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { BatchCreateModalComponent } from "../../modals/batch-create-modal/batch-create-modal.component"; -import { UtilsService } from "../../services/utils.service"; -import { Info } from "../../types/info"; -import { - createMap, - placeToMarker, - createClusterGroup, - gpxToPolyline, -} from "../../shared/map"; -import { Router } from "@angular/router"; -import { SelectModule } from "primeng/select"; -import { MultiSelectModule } from "primeng/multiselect"; -import { TooltipModule } from "primeng/tooltip"; -import { Settings } from "../../types/settings"; -import { SelectItemGroup } from "primeng/api"; -import { YesNoModalComponent } from "../../modals/yes-no-modal/yes-no-modal.component"; -import { CategoryCreateModalComponent } from "../../modals/category-create-modal/category-create-modal.component"; -import { AuthService } from "../../services/auth.service"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { PlaceGPXComponent } from "../../shared/place-gpx/place-gpx.component"; -import { CommonModule } from "@angular/common"; +import { AfterViewInit, Component, OnInit } from '@angular/core'; +import { combineLatest, debounceTime, take, tap } from 'rxjs'; +import { Place, Category } from '../../types/poi'; +import { ApiService } from '../../services/api.service'; +import { PlaceBoxComponent } from '../../shared/place-box/place-box.component'; +import * as L from 'leaflet'; +import 'leaflet.markercluster'; +import 'leaflet-contextmenu'; +import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { PlaceCreateModalComponent } from '../../modals/place-create-modal/place-create-modal.component'; +import { InputTextModule } from 'primeng/inputtext'; +import { SkeletonModule } from 'primeng/skeleton'; +import { TabsModule } from 'primeng/tabs'; +import { ToggleSwitchModule } from 'primeng/toggleswitch'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { BatchCreateModalComponent } from '../../modals/batch-create-modal/batch-create-modal.component'; +import { UtilsService } from '../../services/utils.service'; +import { Info } from '../../types/info'; +import { createMap, placeToMarker, createClusterGroup, gpxToPolyline } from '../../shared/map'; +import { Router } from '@angular/router'; +import { SelectModule } from 'primeng/select'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { TooltipModule } from 'primeng/tooltip'; +import { Settings } from '../../types/settings'; +import { SelectItemGroup } from 'primeng/api'; +import { YesNoModalComponent } from '../../modals/yes-no-modal/yes-no-modal.component'; +import { CategoryCreateModalComponent } from '../../modals/category-create-modal/category-create-modal.component'; +import { AuthService } from '../../services/auth.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { PlaceGPXComponent } from '../../shared/place-gpx/place-gpx.component'; +import { CommonModule } from '@angular/common'; export interface ContextMenuItem { text: string; @@ -60,7 +48,7 @@ export interface MarkerOptions extends L.MarkerOptions { } @Component({ - selector: "app-dashboard", + selector: 'app-dashboard', standalone: true, imports: [ PlaceBoxComponent, @@ -78,11 +66,11 @@ export interface MarkerOptions extends L.MarkerOptions { ButtonModule, CommonModule, ], - templateUrl: "./dashboard.component.html", - styleUrls: ["./dashboard.component.scss"], + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.scss'], }) export class DashboardComponent implements OnInit, AfterViewInit { - searchInput = new FormControl(""); + searchInput = new FormControl(''); info?: Info; isLowNet = false; isDarkMode = false; @@ -123,36 +111,29 @@ export class DashboardComponent implements OnInit, AfterViewInit { ) { this.settingsForm = this.fb.group({ map_lat: [ - "", + '', { - validators: [ - Validators.required, - Validators.pattern("-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)"), - ], + validators: [Validators.required, Validators.pattern('-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)')], }, ], map_lng: [ - "", + '', { validators: [ Validators.required, - Validators.pattern( - "-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)", - ), + Validators.pattern('-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)'), ], }, ], - currency: ["", Validators.required], + currency: ['', Validators.required], do_not_display: [], - tile_layer: ["", Validators.required], + tile_layer: ['', Validators.required], }); // HACK: Subscribe in constructor for takeUntilDestroyed - this.searchInput.valueChanges - .pipe(debounceTime(200), takeUntilDestroyed()) - .subscribe({ - next: () => this.setVisibleMarkers(), - }); + this.searchInput.valueChanges.pipe(debounceTime(200), takeUntilDestroyed()).subscribe({ + next: () => this.setVisibleMarkers(), + }); } ngOnInit(): void { @@ -195,27 +176,24 @@ export class DashboardComponent implements OnInit, AfterViewInit { initMap(): void { if (!this.settings) return; - const isTouch = "ontouchstart" in window; + const isTouch = 'ontouchstart' in window; const contentMenuItems = [ { - text: "Add Point of Interest", - icon: "add-location.png", + text: 'Add Point of Interest', + icon: 'add-location.png', callback: (e: any) => { this.addPlaceModal(e); }, }, ]; - this.map = createMap( - isTouch ? [] : contentMenuItems, - this.settings?.tile_layer, - ); + this.map = createMap(isTouch ? [] : contentMenuItems, this.settings?.tile_layer); if (isTouch) { - this.map.on("contextmenu", (e: any) => { + this.map.on('contextmenu', (e: any) => { this.addPlaceModal(e); }); } this.map.setView(L.latLng(this.settings.map_lat, this.settings.map_lng)); - this.map.on("moveend zoomend", () => this.setVisibleMarkers()); + this.map.on('moveend zoomend', () => this.setVisibleMarkers()); this.markerClusterGroup = createClusterGroup().addTo(this.map); } @@ -223,30 +201,22 @@ export class DashboardComponent implements OnInit, AfterViewInit { if (!this.viewMarkersList || !this.map) return; const bounds = this.map.getBounds(); - this.visiblePlaces = this.filteredPlaces.filter((p) => - bounds.contains([p.lat, p.lng]), - ); + this.visiblePlaces = this.filteredPlaces.filter((p) => bounds.contains([p.lat, p.lng])); - const searchValue = this.searchInput.value?.toLowerCase() ?? ""; + const searchValue = this.searchInput.value?.toLowerCase() ?? ''; if (searchValue) this.visiblePlaces = this.visiblePlaces.filter( - (p) => - p.name.toLowerCase().includes(searchValue) || - p.description?.toLowerCase().includes(searchValue), + (p) => p.name.toLowerCase().includes(searchValue) || p.description?.toLowerCase().includes(searchValue), ); - this.visiblePlaces.sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + this.visiblePlaces.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); } resetFilters() { this.filter_display_visited = false; this.filter_display_favorite_only = false; this.activeCategories = new Set(this.categories.map((c) => c.name)); - this.settings?.do_not_display.forEach((c) => - this.activeCategories.delete(c), - ); + this.settings?.do_not_display.forEach((c) => this.activeCategories.delete(c)); this.updateMarkersAndClusters(); if (this.viewMarkersList) this.setVisibleMarkers(); } @@ -278,47 +248,38 @@ export class DashboardComponent implements OnInit, AfterViewInit { } _placeToMarker(place: Place): L.Marker { - const marker = placeToMarker( - place, - this.isLowNet, - place.visited, - this.isGpxInPlaceMode, - ); + const marker = placeToMarker(place, this.isLowNet, place.visited, this.isGpxInPlaceMode); marker - .on("click", (e) => { + .on('click', (e) => { this.selectedPlace = { ...place }; let toView = { ...e.latlng }; - if ("ontouchstart" in window) toView.lat = toView.lat - 0.0175; + if ('ontouchstart' in window) toView.lat = toView.lat - 0.0175; marker.closeTooltip(); this.map?.setView(toView); }) - .on("contextmenu", () => { - if (this.map && (this.map as any).contextmenu) - (this.map as any).contextmenu.hide(); + .on('contextmenu', () => { + if (this.map && (this.map as any).contextmenu) (this.map as any).contextmenu.hide(); }); return marker; } addPlaceModal(e?: any): void { const opts = e ? { data: { place: e.latlng } } : {}; - const modal: DynamicDialogRef = this.dialogService.open( - PlaceCreateModalComponent, - { - header: "Create Place", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "55vw", - breakpoints: { - "1920px": "70vw", - "1260px": "90vw", - }, - ...opts, + const modal: DynamicDialogRef = this.dialogService.open(PlaceCreateModalComponent, { + header: 'Create Place', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '55vw', + breakpoints: { + '1920px': '70vw', + '1260px': '90vw', }, - )!; + ...opts, + })!; modal.onClose.pipe(take(1)).subscribe({ next: (place: Place | null) => { @@ -329,9 +290,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { .pipe(take(1)) .subscribe({ next: (place: Place) => { - this.places = [...this.places, place].sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + this.places = [...this.places, place].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); setTimeout(() => { this.updateMarkersAndClusters(); }, 10); @@ -342,21 +301,18 @@ export class DashboardComponent implements OnInit, AfterViewInit { } batchAddModal() { - const modal: DynamicDialogRef = this.dialogService.open( - BatchCreateModalComponent, - { - header: "Create Places", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "55vw", - breakpoints: { - "1920px": "70vw", - "1260px": "90vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(BatchCreateModalComponent, { + header: 'Create Places', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '55vw', + breakpoints: { + '1920px': '70vw', + '1260px': '90vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (places: string | null) => { @@ -367,7 +323,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { parsedPlaces = JSON.parse(places); if (!Array.isArray(parsedPlaces)) throw new Error(); } catch (err) { - this.utilsService.toast("error", "Error", "Content looks invalid"); + this.utilsService.toast('error', 'Error', 'Content looks invalid'); return; } @@ -375,9 +331,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { .postPlaces(parsedPlaces) .pipe(take(1)) .subscribe((places) => { - this.places = [...this.places, ...places].sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + this.places = [...this.places, ...places].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); setTimeout(() => { this.updateMarkersAndClusters(); }, 10); @@ -388,7 +342,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { resetHoverPlace() { if (!this.hoveredElement) return; - this.hoveredElement.classList.remove("list-hover"); + this.hoveredElement.classList.remove('list-hover'); this.hoveredElement = undefined; } @@ -405,17 +359,15 @@ export class DashboardComponent implements OnInit, AfterViewInit { if (markerElement) { // marker, not clustered - markerElement.classList.add("list-hover"); + markerElement.classList.add('list-hover'); this.hoveredElement = markerElement; } else { // marker is clustered - const parentCluster = (this.markerClusterGroup as any).getVisibleParent( - marker, - ); + const parentCluster = (this.markerClusterGroup as any).getVisibleParent(marker); if (parentCluster) { const clusterEl = parentCluster.getElement(); if (clusterEl) { - clusterEl.classList.add("list-hover"); + clusterEl.classList.add('list-hover'); this.hoveredElement = clusterEl; } } @@ -431,11 +383,8 @@ export class DashboardComponent implements OnInit, AfterViewInit { .pipe(take(1)) .subscribe({ next: () => { - const idx = this.places.findIndex( - (p) => p.id === this.selectedPlace!.id, - ); - if (idx !== -1) - this.places[idx] = { ...this.places[idx], favorite: favoriteBool }; + const idx = this.places.findIndex((p) => p.id === this.selectedPlace!.id); + if (idx !== -1) this.places[idx] = { ...this.places[idx], favorite: favoriteBool }; this.selectedPlace = { ...this.places[idx] }; this.updateMarkersAndClusters(); }, @@ -451,11 +400,8 @@ export class DashboardComponent implements OnInit, AfterViewInit { .pipe(take(1)) .subscribe({ next: () => { - const idx = this.places.findIndex( - (p) => p.id === this.selectedPlace!.id, - ); - if (idx !== -1) - this.places[idx] = { ...this.places[idx], visited: visitedBool }; + const idx = this.places.findIndex((p) => p.id === this.selectedPlace!.id); + if (idx !== -1) this.places[idx] = { ...this.places[idx], visited: visitedBool }; this.selectedPlace = { ...this.places[idx] }; this.updateMarkersAndClusters(); }, @@ -466,12 +412,12 @@ export class DashboardComponent implements OnInit, AfterViewInit { if (!this.selectedPlace) return; const modal = this.dialogService.open(YesNoModalComponent, { - header: "Confirm deletion", + header: 'Confirm deletion', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, data: `Delete ${this.selectedPlace.name} ?`, })!; @@ -484,9 +430,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { .pipe(take(1)) .subscribe({ next: () => { - this.places = this.places.filter( - (p) => p.id !== this.selectedPlace!.id, - ); + this.places = this.places.filter((p) => p.id !== this.selectedPlace!.id); this.closePlaceBox(); this.updateMarkersAndClusters(); if (this.viewMarkersList) this.setVisibleMarkers(); @@ -500,27 +444,24 @@ export class DashboardComponent implements OnInit, AfterViewInit { if (!this.selectedPlace && !p) return; const _placeToEdit: Place = { ...(this.selectedPlace ?? p)! }; - const modal: DynamicDialogRef = this.dialogService.open( - PlaceCreateModalComponent, - { - header: "Edit Place", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "55vw", - breakpoints: { - "1920px": "70vw", - "1260px": "90vw", - }, - data: { - place: { - ..._placeToEdit, - category: _placeToEdit.category.id, - }, + const modal: DynamicDialogRef = this.dialogService.open(PlaceCreateModalComponent, { + header: 'Edit Place', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '55vw', + breakpoints: { + '1920px': '70vw', + '1260px': '90vw', + }, + data: { + place: { + ..._placeToEdit, + category: _placeToEdit.category.id, }, }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (place: Place | null) => { @@ -534,9 +475,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { const places = [...this.places]; const idx = places.findIndex((p) => p.id == place.id); if (idx > -1) places.splice(idx, 1, place); - places.sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + places.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); this.places = places; if (this.selectedPlace) this.selectedPlace = { ...place }; setTimeout(() => { @@ -551,21 +490,20 @@ export class DashboardComponent implements OnInit, AfterViewInit { displayGPXOnMap(gpx: string) { if (!this.map || !this.selectedPlace) return; - if (!this.gpxLayerGroup) - this.gpxLayerGroup = L.layerGroup().addTo(this.map); + if (!this.gpxLayerGroup) this.gpxLayerGroup = L.layerGroup().addTo(this.map); this.gpxLayerGroup.clearLayers(); try { const gpxPolyline = gpxToPolyline(gpx); const selectedPlaceWithGPX = { ...this.selectedPlace, gpx }; - gpxPolyline.on("click", () => { + gpxPolyline.on('click', () => { this.selectedGPX = selectedPlaceWithGPX; }); this.gpxLayerGroup?.addLayer(gpxPolyline); this.map.fitBounds(gpxPolyline.getBounds(), { padding: [20, 20] }); } catch { - this.utilsService.toast("error", "Error", "Couldn't parse GPX data"); + this.utilsService.toast('error', 'Error', "Couldn't parse GPX data"); } this.closePlaceBox(); } @@ -578,11 +516,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { .subscribe({ next: (p) => { if (!p.gpx) { - this.utilsService.toast( - "error", - "Error", - "Couldn't retrieve GPX data", - ); + this.utilsService.toast('error', 'Error', "Couldn't retrieve GPX data"); return; } this.displayGPXOnMap(p.gpx); @@ -597,7 +531,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { this.settingsForm.reset(this.settings); this.doNotDisplayOptions = [ { - label: "Categories", + label: 'Categories', items: this.categories.map((c) => ({ label: c.name, value: c.name })), }, ]; @@ -610,13 +544,13 @@ export class DashboardComponent implements OnInit, AfterViewInit { toggleMarkersList() { this.viewMarkersList = !this.viewMarkersList; this.viewMarkersListSearch = false; - this.searchInput.setValue(""); + this.searchInput.setValue(''); if (this.viewMarkersList) this.setVisibleMarkers(); } toggleMarkersListSearch() { this.viewMarkersListSearch = !this.viewMarkersListSearch; - if (this.viewMarkersListSearch) this.searchInput.setValue(""); + if (this.viewMarkersListSearch) this.searchInput.setValue(''); } setMapCenterToCurrent() { @@ -631,7 +565,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { if (!input.files?.length) return; const formdata = new FormData(); - formdata.append("file", input.files[0]); + formdata.append('file', input.files[0]); this.apiService .settingsUserImport(formdata) @@ -667,12 +601,12 @@ export class DashboardComponent implements OnInit, AfterViewInit { .pipe(take(1)) .subscribe((resp: Object) => { const dataBlob = new Blob([JSON.stringify(resp, null, 2)], { - type: "application/json", + type: 'application/json', }); const downloadURL = URL.createObjectURL(dataBlob); - const link = document.createElement("a"); + const link = document.createElement('a'); link.href = downloadURL; - link.download = `TRIP_backup_${new Date().toISOString().split("T")[0]}.json`; + link.download = `TRIP_backup_${new Date().toISOString().split('T')[0]}.json`; link.click(); link.remove(); URL.revokeObjectURL(downloadURL); @@ -699,22 +633,19 @@ export class DashboardComponent implements OnInit, AfterViewInit { } editCategory(c: Category) { - const modal: DynamicDialogRef = this.dialogService.open( - CategoryCreateModalComponent, - { - header: "Update Category", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - data: { category: c }, - width: "40vw", - breakpoints: { - "960px": "70vw", - "640px": "90vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(CategoryCreateModalComponent, { + header: 'Update Category', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + data: { category: c }, + width: '40vw', + breakpoints: { + '960px': '70vw', + '640px': '90vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (category: Category | null) => { @@ -725,17 +656,12 @@ export class DashboardComponent implements OnInit, AfterViewInit { .pipe(take(1)) .subscribe({ next: (updated) => { - this.categories = this.categories.map((cat) => - cat.id === updated.id ? updated : cat, - ); + this.categories = this.categories.map((cat) => (cat.id === updated.id ? updated : cat)); this.sortCategories(); - this.activeCategories = new Set( - this.categories.map((c) => c.name), - ); + this.activeCategories = new Set(this.categories.map((c) => c.name)); this.places = this.places.map((p) => { - if (p.category.id == updated.id) - return { ...p, category: updated }; + if (p.category.id == updated.id) return { ...p, category: updated }; return p; }); setTimeout(() => { @@ -748,21 +674,18 @@ export class DashboardComponent implements OnInit, AfterViewInit { } addCategory() { - const modal: DynamicDialogRef = this.dialogService.open( - CategoryCreateModalComponent, - { - header: "Create Category", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "40vw", - breakpoints: { - "960px": "70vw", - "640px": "90vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(CategoryCreateModalComponent, { + header: 'Create Category', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '40vw', + breakpoints: { + '960px': '70vw', + '640px': '90vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (category: Category | null) => { @@ -774,9 +697,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { .subscribe({ next: (category: Category) => { this.categories.push(category); - this.categories.sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + this.categories.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); this.activeCategories.add(category.name); }, }); @@ -786,14 +707,14 @@ export class DashboardComponent implements OnInit, AfterViewInit { deleteCategory(c_id: number) { const modal = this.dialogService.open(YesNoModalComponent, { - header: "Confirm deletion", + header: 'Confirm deletion', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, - data: "Delete this category ?", + data: 'Delete this category ?', })!; modal.onClose.pipe(take(1)).subscribe({ @@ -806,9 +727,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { next: () => { this.categories = this.categories.filter((c) => c.id !== c_id); - this.activeCategories = new Set( - this.categories.map((c) => c.name), - ); + this.activeCategories = new Set(this.categories.map((c) => c.name)); }, }); }, @@ -824,13 +743,11 @@ export class DashboardComponent implements OnInit, AfterViewInit { } sortCategories() { - this.categories = [...this.categories].sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + this.categories = [...this.categories].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); } navigateToTrips() { - this.router.navigateByUrl("/trips"); + this.router.navigateByUrl('/trips'); } logout() { @@ -849,7 +766,7 @@ export class DashboardComponent implements OnInit, AfterViewInit { if (!this.selectedGPX?.gpx) return; const dataBlob = new Blob([this.selectedGPX.gpx]); const downloadURL = URL.createObjectURL(dataBlob); - const link = document.createElement("a"); + const link = document.createElement('a'); link.href = downloadURL; link.download = `TRIP_${this.selectedGPX.name}.gpx`; link.click(); @@ -874,13 +791,8 @@ export class DashboardComponent implements OnInit, AfterViewInit { .subscribe({ next: (remote_version) => { if (!remote_version) - this.utilsService.toast( - "success", - "Latest version", - "You're running the latest version of TRIP", - ); - if (this.info && remote_version != this.info?.version) - this.info.update = remote_version; + this.utilsService.toast('success', 'Latest version', "You're running the latest version of TRIP"); + if (this.info && remote_version != this.info?.version) this.info.update = remote_version; }, }); } diff --git a/src/src/app/components/shared-trip/shared-trip.component.html b/src/src/app/components/shared-trip/shared-trip.component.html index 842d4b5..12ab90b 100644 --- a/src/src/app/components/shared-trip/shared-trip.component.html +++ b/src/src/app/components/shared-trip/shared-trip.component.html @@ -8,16 +8,16 @@
{{ - trip.days.length }} {{ trip.days!.length > 1 ? 'days' : 'day'}} + trip.days.length }} {{ trip.days!.length > 1 ? 'days' : 'day' }} {{ - (totalPrice | number:'1.0-2') || '-' }} {{ trip.currency }} + (totalPrice | number: '1.0-2') || '-' }} {{ trip.currency }}
@@ -44,9 +44,11 @@
- + } } @placeholder (minimum 0.4s) {
@@ -257,8 +336,7 @@ }
-

{{ selectedItem.text }} -

+

{{ selectedItem.text }}

@if (selectedItem.gpx) { @@ -302,16 +380,23 @@

Latitude, Longitude

{{ - selectedItem.lat }}, {{ selectedItem.lng }}

+ [cdkCopyToClipboard]="selectedItem.lat + ',' + selectedItem.lng"> + {{ selectedItem.lat }}, {{ selectedItem.lng }} +

} @if (selectedItem.price) {

Price

-

{{ selectedItem.price }} @if (selectedItem.price) { {{ trip.currency }} } - @if (selectedItem.paid_by) {(by {{ selectedItem.paid_by }})} +

+ {{ selectedItem.price }} + @if (selectedItem.price) { + {{ trip.currency }} + } + @if (selectedItem.paid_by) { + (by {{ selectedItem.paid_by }}) + }

} @@ -319,9 +404,8 @@ @if (selectedItem.status) {

Status

- {{ - selectedItem.status.label }} + {{ selectedItem.status.label }}
} @@ -349,8 +433,7 @@
-
-
+
@if (!selectedItem) { @@ -370,7 +453,8 @@
@defer { {{ - places.length }} + places.length + }} } @placeholder (minimum 0.4s) { } @@ -383,7 +467,7 @@ @for (p of places; track p.id) {
- +

{{ p.name }}

@@ -404,8 +488,11 @@ {{ - p.price || '-' - }} @if (p.price) { {{ trip.currency }} } + p.price || '-' }} + @if (p.price) { + {{ trip.currency }} + } + @if (trip.collaborators.length) { {{ p.user @@ -419,7 +506,7 @@ } } @placeholder (minimum 0.4s) {
- @for (_ of [1,2,3]; track _) { + @for (_ of [1, 2, 3]; track _) {
@@ -454,9 +541,14 @@
{{ - getDayStats(d).price || '-' }} @if (getDayStats(d).price) { {{ trip.currency }} } + getDayStats(d).price || '-' }} + @if (getDayStats(d).price) { + {{ trip.currency }} + } + {{ - getDayStats(d).places }} + getDayStats(d).places + }}
} @empty { @@ -474,16 +566,19 @@

About

-
+
+ +
-
TRIP is free and always will be. Building it takes a lot of time and effort, and since I'm committed to - keeping it free, your support simply helps keep the project going. Thank you! ❤️
+
+ TRIP is free and always will be. Building it takes a lot of time and effort, and since I'm committed to + keeping it free, your support simply helps keep the project going. Thank you! ❤️ +
- Buy me - a coffee + Buy me a + coffee
} @@ -505,12 +600,17 @@ [class.prettyprint]="isPrinting"> @if (isMapFullscreenDays) {
- @for(day of trip.days; track day.id) { -
@@ -700,7 +799,7 @@
📍 Places
- @for(place of trip?.places; track place.id) { + @for (place of trip?.places; track place.id) {
diff --git a/src/src/app/components/shared-trip/shared-trip.component.ts b/src/src/app/components/shared-trip/shared-trip.component.ts index dc33f86..009fcfb 100644 --- a/src/src/app/components/shared-trip/shared-trip.component.ts +++ b/src/src/app/components/shared-trip/shared-trip.component.ts @@ -1,47 +1,33 @@ -import { AfterViewInit, Component } from "@angular/core"; -import { ApiService } from "../../services/api.service"; -import { ButtonModule } from "primeng/button"; -import { SkeletonModule } from "primeng/skeleton"; -import * as L from "leaflet"; -import { antPath } from "leaflet-ant-path"; -import { TableModule } from "primeng/table"; -import { - Trip, - FlattenedTripItem, - TripDay, - TripItem, - TripStatus, - PackingItem, - ChecklistItem, -} from "../../types/trip"; -import { Place } from "../../types/poi"; -import { - createMap, - placeToMarker, - createClusterGroup, - tripDayMarker, - gpxToPolyline, -} from "../../shared/map"; -import { ActivatedRoute } from "@angular/router"; -import { debounceTime, take, tap } from "rxjs"; -import { UtilsService } from "../../services/utils.service"; -import { CommonModule, DecimalPipe } from "@angular/common"; -import { MenuItem } from "primeng/api"; -import { MenuModule } from "primeng/menu"; -import { LinkifyPipe } from "../../shared/linkify.pipe"; -import { TooltipModule } from "primeng/tooltip"; -import { FormControl, FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { MultiSelectModule } from "primeng/multiselect"; -import { DialogModule } from "primeng/dialog"; -import { CheckboxModule } from "primeng/checkbox"; -import { InputTextModule } from "primeng/inputtext"; -import { ClipboardModule } from "@angular/cdk/clipboard"; -import { calculateDistanceBetween } from "../../shared/haversine"; -import { orderByPipe } from "../../shared/order-by.pipe"; +import { AfterViewInit, Component } from '@angular/core'; +import { ApiService } from '../../services/api.service'; +import { ButtonModule } from 'primeng/button'; +import { SkeletonModule } from 'primeng/skeleton'; +import * as L from 'leaflet'; +import { antPath } from 'leaflet-ant-path'; +import { TableModule } from 'primeng/table'; +import { Trip, FlattenedTripItem, TripDay, TripItem, TripStatus, PackingItem, ChecklistItem } from '../../types/trip'; +import { Place } from '../../types/poi'; +import { createMap, placeToMarker, createClusterGroup, tripDayMarker, gpxToPolyline } from '../../shared/map'; +import { ActivatedRoute } from '@angular/router'; +import { debounceTime, take, tap } from 'rxjs'; +import { UtilsService } from '../../services/utils.service'; +import { CommonModule, DecimalPipe } from '@angular/common'; +import { MenuItem } from 'primeng/api'; +import { MenuModule } from 'primeng/menu'; +import { LinkifyPipe } from '../../shared/linkify.pipe'; +import { TooltipModule } from 'primeng/tooltip'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { DialogModule } from 'primeng/dialog'; +import { CheckboxModule } from 'primeng/checkbox'; +import { InputTextModule } from 'primeng/inputtext'; +import { ClipboardModule } from '@angular/cdk/clipboard'; +import { calculateDistanceBetween } from '../../shared/haversine'; +import { orderByPipe } from '../../shared/order-by.pipe'; @Component({ - selector: "app-shared-trip", + selector: 'app-shared-trip', standalone: true, imports: [ CommonModule, @@ -61,8 +47,8 @@ import { orderByPipe } from "../../shared/order-by.pipe"; ClipboardModule, orderByPipe, ], - templateUrl: "./shared-trip.component.html", - styleUrls: ["./shared-trip.component.scss"], + templateUrl: './shared-trip.component.html', + styleUrls: ['./shared-trip.component.scss'], }) export class SharedTripComponent implements AfterViewInit { token?: string; @@ -98,18 +84,18 @@ export class SharedTripComponent implements AfterViewInit { readonly menuTripActionsItems: MenuItem[] = [ { - label: "Lists", + label: 'Lists', items: [ { - label: "Checklist", - icon: "pi pi-check-square", + label: 'Checklist', + icon: 'pi pi-check-square', command: () => { this.openChecklist(); }, }, { - label: "Packing", - icon: "pi pi-briefcase", + label: 'Packing', + icon: 'pi pi-briefcase', command: () => { this.openPackingList(); }, @@ -117,11 +103,11 @@ export class SharedTripComponent implements AfterViewInit { ], }, { - label: "Trip", + label: 'Trip', items: [ { - label: "Pretty Print", - icon: "pi pi-print", + label: 'Pretty Print', + icon: 'pi pi-print', command: () => { this.togglePrint(); }, @@ -131,11 +117,11 @@ export class SharedTripComponent implements AfterViewInit { ]; readonly menuTripTableActionsItems: MenuItem[] = [ { - label: "Actions", + label: 'Actions', items: [ { - label: "Pretty Print", - icon: "pi pi-print", + label: 'Pretty Print', + icon: 'pi pi-print', command: () => { this.togglePrint(); }, @@ -143,18 +129,18 @@ export class SharedTripComponent implements AfterViewInit { ], }, { - label: "Table", + label: 'Table', items: [ { - label: "Filter", - icon: "pi pi-filter", + label: 'Filter', + icon: 'pi pi-filter', command: () => { this.toggleFiltering(); }, }, { - label: "Group", - icon: "pi pi-arrow-down-left-and-arrow-up-right-to-center", + label: 'Group', + icon: 'pi pi-arrow-down-left-and-arrow-up-right-to-center', command: () => { this.tableExpandableMode = !this.tableExpandableMode; }, @@ -162,18 +148,18 @@ export class SharedTripComponent implements AfterViewInit { ], }, { - label: "Directions", + label: 'Directions', items: [ { - label: "Highlight", - icon: "pi pi-directions", + label: 'Highlight', + icon: 'pi pi-directions', command: () => { this.toggleTripDaysHighlight(); }, }, { - label: "GMaps itinerary", - icon: "pi pi-car", + label: 'GMaps itinerary', + icon: 'pi pi-car', command: () => { this.tripToNavigation(); }, @@ -182,24 +168,18 @@ export class SharedTripComponent implements AfterViewInit { }, ]; readonly tripTableColumns: string[] = [ - "day", - "time", - "text", - "place", - "comment", - "LatLng", - "price", - "status", - "distance", + 'day', + 'time', + 'text', + 'place', + 'comment', + 'LatLng', + 'price', + 'status', + 'distance', ]; - tripTableSelectedColumns: string[] = [ - "day", - "time", - "text", - "place", - "comment", - ]; - tripTableSearchInput = new FormControl(""); + tripTableSelectedColumns: string[] = ['day', 'time', 'text', 'place', 'comment']; + tripTableSearchInput = new FormControl(''); dayStatsCache = new Map(); placesUsedInTable = new Set(); @@ -210,14 +190,12 @@ export class SharedTripComponent implements AfterViewInit { private route: ActivatedRoute, ) { this.statuses = this.utilsService.statuses; - this.tripTableSearchInput.valueChanges - .pipe(debounceTime(300), takeUntilDestroyed()) - .subscribe({ - next: (value) => { - if (value) this.flattenTripDayItems(value.toLowerCase()); - else this.flattenTripDayItems(); - }, - }); + this.tripTableSearchInput.valueChanges.pipe(debounceTime(300), takeUntilDestroyed()).subscribe({ + next: (value) => { + if (value) this.flattenTripDayItems(value.toLowerCase()); + else this.flattenTripDayItems(); + }, + }); } ngAfterViewInit(): void { @@ -225,7 +203,7 @@ export class SharedTripComponent implements AfterViewInit { .pipe( take(1), tap((params) => { - const token = params.get("token"); + const token = params.get('token'); if (token) { this.token = token; this.loadTripData(token); @@ -252,12 +230,10 @@ export class SharedTripComponent implements AfterViewInit { initMap(): void { const contentMenuItems = [ { - text: "Copy coordinates", + text: 'Copy coordinates', callback: (e: any) => { const latlng = e.latlng; - navigator.clipboard.writeText( - `${parseFloat(latlng.lat).toFixed(5)}, ${parseFloat(latlng.lng).toFixed(5)}`, - ); + navigator.clipboard.writeText(`${parseFloat(latlng.lat).toFixed(5)}, ${parseFloat(latlng.lng).toFixed(5)}`); }, }, ]; @@ -305,11 +281,7 @@ export class SharedTripComponent implements AfterViewInit { if (!this.trip?.days) return []; return this.trip.days - .flatMap((day) => - day.items.filter((item) => - ["constraint", "pending"].includes(item.status as string), - ), - ) + .flatMap((day) => day.items.filter((item) => ['constraint', 'pending'].includes(item.status as string))) .map((item) => ({ ...item, status: this.statusToTripStatus(item.status as string), @@ -383,9 +355,7 @@ export class SharedTripComponent implements AfterViewInit { setPlacesAndMarkers() { this.computePlacesUsedInTable(); - this.places = [...(this.trip?.places ?? [])].sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + this.places = [...(this.trip?.places ?? [])].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); this.markerClusterGroup?.clearLayers(); this.places.forEach((p) => { const marker = this._placeToMarker(p); @@ -394,12 +364,8 @@ export class SharedTripComponent implements AfterViewInit { } _placeToMarker(place: Place): L.Marker { - const marker = placeToMarker( - place, - false, - !this.placesUsedInTable.has(place.id), - ); - marker.on("click", () => { + const marker = placeToMarker(place, false, !this.placesUsedInTable.has(place.id)); + marker.on('click', () => { this.onMapMarkerClick(place.id); marker.closeTooltip(); }); @@ -409,9 +375,7 @@ export class SharedTripComponent implements AfterViewInit { resetMapBounds() { if (!this.places.length) { this.map?.fitBounds( - this.flattenedTripItems - .filter((i) => i.lat != null && i.lng != null) - .map((i) => [i.lat!, i.lng!]), + this.flattenedTripItems.filter((i) => i.lat != null && i.lng != null).map((i) => [i.lat!, i.lng!]), { padding: [30, 30] }, ); return; @@ -425,7 +389,7 @@ export class SharedTripComponent implements AfterViewInit { toggleMapFullscreen() { this.isMapFullscreen = !this.isMapFullscreen; - document.body.classList.toggle("overflow-hidden"); + document.body.classList.toggle('overflow-hidden'); setTimeout(() => { this.map?.invalidateSize(); @@ -444,14 +408,12 @@ export class SharedTripComponent implements AfterViewInit { return; } this.totalPrice = - this.trip?.days - .flatMap((d) => d.items) - .reduce((price, item) => price + (item.price ?? 0), 0) ?? 0; + this.trip?.days.flatMap((d) => d.items).reduce((price, item) => price + (item.price ?? 0), 0) ?? 0; } resetPlaceHighlightMarker() { if (this.tripMapHoveredElement) { - this.tripMapHoveredElement.classList.remove("list-hover"); + this.tripMapHoveredElement.classList.remove('list-hover'); this.tripMapHoveredElement = undefined; } @@ -468,8 +430,7 @@ export class SharedTripComponent implements AfterViewInit { } placeHighlightMarker(item: any) { - if (this.tripMapHoveredElement || this.tripMapTemporaryMarker) - this.resetPlaceHighlightMarker(); + if (this.tripMapHoveredElement || this.tripMapTemporaryMarker) this.resetPlaceHighlightMarker(); let marker: L.Marker | undefined; this.markerClusterGroup?.eachLayer((layer: any) => { @@ -487,10 +448,7 @@ export class SharedTripComponent implements AfterViewInit { // TripItem without place, but latlng this.tripMapTemporaryMarker = tripDayMarker(item).addTo(this.map!); if (this.tripMapGpxLayer) { - this.map?.fitBounds( - [[item.lat, item.lng], (this.tripMapGpxLayer as any).getBounds()], - { padding: [30, 30] }, - ); + this.map?.fitBounds([[item.lat, item.lng], (this.tripMapGpxLayer as any).getBounds()], { padding: [30, 30] }); } else this.map?.fitBounds([[item.lat, item.lng]], { padding: [60, 60] }); return; } @@ -499,18 +457,16 @@ export class SharedTripComponent implements AfterViewInit { const markerElement = marker.getElement() as HTMLElement; // search for Marker. If 'null', is inside Cluster if (markerElement) { // marker, not clustered - markerElement.classList.add("list-hover"); + markerElement.classList.add('list-hover'); this.tripMapHoveredElement = markerElement; targetLatLng = marker.getLatLng(); } else { // marker is clustered - const parentCluster = (this.markerClusterGroup as any).getVisibleParent( - marker, - ); + const parentCluster = (this.markerClusterGroup as any).getVisibleParent(marker); if (parentCluster) { const clusterEl = parentCluster.getElement(); if (clusterEl) { - clusterEl.classList.add("list-hover"); + clusterEl.classList.add('list-hover'); this.tripMapHoveredElement = clusterEl; } targetLatLng = parentCluster.getLatLng(); @@ -574,11 +530,7 @@ export class SharedTripComponent implements AfterViewInit { .filter((n) => n !== undefined); if (items.length < 2) { - this.utilsService.toast( - "info", - "Info", - "Not enough values to map an itinerary", - ); + this.utilsService.toast('info', 'Info', 'Not enough values to map an itinerary'); return; } @@ -590,20 +542,20 @@ export class SharedTripComponent implements AfterViewInit { const layGroup = L.featureGroup(); const COLORS: string[] = [ - "#e6194b", - "#3cb44b", - "#ffe119", - "#4363d8", - "#9a6324", - "#f58231", - "#911eb4", - "#46f0f0", - "#f032e6", - "#bcf60c", - "#fabebe", - "#008080", - "#e6beff", - "#808000", + '#e6194b', + '#3cb44b', + '#ffe119', + '#4363d8', + '#9a6324', + '#f58231', + '#911eb4', + '#46f0f0', + '#f032e6', + '#bcf60c', + '#fabebe', + '#008080', + '#e6beff', + '#808000', ]; let prevPoint: [number, number] | null = null; @@ -614,7 +566,7 @@ export class SharedTripComponent implements AfterViewInit { dashArray: [10, 20], weight: 5, color: COLORS[idx % COLORS.length], - pulseColor: "#FFFFFF", + pulseColor: '#FFFFFF', paused: false, reverse: false, hardwareAccelerated: true, @@ -665,9 +617,7 @@ export class SharedTripComponent implements AfterViewInit { const idx = this.trip?.days.findIndex((d) => d.id === day_id); if (!this.trip || idx === undefined || idx == -1) return; - const data = this.trip.days[idx].items.sort((a, b) => - a.time < b.time ? -1 : a.time > b.time ? 1 : 0, - ); + const data = this.trip.days[idx].items.sort((a, b) => (a.time < b.time ? -1 : a.time > b.time ? 1 : 0)); const items = data .map((item) => { if (item.lat && item.lng) @@ -693,11 +643,7 @@ export class SharedTripComponent implements AfterViewInit { .filter((n) => n !== undefined); if (items.length < 2) { - this.utilsService.toast( - "info", - "Info", - "Not enough values to map an itinerary", - ); + this.utilsService.toast('info', 'Info', 'Not enough values to map an itinerary'); return; } @@ -712,8 +658,8 @@ export class SharedTripComponent implements AfterViewInit { delay: 400, dashArray: [10, 20], weight: 5, - color: "#0000FF", - pulseColor: "#FFFFFF", + color: '#0000FF', + pulseColor: '#FFFFFF', paused: false, reverse: false, hardwareAccelerated: true, @@ -751,15 +697,9 @@ export class SharedTripComponent implements AfterViewInit { } onMapMarkerClick(place_id: number) { - const item = this.flattenedTripItems.find( - (i) => i.place && i.place.id == place_id, - ); + const item = this.flattenedTripItems.find((i) => i.place && i.place.id == place_id); if (!item) { - this.utilsService.toast( - "info", - "Place not used", - "The place is not used in the table", - ); + this.utilsService.toast('info', 'Place not used', 'The place is not used in the table'); return; } @@ -773,14 +713,14 @@ export class SharedTripComponent implements AfterViewInit { // TODO: More services // const url = `http://maps.apple.com/?daddr=${this.selectedItem.lat},${this.selectedItem.lng}`; const url = `https://www.google.com/maps/dir/?api=1&destination=${this.selectedItem.lat},${this.selectedItem.lng}`; - window.open(url, "_blank"); + window.open(url, '_blank'); } downloadItemGPX() { if (!this.selectedItem?.gpx) return; const dataBlob = new Blob([this.selectedItem.gpx]); const downloadURL = URL.createObjectURL(dataBlob); - const link = document.createElement("a"); + const link = document.createElement('a'); link.href = downloadURL; link.download = `TRIP_${this.trip?.name}_${this.selectedItem.text}.gpx`; link.click(); @@ -791,27 +731,23 @@ export class SharedTripComponent implements AfterViewInit { tripDayToNavigation(day_id: number) { const idx = this.trip?.days.findIndex((d) => d.id === day_id); if (!this.trip || idx === undefined || idx == -1) return; - const data = this.trip.days[idx].items.sort((a, b) => - a.time < b.time ? -1 : a.time > b.time ? 1 : 0, - ); + const data = this.trip.days[idx].items.sort((a, b) => (a.time < b.time ? -1 : a.time > b.time ? 1 : 0)); const items = data.filter((item) => item.lat && item.lng); if (!items.length) return; - const waypoints = items.map((item) => `${item.lat},${item.lng}`).join("/"); + const waypoints = items.map((item) => `${item.lat},${item.lng}`).join('/'); const url = `https://www.google.com/maps/dir/${waypoints}`; - window.open(url, "_blank"); + window.open(url, '_blank'); } tripToNavigation() { // TODO: More services - const items = this.flattenedTripItems.filter( - (item) => item.lat && item.lng, - ); + const items = this.flattenedTripItems.filter((item) => item.lat && item.lng); if (!items.length) return; - const waypoints = items.map((item) => `${item.lat},${item.lng}`).join("/"); + const waypoints = items.map((item) => `${item.lat},${item.lng}`).join('/'); const url = `https://www.google.com/maps/dir/${waypoints}`; - window.open(url, "_blank"); + window.open(url, '_blank'); } openPackingList() { @@ -832,24 +768,13 @@ export class SharedTripComponent implements AfterViewInit { computeDispPackingList() { const sorted: PackingItem[] = [...this.packingList].sort((a, b) => - a.packed !== b.packed - ? a.packed - ? 1 - : -1 - : a.text < b.text - ? -1 - : a.text > b.text - ? 1 - : 0, + a.packed !== b.packed ? (a.packed ? 1 : -1) : a.text < b.text ? -1 : a.text > b.text ? 1 : 0, ); - this.dispPackingList = sorted.reduce>( - (acc, item) => { - (acc[item.category] ??= []).push(item); - return acc; - }, - {}, - ); + this.dispPackingList = sorted.reduce>((acc, item) => { + (acc[item.category] ??= []).push(item); + return acc; + }, {}); } openChecklist() { diff --git a/src/src/app/components/trip/trip.component.ts b/src/src/app/components/trip/trip.component.ts index 126feb7..0612f5e 100644 --- a/src/src/app/components/trip/trip.component.ts +++ b/src/src/app/components/trip/trip.component.ts @@ -1,13 +1,13 @@ -import { AfterViewInit, Component } from "@angular/core"; -import { ApiService } from "../../services/api.service"; -import { FormControl, FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { InputTextModule } from "primeng/inputtext"; -import { SkeletonModule } from "primeng/skeleton"; -import { FloatLabelModule } from "primeng/floatlabel"; -import * as L from "leaflet"; -import { antPath } from "leaflet-ant-path"; -import { TableModule } from "primeng/table"; +import { AfterViewInit, Component } from '@angular/core'; +import { ApiService } from '../../services/api.service'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { InputTextModule } from 'primeng/inputtext'; +import { SkeletonModule } from 'primeng/skeleton'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import * as L from 'leaflet'; +import { antPath } from 'leaflet-ant-path'; +import { TableModule } from 'primeng/table'; import { Trip, FlattenedTripItem, @@ -17,56 +17,41 @@ import { PackingItem, ChecklistItem, TripMember, -} from "../../types/trip"; -import { Place } from "../../types/poi"; -import { - createMap, - placeToMarker, - createClusterGroup, - tripDayMarker, - gpxToPolyline, -} from "../../shared/map"; -import { ActivatedRoute, Router } from "@angular/router"; -import { DialogService, DynamicDialogRef } from "primeng/dynamicdialog"; -import { TripPlaceSelectModalComponent } from "../../modals/trip-place-select-modal/trip-place-select-modal.component"; -import { TripCreateDayModalComponent } from "../../modals/trip-create-day-modal/trip-create-day-modal.component"; -import { TripCreateDayItemModalComponent } from "../../modals/trip-create-day-item-modal/trip-create-day-item-modal.component"; -import { TripCreateItemsModalComponent } from "../../modals/trip-create-items-modal/trip-create-items-modal.component"; -import { - combineLatest, - debounceTime, - forkJoin, - Observable, - of, - switchMap, - take, - tap, -} from "rxjs"; -import { YesNoModalComponent } from "../../modals/yes-no-modal/yes-no-modal.component"; -import { UtilsService } from "../../services/utils.service"; -import { TripCreateModalComponent } from "../../modals/trip-create-modal/trip-create-modal.component"; -import { AsyncPipe, CommonModule, DecimalPipe } from "@angular/common"; -import { MenuItem } from "primeng/api"; -import { MenuModule } from "primeng/menu"; -import { LinkifyPipe } from "../../shared/linkify.pipe"; -import { PlaceCreateModalComponent } from "../../modals/place-create-modal/place-create-modal.component"; -import { Settings } from "../../types/settings"; -import { DialogModule } from "primeng/dialog"; -import { ClipboardModule } from "@angular/cdk/clipboard"; -import { TooltipModule } from "primeng/tooltip"; -import { MultiSelectModule } from "primeng/multiselect"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { CheckboxChangeEvent, CheckboxModule } from "primeng/checkbox"; -import { TripCreatePackingModalComponent } from "../../modals/trip-create-packing-modal/trip-create-packing-modal.component"; -import { TripCreateChecklistModalComponent } from "../../modals/trip-create-checklist-modal/trip-create-checklist-modal.component"; -import { TripInviteMemberModalComponent } from "../../modals/trip-invite-member-modal/trip-invite-member-modal.component"; -import { calculateDistanceBetween } from "../../shared/haversine"; -import { orderByPipe } from "../../shared/order-by.pipe"; -import { TripNotesModalComponent } from "../../modals/trip-notes-modal/trip-notes-modal.component"; -import { TripArchiveModalComponent } from "../../modals/trip-archive-modal/trip-archive-modal.component"; +} from '../../types/trip'; +import { Place } from '../../types/poi'; +import { createMap, placeToMarker, createClusterGroup, tripDayMarker, gpxToPolyline } from '../../shared/map'; +import { ActivatedRoute, Router } from '@angular/router'; +import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { TripPlaceSelectModalComponent } from '../../modals/trip-place-select-modal/trip-place-select-modal.component'; +import { TripCreateDayModalComponent } from '../../modals/trip-create-day-modal/trip-create-day-modal.component'; +import { TripCreateDayItemModalComponent } from '../../modals/trip-create-day-item-modal/trip-create-day-item-modal.component'; +import { TripCreateItemsModalComponent } from '../../modals/trip-create-items-modal/trip-create-items-modal.component'; +import { combineLatest, debounceTime, forkJoin, Observable, of, switchMap, take, tap } from 'rxjs'; +import { YesNoModalComponent } from '../../modals/yes-no-modal/yes-no-modal.component'; +import { UtilsService } from '../../services/utils.service'; +import { TripCreateModalComponent } from '../../modals/trip-create-modal/trip-create-modal.component'; +import { AsyncPipe, CommonModule, DecimalPipe } from '@angular/common'; +import { MenuItem } from 'primeng/api'; +import { MenuModule } from 'primeng/menu'; +import { LinkifyPipe } from '../../shared/linkify.pipe'; +import { PlaceCreateModalComponent } from '../../modals/place-create-modal/place-create-modal.component'; +import { Settings } from '../../types/settings'; +import { DialogModule } from 'primeng/dialog'; +import { ClipboardModule } from '@angular/cdk/clipboard'; +import { TooltipModule } from 'primeng/tooltip'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { CheckboxChangeEvent, CheckboxModule } from 'primeng/checkbox'; +import { TripCreatePackingModalComponent } from '../../modals/trip-create-packing-modal/trip-create-packing-modal.component'; +import { TripCreateChecklistModalComponent } from '../../modals/trip-create-checklist-modal/trip-create-checklist-modal.component'; +import { TripInviteMemberModalComponent } from '../../modals/trip-invite-member-modal/trip-invite-member-modal.component'; +import { calculateDistanceBetween } from '../../shared/haversine'; +import { orderByPipe } from '../../shared/order-by.pipe'; +import { TripNotesModalComponent } from '../../modals/trip-notes-modal/trip-notes-modal.component'; +import { TripArchiveModalComponent } from '../../modals/trip-archive-modal/trip-archive-modal.component'; @Component({ - selector: "app-trip", + selector: 'app-trip', standalone: true, imports: [ CommonModule, @@ -88,8 +73,8 @@ import { TripArchiveModalComponent } from "../../modals/trip-archive-modal/trip- CheckboxModule, orderByPipe, ], - templateUrl: "./trip.component.html", - styleUrls: ["./trip.component.scss"], + templateUrl: './trip.component.html', + styleUrls: ['./trip.component.scss'], }) export class TripComponent implements AfterViewInit { tripSharedURL$?: Observable; @@ -129,18 +114,18 @@ export class TripComponent implements AfterViewInit { readonly menuTripActionsItems: MenuItem[] = [ { - label: "Lists", + label: 'Lists', items: [ { - label: "Checklist", - icon: "pi pi-check-square", + label: 'Checklist', + icon: 'pi pi-check-square', command: () => { this.openChecklist(); }, }, { - label: "Packing", - icon: "pi pi-briefcase", + label: 'Packing', + icon: 'pi pi-briefcase', command: () => { this.openPackingList(); }, @@ -148,18 +133,18 @@ export class TripComponent implements AfterViewInit { ], }, { - label: "Collaboration", + label: 'Collaboration', items: [ { - label: "Members", - icon: "pi pi-users", + label: 'Members', + icon: 'pi pi-users', command: () => { this.openMembersDialog(); }, }, { - label: "Share", - icon: "pi pi-share-alt", + label: 'Share', + icon: 'pi pi-share-alt', command: () => { this.shareDialogVisible = true; }, @@ -167,39 +152,39 @@ export class TripComponent implements AfterViewInit { ], }, { - label: "Trip", + label: 'Trip', items: [ { - label: "Pretty Print", - icon: "pi pi-print", + label: 'Pretty Print', + icon: 'pi pi-print', command: () => { this.togglePrint(); }, }, { - label: "Notes", - icon: "pi pi-info-circle", + label: 'Notes', + icon: 'pi pi-info-circle', command: () => { this.openTripNotesModal(); }, }, { - label: "Archive", - icon: "pi pi-box", + label: 'Archive', + icon: 'pi pi-box', command: () => { this.openArchiveTripModal(); }, }, { - label: "Edit", - icon: "pi pi-pencil", + label: 'Edit', + icon: 'pi pi-pencil', command: () => { this.editTrip(); }, }, { - label: "Delete", - icon: "pi pi-trash", + label: 'Delete', + icon: 'pi pi-trash', command: () => { this.deleteTrip(); }, @@ -209,11 +194,11 @@ export class TripComponent implements AfterViewInit { ]; readonly menuTripTableActionsItems: MenuItem[] = [ { - label: "Actions", + label: 'Actions', items: [ { - label: "Pretty Print", - icon: "pi pi-print", + label: 'Pretty Print', + icon: 'pi pi-print', command: () => { this.togglePrint(); }, @@ -221,18 +206,18 @@ export class TripComponent implements AfterViewInit { ], }, { - label: "Table", + label: 'Table', items: [ { - label: "Filter", - icon: "pi pi-filter", + label: 'Filter', + icon: 'pi pi-filter', command: () => { this.toggleFiltering(); }, }, { - label: "Group", - icon: "pi pi-arrow-down-left-and-arrow-up-right-to-center", + label: 'Group', + icon: 'pi pi-arrow-down-left-and-arrow-up-right-to-center', command: () => { this.tableExpandableMode = !this.tableExpandableMode; }, @@ -240,18 +225,18 @@ export class TripComponent implements AfterViewInit { ], }, { - label: "Directions", + label: 'Directions', items: [ { - label: "Highlight", - icon: "pi pi-directions", + label: 'Highlight', + icon: 'pi pi-directions', command: () => { this.toggleTripDaysHighlight(); }, }, { - label: "GMaps itinerary", - icon: "pi pi-car", + label: 'GMaps itinerary', + icon: 'pi pi-car', command: () => { this.tripToNavigation(); }, @@ -261,28 +246,28 @@ export class TripComponent implements AfterViewInit { ]; readonly menuTripDayActionsItems: MenuItem[] = [ { - label: "Actions", + label: 'Actions', items: [ { - label: "Item", - icon: "pi pi-plus", - iconClass: "text-blue-500!", + label: 'Item', + icon: 'pi pi-plus', + iconClass: 'text-blue-500!', command: () => { this.addItem(); }, }, { - label: "Edit", - icon: "pi pi-pencil", + label: 'Edit', + icon: 'pi pi-pencil', command: () => { if (!this.selectedTripDayForMenu) return; this.editDay(this.selectedTripDayForMenu); }, }, { - label: "Delete", - icon: "pi pi-trash", - iconClass: "text-red-500!", + label: 'Delete', + icon: 'pi pi-trash', + iconClass: 'text-red-500!', command: () => { if (!this.selectedTripDayForMenu) return; this.deleteDay(this.selectedTripDayForMenu); @@ -292,24 +277,18 @@ export class TripComponent implements AfterViewInit { }, ]; readonly tripTableColumns: string[] = [ - "day", - "time", - "text", - "place", - "comment", - "LatLng", - "price", - "status", - "distance", + 'day', + 'time', + 'text', + 'place', + 'comment', + 'LatLng', + 'price', + 'status', + 'distance', ]; - tripTableSelectedColumns: string[] = [ - "day", - "time", - "text", - "place", - "comment", - ]; - tripTableSearchInput = new FormControl(""); + tripTableSelectedColumns: string[] = ['day', 'time', 'text', 'place', 'comment']; + tripTableSearchInput = new FormControl(''); selectedTripDayForMenu?: TripDay; dayStatsCache = new Map(); @@ -323,14 +302,12 @@ export class TripComponent implements AfterViewInit { private route: ActivatedRoute, ) { this.statuses = this.utilsService.statuses; - this.tripTableSearchInput.valueChanges - .pipe(debounceTime(300), takeUntilDestroyed()) - .subscribe({ - next: (value) => { - if (value) this.flattenTripDayItems(value.toLowerCase()); - else this.flattenTripDayItems(); - }, - }); + this.tripTableSearchInput.valueChanges.pipe(debounceTime(300), takeUntilDestroyed()).subscribe({ + next: (value) => { + if (value) this.flattenTripDayItems(value.toLowerCase()); + else this.flattenTripDayItems(); + }, + }); } ngAfterViewInit(): void { @@ -338,7 +315,7 @@ export class TripComponent implements AfterViewInit { .pipe( take(1), tap((params) => { - const id = params.get("id"); + const id = params.get('id'); if (id) { this.loadTripData(+id); this.tripSharedURL$ = this.apiService.getSharedTripURL(+id); @@ -370,12 +347,10 @@ export class TripComponent implements AfterViewInit { initMap(settings: Settings): void { const contentMenuItems = [ { - text: "Copy coordinates", + text: 'Copy coordinates', callback: (e: any) => { const latlng = e.latlng; - navigator.clipboard.writeText( - `${parseFloat(latlng.lat).toFixed(5)}, ${parseFloat(latlng.lng).toFixed(5)}`, - ); + navigator.clipboard.writeText(`${parseFloat(latlng.lat).toFixed(5)}, ${parseFloat(latlng.lng).toFixed(5)}`); }, }, ]; @@ -388,7 +363,7 @@ export class TripComponent implements AfterViewInit { } back() { - this.router.navigateByUrl("/trips"); + this.router.navigateByUrl('/trips'); } togglePrint() { @@ -423,11 +398,7 @@ export class TripComponent implements AfterViewInit { if (!this.trip?.days) return []; return this.trip.days - .flatMap((day) => - day.items.filter((item) => - ["constraint", "pending"].includes(item.status as string), - ), - ) + .flatMap((day) => day.items.filter((item) => ['constraint', 'pending'].includes(item.status as string))) .map((item) => ({ ...item, status: this.statusToTripStatus(item.status as string), @@ -501,9 +472,7 @@ export class TripComponent implements AfterViewInit { setPlacesAndMarkers() { this.computePlacesUsedInTable(); - this.places = [...(this.trip?.places ?? [])].sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + this.places = [...(this.trip?.places ?? [])].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); this.markerClusterGroup?.clearLayers(); this.places.forEach((p) => { const marker = this._placeToMarker(p); @@ -512,12 +481,8 @@ export class TripComponent implements AfterViewInit { } _placeToMarker(place: Place): L.Marker { - const marker = placeToMarker( - place, - false, - !this.placesUsedInTable.has(place.id), - ); - marker.on("click", () => { + const marker = placeToMarker(place, false, !this.placesUsedInTable.has(place.id)); + marker.on('click', () => { this.onMapMarkerClick(place.id); marker.closeTooltip(); }); @@ -527,9 +492,7 @@ export class TripComponent implements AfterViewInit { resetMapBounds() { if (!this.places.length) { this.map?.fitBounds( - this.flattenedTripItems - .filter((i) => i.lat != null && i.lng != null) - .map((i) => [i.lat!, i.lng!]), + this.flattenedTripItems.filter((i) => i.lat != null && i.lng != null).map((i) => [i.lat!, i.lng!]), { padding: [30, 30] }, ); return; @@ -543,7 +506,7 @@ export class TripComponent implements AfterViewInit { toggleMapFullscreen() { this.isMapFullscreen = !this.isMapFullscreen; - document.body.classList.toggle("overflow-hidden"); + document.body.classList.toggle('overflow-hidden'); setTimeout(() => { this.map?.invalidateSize(); @@ -562,14 +525,12 @@ export class TripComponent implements AfterViewInit { return; } this.totalPrice = - this.trip?.days - .flatMap((d) => d.items) - .reduce((price, item) => price + (item.price ?? 0), 0) ?? 0; + this.trip?.days.flatMap((d) => d.items).reduce((price, item) => price + (item.price ?? 0), 0) ?? 0; } resetPlaceHighlightMarker() { if (this.tripMapHoveredElement) { - this.tripMapHoveredElement.classList.remove("list-hover"); + this.tripMapHoveredElement.classList.remove('list-hover'); this.tripMapHoveredElement = undefined; } @@ -586,8 +547,7 @@ export class TripComponent implements AfterViewInit { } placeHighlightMarker(item: any) { - if (this.tripMapHoveredElement || this.tripMapTemporaryMarker) - this.resetPlaceHighlightMarker(); + if (this.tripMapHoveredElement || this.tripMapTemporaryMarker) this.resetPlaceHighlightMarker(); let marker: L.Marker | undefined; this.markerClusterGroup?.eachLayer((layer: any) => { @@ -605,10 +565,7 @@ export class TripComponent implements AfterViewInit { // TripItem without place, but latlng this.tripMapTemporaryMarker = tripDayMarker(item).addTo(this.map!); if (this.tripMapGpxLayer) { - this.map?.fitBounds( - [[item.lat, item.lng], (this.tripMapGpxLayer as any).getBounds()], - { padding: [30, 30] }, - ); + this.map?.fitBounds([[item.lat, item.lng], (this.tripMapGpxLayer as any).getBounds()], { padding: [30, 30] }); } else this.map?.fitBounds([[item.lat, item.lng]], { padding: [60, 60] }); return; } @@ -617,18 +574,16 @@ export class TripComponent implements AfterViewInit { const markerElement = marker.getElement() as HTMLElement; // search for Marker. If 'null', is inside Cluster if (markerElement) { // marker, not clustered - markerElement.classList.add("list-hover"); + markerElement.classList.add('list-hover'); this.tripMapHoveredElement = markerElement; targetLatLng = marker.getLatLng(); } else { // marker is clustered - const parentCluster = (this.markerClusterGroup as any).getVisibleParent( - marker, - ); + const parentCluster = (this.markerClusterGroup as any).getVisibleParent(marker); if (parentCluster) { const clusterEl = parentCluster.getElement(); if (clusterEl) { - clusterEl.classList.add("list-hover"); + clusterEl.classList.add('list-hover'); this.tripMapHoveredElement = clusterEl; } targetLatLng = parentCluster.getLatLng(); @@ -692,11 +647,7 @@ export class TripComponent implements AfterViewInit { .filter((n) => n !== undefined); if (items.length < 2) { - this.utilsService.toast( - "info", - "Info", - "Not enough values to map an itinerary", - ); + this.utilsService.toast('info', 'Info', 'Not enough values to map an itinerary'); return; } @@ -708,20 +659,20 @@ export class TripComponent implements AfterViewInit { const layGroup = L.featureGroup(); const COLORS: string[] = [ - "#e6194b", - "#3cb44b", - "#ffe119", - "#4363d8", - "#9a6324", - "#f58231", - "#911eb4", - "#46f0f0", - "#f032e6", - "#bcf60c", - "#fabebe", - "#008080", - "#e6beff", - "#808000", + '#e6194b', + '#3cb44b', + '#ffe119', + '#4363d8', + '#9a6324', + '#f58231', + '#911eb4', + '#46f0f0', + '#f032e6', + '#bcf60c', + '#fabebe', + '#008080', + '#e6beff', + '#808000', ]; let prevPoint: [number, number] | null = null; @@ -732,7 +683,7 @@ export class TripComponent implements AfterViewInit { dashArray: [10, 20], weight: 5, color: COLORS[idx % COLORS.length], - pulseColor: "#FFFFFF", + pulseColor: '#FFFFFF', paused: false, reverse: false, hardwareAccelerated: true, @@ -783,9 +734,7 @@ export class TripComponent implements AfterViewInit { const idx = this.trip?.days.findIndex((d) => d.id === day_id); if (!this.trip || idx === undefined || idx == -1) return; - const data = this.trip.days[idx].items.sort((a, b) => - a.time < b.time ? -1 : a.time > b.time ? 1 : 0, - ); + const data = this.trip.days[idx].items.sort((a, b) => (a.time < b.time ? -1 : a.time > b.time ? 1 : 0)); const items = data .map((item) => { if (item.lat && item.lng) @@ -811,11 +760,7 @@ export class TripComponent implements AfterViewInit { .filter((n) => n !== undefined); if (items.length < 2) { - this.utilsService.toast( - "info", - "Info", - "Not enough values to map an itinerary", - ); + this.utilsService.toast('info', 'Info', 'Not enough values to map an itinerary'); return; } @@ -830,8 +775,8 @@ export class TripComponent implements AfterViewInit { delay: 400, dashArray: [10, 20], weight: 5, - color: "#0000FF", - pulseColor: "#FFFFFF", + color: '#0000FF', + pulseColor: '#FFFFFF', paused: false, reverse: false, hardwareAccelerated: true, @@ -869,15 +814,9 @@ export class TripComponent implements AfterViewInit { } onMapMarkerClick(place_id: number) { - const item = this.flattenedTripItems.find( - (i) => i.place && i.place.id == place_id, - ); + const item = this.flattenedTripItems.find((i) => i.place && i.place.id == place_id); if (!item) { - this.utilsService.toast( - "info", - "Place not used", - "The place is not used in the table", - ); + this.utilsService.toast('info', 'Place not used', 'The place is not used in the table'); return; } @@ -888,12 +827,12 @@ export class TripComponent implements AfterViewInit { deleteTrip() { const modal = this.dialogService.open(YesNoModalComponent, { - header: "Confirm deletion", + header: 'Confirm deletion', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, data: `Delete ${this.trip?.name} ? This will delete everything.`, })!; @@ -906,7 +845,7 @@ export class TripComponent implements AfterViewInit { .pipe(take(1)) .subscribe({ next: () => { - this.router.navigateByUrl("/trips"); + this.router.navigateByUrl('/trips'); }, }); }, @@ -914,21 +853,18 @@ export class TripComponent implements AfterViewInit { } editTrip() { - const modal: DynamicDialogRef = this.dialogService.open( - TripCreateModalComponent, - { - header: "Update Trip", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "50vw", - breakpoints: { - "640px": "80vw", - }, - data: { trip: this.trip }, + const modal: DynamicDialogRef = this.dialogService.open(TripCreateModalComponent, { + header: 'Update Trip', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '50vw', + breakpoints: { + '640px': '80vw', }, - )!; + data: { trip: this.trip }, + })!; modal.onClose.pipe(take(1)).subscribe({ next: (new_trip: Trip | null) => { @@ -946,12 +882,12 @@ export class TripComponent implements AfterViewInit { openUnarchiveTripModal() { const modal = this.dialogService.open(YesNoModalComponent, { - header: "Restore Trip", + header: 'Restore Trip', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, data: `Restore ${this.trip?.name} ?`, })!; @@ -977,10 +913,10 @@ export class TripComponent implements AfterViewInit { modal: true, closable: true, dismissableMask: true, - width: "30vw", + width: '30vw', breakpoints: { - "1024px": "60vw", - "640px": "90vw", + '1024px': '60vw', + '640px': '90vw', }, data: this.trip, })!; @@ -989,10 +925,7 @@ export class TripComponent implements AfterViewInit { next: (review: string) => { if (review === undefined) return; this.apiService - .putTrip( - { archived: !currentArchiveStatus, archival_review: review }, - this.trip?.id!, - ) + .putTrip({ archived: !currentArchiveStatus, archival_review: review }, this.trip?.id!) .pipe(take(1)) .subscribe({ next: (trip) => (this.trip = trip), @@ -1004,21 +937,18 @@ export class TripComponent implements AfterViewInit { addDay() { if (!this.trip) return; - const modal: DynamicDialogRef = this.dialogService.open( - TripCreateDayModalComponent, - { - header: "Create Day", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "50vw", - data: { days: this.trip.days }, - breakpoints: { - "640px": "80vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(TripCreateDayModalComponent, { + header: 'Create Day', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '50vw', + data: { days: this.trip.days }, + breakpoints: { + '640px': '80vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (day: TripDay | null) => { @@ -1042,14 +972,14 @@ export class TripComponent implements AfterViewInit { // TODO: More services // const url = `http://maps.apple.com/?daddr=${this.selectedItem.lat},${this.selectedItem.lng}`; const url = `https://www.google.com/maps/dir/?api=1&destination=${this.selectedItem.lat},${this.selectedItem.lng}`; - window.open(url, "_blank"); + window.open(url, '_blank'); } downloadItemGPX() { if (!this.selectedItem?.gpx) return; const dataBlob = new Blob([this.selectedItem.gpx]); const downloadURL = URL.createObjectURL(dataBlob); - const link = document.createElement("a"); + const link = document.createElement('a'); link.href = downloadURL; link.download = `TRIP_${this.trip?.name}_${this.selectedItem.text}.gpx`; link.click(); @@ -1060,47 +990,40 @@ export class TripComponent implements AfterViewInit { tripDayToNavigation(day_id: number) { const idx = this.trip?.days.findIndex((d) => d.id === day_id); if (!this.trip || idx === undefined || idx == -1) return; - const data = this.trip.days[idx].items.sort((a, b) => - a.time < b.time ? -1 : a.time > b.time ? 1 : 0, - ); + const data = this.trip.days[idx].items.sort((a, b) => (a.time < b.time ? -1 : a.time > b.time ? 1 : 0)); const items = data.filter((item) => item.lat && item.lng); if (!items.length) return; - const waypoints = items.map((item) => `${item.lat},${item.lng}`).join("/"); + const waypoints = items.map((item) => `${item.lat},${item.lng}`).join('/'); const url = `https://www.google.com/maps/dir/${waypoints}`; - window.open(url, "_blank"); + window.open(url, '_blank'); } tripToNavigation() { // TODO: More services - const items = this.flattenedTripItems.filter( - (item) => item.lat && item.lng, - ); + const items = this.flattenedTripItems.filter((item) => item.lat && item.lng); if (!items.length) return; - const waypoints = items.map((item) => `${item.lat},${item.lng}`).join("/"); + const waypoints = items.map((item) => `${item.lat},${item.lng}`).join('/'); const url = `https://www.google.com/maps/dir/${waypoints}`; - window.open(url, "_blank"); + window.open(url, '_blank'); } editDay(day: TripDay) { if (!this.trip) return; - const modal: DynamicDialogRef = this.dialogService.open( - TripCreateDayModalComponent, - { - header: "Create Day", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "50vw", - data: { day: day, days: this.trip.days }, - breakpoints: { - "640px": "80vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(TripCreateDayModalComponent, { + header: 'Create Day', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '50vw', + data: { day: day, days: this.trip.days }, + breakpoints: { + '640px': '80vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (day: TripDay | null) => { @@ -1126,12 +1049,12 @@ export class TripComponent implements AfterViewInit { if (!this.trip) return; const modal = this.dialogService.open(YesNoModalComponent, { - header: "Confirm deletion", + header: 'Confirm deletion', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, data: `Delete ${day.label} ? This will delete everything for this day.`, })!; @@ -1159,21 +1082,18 @@ export class TripComponent implements AfterViewInit { manageTripPlaces() { if (!this.trip) return; - const modal: DynamicDialogRef = this.dialogService.open( - TripPlaceSelectModalComponent, - { - header: "Select Place(s)", - modal: true, - appendTo: "body", - closable: true, - width: "50vw", - data: { places: this.places }, - breakpoints: { - "960px": "80vw", - "640px": "90vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(TripPlaceSelectModalComponent, { + header: 'Select Place(s)', + modal: true, + appendTo: 'body', + closable: true, + width: '50vw', + data: { places: this.places }, + breakpoints: { + '960px': '80vw', + '640px': '90vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (places: Place[] | null) => { @@ -1196,37 +1116,30 @@ export class TripComponent implements AfterViewInit { addItem(day_id?: number) { if (!this.trip) return; - const modal: DynamicDialogRef = this.dialogService.open( - TripCreateDayItemModalComponent, - { - header: "Create Item", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "75vw", - breakpoints: { - "1260px": "90vw", - }, - data: { - places: this.places, - days: this.trip.days, - selectedDay: day_id, - members: this.tripMembers, - }, + const modal: DynamicDialogRef = this.dialogService.open(TripCreateDayItemModalComponent, { + header: 'Create Item', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '75vw', + breakpoints: { + '1260px': '90vw', }, - )!; + data: { + places: this.places, + days: this.trip.days, + selectedDay: day_id, + members: this.tripMembers, + }, + })!; modal.onClose.pipe(take(1)).subscribe({ next: (item: (TripItem & { day_id: number[] }) | null) => { if (!item) return; const obs$ = item.day_id.map((day_id) => - this.apiService.postTripDayItem( - { ...item, day_id }, - this.trip!.id!, - day_id, - ), + this.apiService.postTripDayItem({ ...item, day_id }, this.trip!.id!, day_id), ); forkJoin(obs$) @@ -1234,9 +1147,7 @@ export class TripComponent implements AfterViewInit { .subscribe({ next: (items: TripItem[]) => { items.forEach((item) => { - const idx = this.trip!.days.findIndex( - (d) => d.id == item.day_id, - ); + const idx = this.trip!.days.findIndex((d) => d.id == item.day_id); if (idx === -1) return; const td: TripDay = this.trip!.days[idx]; @@ -1261,29 +1172,26 @@ export class TripComponent implements AfterViewInit { editItem(item: TripItem) { if (!this.trip) return; - const modal: DynamicDialogRef = this.dialogService.open( - TripCreateDayItemModalComponent, - { - header: "Update Item", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "75vw", - breakpoints: { - "1260px": "90vw", - }, - data: { - places: this.places, - days: this.trip?.days, - item: { - ...item, - status: item.status ? (item.status as TripStatus).label : null, - }, - members: this.tripMembers, - }, + const modal: DynamicDialogRef = this.dialogService.open(TripCreateDayItemModalComponent, { + header: 'Update Item', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '75vw', + breakpoints: { + '1260px': '90vw', }, - )!; + data: { + places: this.places, + days: this.trip?.days, + item: { + ...item, + status: item.status ? (item.status as TripStatus).label : null, + }, + members: this.tripMembers, + }, + })!; modal.onClose.pipe(take(1)).subscribe({ next: (updated: TripItem | null) => { @@ -1302,12 +1210,12 @@ export class TripComponent implements AfterViewInit { deleteItem(item: TripItem) { const modal = this.dialogService.open(YesNoModalComponent, { - header: "Confirm deletion", + header: 'Confirm deletion', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, data: `Delete ${item.text.substring(0, 50)} ? This will delete everything for this day.`, })!; @@ -1330,29 +1238,24 @@ export class TripComponent implements AfterViewInit { addItems() { if (!this.trip) return; - const modal: DynamicDialogRef = this.dialogService.open( - TripCreateItemsModalComponent, - { - header: "Create Items", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "75vw", - breakpoints: { - "1260px": "90vw", - }, - data: { days: this.trip.days }, + const modal: DynamicDialogRef = this.dialogService.open(TripCreateItemsModalComponent, { + header: 'Create Items', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '75vw', + breakpoints: { + '1260px': '90vw', }, - )!; + data: { days: this.trip.days }, + })!; modal.onClose.pipe(take(1)).subscribe({ next: (items: TripItem[] | null) => { if (!items?.length) return; const day_id = items[0].day_id; - const obs$ = items.map((item) => - this.apiService.postTripDayItem(item, this.trip!.id!, item.day_id), - ); + const obs$ = items.map((item) => this.apiService.postTripDayItem(item, this.trip!.id!, item.day_id)); forkJoin(obs$) .pipe(take(1)) @@ -1371,21 +1274,18 @@ export class TripComponent implements AfterViewInit { } addPlace() { - const modal: DynamicDialogRef = this.dialogService.open( - PlaceCreateModalComponent, - { - header: "Create Place", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "55vw", - breakpoints: { - "1920px": "70vw", - "1260px": "90vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(PlaceCreateModalComponent, { + header: 'Create Place', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '55vw', + breakpoints: { + '1920px': '70vw', + '1260px': '90vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (place: Place | null) => { @@ -1395,10 +1295,7 @@ export class TripComponent implements AfterViewInit { .postPlace(place) .pipe( switchMap((createdPlace: Place) => - this.apiService.putTrip( - { place_ids: [createdPlace, ...this.places].map((p) => p.id) }, - this.trip?.id!, - ), + this.apiService.putTrip({ place_ids: [createdPlace, ...this.places].map((p) => p.id) }, this.trip?.id!), ), take(1), ) @@ -1414,24 +1311,21 @@ export class TripComponent implements AfterViewInit { } editPlace(pEdit: Place) { - const modal: DynamicDialogRef = this.dialogService.open( - PlaceCreateModalComponent, - { - header: "Edit Place", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "55vw", - breakpoints: { - "1920px": "70vw", - "1260px": "90vw", - }, - data: { - place: { ...pEdit, category: pEdit.category.id }, - }, + const modal: DynamicDialogRef = this.dialogService.open(PlaceCreateModalComponent, { + header: 'Edit Place', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '55vw', + breakpoints: { + '1920px': '70vw', + '1260px': '90vw', }, - )!; + data: { + place: { ...pEdit, category: pEdit.category.id }, + }, + })!; modal.onClose.pipe(take(1)).subscribe({ next: (p: Place | null) => { @@ -1446,9 +1340,7 @@ export class TripComponent implements AfterViewInit { const idx = places.findIndex((p) => p.id == place.id); if (idx > -1) places.splice(idx, 1, place); places.push(place); - places.sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + places.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); if (this.selectedItem?.place) this.selectedItem.place = place; }, }); @@ -1487,9 +1379,7 @@ export class TripComponent implements AfterViewInit { if (this.selectedItem && this.selectedItem.id === old.id) { this.selectedItem = { ...updated, - status: updated.status - ? this.statusToTripStatus(updated.status as string) - : undefined, + status: updated.status ? this.statusToTripStatus(updated.status as string) : undefined, }; } } @@ -1538,12 +1428,12 @@ export class TripComponent implements AfterViewInit { if (!this.trip) return; const modal = this.dialogService.open(YesNoModalComponent, { - header: "Confirm deletion", + header: 'Confirm deletion', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, data: `Stop sharing ${this.trip.name} ?`, })!; @@ -1583,21 +1473,18 @@ export class TripComponent implements AfterViewInit { addPackingItem() { if (!this.trip) return; - const modal: DynamicDialogRef = this.dialogService.open( - TripCreatePackingModalComponent, - { - header: "Create Packing", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "40vw", - breakpoints: { - "1260px": "70vw", - "600px": "90vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(TripCreatePackingModalComponent, { + header: 'Create Packing', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '40vw', + breakpoints: { + '1260px': '70vw', + '600px': '90vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (item: PackingItem | null) => { @@ -1632,12 +1519,12 @@ export class TripComponent implements AfterViewInit { deletePackingItem(item: PackingItem) { const modal = this.dialogService.open(YesNoModalComponent, { - header: "Confirm deletion", + header: 'Confirm deletion', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, data: `Delete ${item.text.substring(0, 50)} ?`, })!; @@ -1661,24 +1548,13 @@ export class TripComponent implements AfterViewInit { computeDispPackingList() { const sorted: PackingItem[] = [...this.packingList].sort((a, b) => - a.packed !== b.packed - ? a.packed - ? 1 - : -1 - : a.text < b.text - ? -1 - : a.text > b.text - ? 1 - : 0, + a.packed !== b.packed ? (a.packed ? 1 : -1) : a.text < b.text ? -1 : a.text > b.text ? 1 : 0, ); - this.dispPackingList = sorted.reduce>( - (acc, item) => { - (acc[item.category] ??= []).push(item); - return acc; - }, - {}, - ); + this.dispPackingList = sorted.reduce>((acc, item) => { + (acc[item.category] ??= []).push(item); + return acc; + }, {}); } openChecklist() { @@ -1699,21 +1575,18 @@ export class TripComponent implements AfterViewInit { addChecklistItem() { if (!this.trip) return; - const modal: DynamicDialogRef = this.dialogService.open( - TripCreateChecklistModalComponent, - { - header: "Create item", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "40vw", - breakpoints: { - "1260px": "70vw", - "600px": "90vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(TripCreateChecklistModalComponent, { + header: 'Create item', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '40vw', + breakpoints: { + '1260px': '70vw', + '600px': '90vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (item: ChecklistItem | null) => { @@ -1746,12 +1619,12 @@ export class TripComponent implements AfterViewInit { deleteChecklistItem(item: ChecklistItem) { const modal = this.dialogService.open(YesNoModalComponent, { - header: "Confirm deletion", + header: 'Confirm deletion', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, data: `Delete ${item.text.substring(0, 50)} ?`, })!; @@ -1764,9 +1637,7 @@ export class TripComponent implements AfterViewInit { .pipe(take(1)) .subscribe({ next: () => { - const index = this.checklistItems.findIndex( - (p) => p.id == item.id, - ); + const index = this.checklistItems.findIndex((p) => p.id == item.id); if (index > -1) this.checklistItems.splice(index, 1); }, }); @@ -1804,21 +1675,18 @@ export class TripComponent implements AfterViewInit { addMember() { if (!this.trip) return; - const modal: DynamicDialogRef = this.dialogService.open( - TripInviteMemberModalComponent, - { - header: "Invite member", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "40vw", - breakpoints: { - "1260px": "70vw", - "600px": "90vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(TripInviteMemberModalComponent, { + header: 'Invite member', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '40vw', + breakpoints: { + '1260px': '70vw', + '600px': '90vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (user: string | null) => { @@ -1838,12 +1706,12 @@ export class TripComponent implements AfterViewInit { deleteMember(username: string) { const modal = this.dialogService.open(YesNoModalComponent, { - header: "Confirm deletion", + header: 'Confirm deletion', modal: true, closable: true, dismissableMask: true, breakpoints: { - "640px": "90vw", + '640px': '90vw', }, data: `Delete ${username.substring(0, 50)} from Trip ?`, })!; @@ -1856,9 +1724,7 @@ export class TripComponent implements AfterViewInit { .pipe(take(1)) .subscribe({ next: () => { - const index = this.tripMembers.findIndex( - (p) => p.user == username, - ); + const index = this.tripMembers.findIndex((p) => p.user == username); if (index > -1) this.tripMembers.splice(index, 1); }, }); @@ -1868,14 +1734,14 @@ export class TripComponent implements AfterViewInit { openTripNotesModal() { const modal = this.dialogService.open(TripNotesModalComponent, { - header: "Notes", + header: 'Notes', modal: true, closable: true, dismissableMask: true, - width: "40vw", + width: '40vw', breakpoints: { - "1024px": "70vw", - "640px": "90vw", + '1024px': '70vw', + '640px': '90vw', }, data: this.trip?.notes, })!; diff --git a/src/src/app/components/trips/trips.component.ts b/src/src/app/components/trips/trips.component.ts index 2c2f46b..3989062 100644 --- a/src/src/app/components/trips/trips.component.ts +++ b/src/src/app/components/trips/trips.component.ts @@ -1,14 +1,14 @@ -import { Component, OnInit } from "@angular/core"; -import { ApiService } from "../../services/api.service"; -import { ButtonModule } from "primeng/button"; -import { SkeletonModule } from "primeng/skeleton"; -import { TripBase, TripInvitation } from "../../types/trip"; -import { DialogService, DynamicDialogRef } from "primeng/dynamicdialog"; -import { TripCreateModalComponent } from "../../modals/trip-create-modal/trip-create-modal.component"; -import { Router } from "@angular/router"; -import { forkJoin, take } from "rxjs"; -import { DatePipe } from "@angular/common"; -import { DialogModule } from "primeng/dialog"; +import { Component, OnInit } from '@angular/core'; +import { ApiService } from '../../services/api.service'; +import { ButtonModule } from 'primeng/button'; +import { SkeletonModule } from 'primeng/skeleton'; +import { TripBase, TripInvitation } from '../../types/trip'; +import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { TripCreateModalComponent } from '../../modals/trip-create-modal/trip-create-modal.component'; +import { Router } from '@angular/router'; +import { forkJoin, take } from 'rxjs'; +import { DatePipe } from '@angular/common'; +import { DialogModule } from 'primeng/dialog'; interface TripBaseWithDates extends TripBase { from?: Date; @@ -16,11 +16,11 @@ interface TripBaseWithDates extends TripBase { } @Component({ - selector: "app-trips", + selector: 'app-trips', standalone: true, imports: [SkeletonModule, ButtonModule, DialogModule, DatePipe], - templateUrl: "./trips.component.html", - styleUrls: ["./trips.component.scss"], + templateUrl: './trips.component.html', + styleUrls: ['./trips.component.scss'], }) export class TripsComponent implements OnInit { trips: TripBase[] = []; @@ -53,7 +53,7 @@ export class TripsComponent implements OnInit { } gotoMap() { - this.router.navigateByUrl("/"); + this.router.navigateByUrl('/'); } viewTrip(id: number) { @@ -61,20 +61,17 @@ export class TripsComponent implements OnInit { } addTrip() { - const modal: DynamicDialogRef = this.dialogService.open( - TripCreateModalComponent, - { - header: "Create Trip", - modal: true, - appendTo: "body", - closable: true, - dismissableMask: true, - width: "50vw", - breakpoints: { - "960px": "80vw", - }, + const modal: DynamicDialogRef = this.dialogService.open(TripCreateModalComponent, { + header: 'Create Trip', + modal: true, + appendTo: 'body', + closable: true, + dismissableMask: true, + width: '50vw', + breakpoints: { + '960px': '80vw', }, - )!; + })!; modal.onClose.pipe(take(1)).subscribe({ next: (trip: TripBaseWithDates | null) => { @@ -85,12 +82,8 @@ export class TripsComponent implements OnInit { let dayCount = 0; if (trip.from && trip.to) { - const obs$ = this.generateDaysLabel(trip.from!, trip.to!).map( - (label) => - this.apiService.postTripDay( - { id: -1, label: label, items: [] }, - new_trip.id, - ), + const obs$ = this.generateDaysLabel(trip.from!, trip.to!).map((label) => + this.apiService.postTripDay({ id: -1, label: label, items: [] }, new_trip.id), ); dayCount = obs$.length; forkJoin(obs$).pipe(take(1)).subscribe(); @@ -116,32 +109,17 @@ export class TripsComponent implements OnInit { generateDaysLabel(from: Date, to: Date): string[] { const labels: string[] = []; - const sameMonth = - from.getFullYear() === to.getFullYear() && - from.getMonth() === to.getMonth(); + const sameMonth = from.getFullYear() === to.getFullYear() && from.getMonth() === to.getMonth(); - const months = [ - "Jan.", - "Feb.", - "Mar.", - "Apr.", - "May.", - "Jun.", - "Jul.", - "Aug.", - "Sep.", - "Oct.", - "Nov.", - "Dec.", - ]; + const months = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May.', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.']; const current = new Date(from); while (current <= to) { - let label = ""; + let label = ''; if (sameMonth) { - label = `${current.getDate().toString().padStart(2, "0")} ${months[current.getMonth()]}`; + label = `${current.getDate().toString().padStart(2, '0')} ${months[current.getMonth()]}`; } else { - label = `${(current.getMonth() + 1).toString().padStart(2, "0")}/${current.getDate().toString().padStart(2, "0")}`; + label = `${(current.getMonth() + 1).toString().padStart(2, '0')}/${current.getDate().toString().padStart(2, '0')}`; } labels.push(label); current.setDate(current.getDate() + 1); diff --git a/src/src/app/decs.d.ts b/src/src/app/decs.d.ts index 37180fd..62ed978 100644 --- a/src/src/app/decs.d.ts +++ b/src/src/app/decs.d.ts @@ -1 +1 @@ -declare module "leaflet-ant-path"; +declare module 'leaflet-ant-path'; diff --git a/src/src/app/modals/batch-create-modal/batch-create-modal.component.ts b/src/src/app/modals/batch-create-modal/batch-create-modal.component.ts index 71721a7..e723572 100644 --- a/src/src/app/modals/batch-create-modal/batch-create-modal.component.ts +++ b/src/src/app/modals/batch-create-modal/batch-create-modal.component.ts @@ -1,19 +1,19 @@ -import { Component } from "@angular/core"; -import { FormControl, ReactiveFormsModule } from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { TextareaModule } from "primeng/textarea"; +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { TextareaModule } from 'primeng/textarea'; @Component({ - selector: "app-batch-create-modal", + selector: 'app-batch-create-modal', imports: [FloatLabelModule, ButtonModule, ReactiveFormsModule, TextareaModule], standalone: true, - templateUrl: "./batch-create-modal.component.html", - styleUrl: "./batch-create-modal.component.scss", + templateUrl: './batch-create-modal.component.html', + styleUrl: './batch-create-modal.component.scss', }) export class BatchCreateModalComponent { - batchInput = new FormControl(""); + batchInput = new FormControl(''); constructor(private ref: DynamicDialogRef) {} diff --git a/src/src/app/modals/category-create-modal/category-create-modal.component.ts b/src/src/app/modals/category-create-modal/category-create-modal.component.ts index c479335..ce3f7a6 100644 --- a/src/src/app/modals/category-create-modal/category-create-modal.component.ts +++ b/src/src/app/modals/category-create-modal/category-create-modal.component.ts @@ -1,31 +1,19 @@ -import { Component } from "@angular/core"; -import { - FormBuilder, - FormGroup, - ReactiveFormsModule, - Validators, -} from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { InputTextModule } from "primeng/inputtext"; -import { FocusTrapModule } from "primeng/focustrap"; -import { ColorPickerModule } from "primeng/colorpicker"; -import { Category } from "../../types/poi"; +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputTextModule } from 'primeng/inputtext'; +import { FocusTrapModule } from 'primeng/focustrap'; +import { ColorPickerModule } from 'primeng/colorpicker'; +import { Category } from '../../types/poi'; @Component({ - selector: "app-category-create-modal", - imports: [ - FloatLabelModule, - InputTextModule, - ButtonModule, - ColorPickerModule, - ReactiveFormsModule, - FocusTrapModule, - ], + selector: 'app-category-create-modal', + imports: [FloatLabelModule, InputTextModule, ButtonModule, ColorPickerModule, ReactiveFormsModule, FocusTrapModule], standalone: true, - templateUrl: "./category-create-modal.component.html", - styleUrl: "./category-create-modal.component.scss", + templateUrl: './category-create-modal.component.html', + styleUrl: './category-create-modal.component.scss', }) export class CategoryCreateModalComponent { categoryForm: FormGroup; @@ -38,14 +26,11 @@ export class CategoryCreateModalComponent { ) { this.categoryForm = this.fb.group({ id: -1, - name: ["", Validators.required], + name: ['', Validators.required], color: [ - "#000000", + '#000000', { - validators: [ - Validators.required, - Validators.pattern("\#[abcdefABCDEF0-9]{6}"), - ], + validators: [Validators.required, Validators.pattern('\#[abcdefABCDEF0-9]{6}')], }, ], image: null, @@ -58,8 +43,8 @@ export class CategoryCreateModalComponent { closeDialog() { // Normalize data for API POST let ret = this.categoryForm.value; - if (!ret["name"]) return; - if (!this.updatedImage) delete ret["image"]; + if (!ret['name']) return; + if (!this.updatedImage) delete ret['image']; this.ref.close(ret); } @@ -70,8 +55,8 @@ export class CategoryCreateModalComponent { const reader = new FileReader(); reader.onload = (e) => { - this.categoryForm.get("image")?.setValue(e.target?.result as string); - this.categoryForm.get("image")?.markAsDirty(); + this.categoryForm.get('image')?.setValue(e.target?.result as string); + this.categoryForm.get('image')?.markAsDirty(); this.updatedImage = true; }; @@ -80,7 +65,7 @@ export class CategoryCreateModalComponent { } clearImage() { - this.categoryForm.get("image")?.setValue(null); + this.categoryForm.get('image')?.setValue(null); this.updatedImage = false; } } diff --git a/src/src/app/modals/place-create-modal/place-create-modal.component.ts b/src/src/app/modals/place-create-modal/place-create-modal.component.ts index 69b7e9b..8961c69 100644 --- a/src/src/app/modals/place-create-modal/place-create-modal.component.ts +++ b/src/src/app/modals/place-create-modal/place-create-modal.component.ts @@ -1,32 +1,27 @@ -import { Component } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { - FormBuilder, - FormGroup, - ReactiveFormsModule, - Validators, -} from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { InputTextModule } from "primeng/inputtext"; -import { SelectModule } from "primeng/select"; -import { TextareaModule } from "primeng/textarea"; -import { Observable } from "rxjs"; -import { AsyncPipe } from "@angular/common"; -import { InputGroupModule } from "primeng/inputgroup"; -import { InputGroupAddonModule } from "primeng/inputgroupaddon"; -import { ApiService } from "../../services/api.service"; -import { UtilsService } from "../../services/utils.service"; -import { FocusTrapModule } from "primeng/focustrap"; -import { Category, Place } from "../../types/poi"; -import { CheckboxModule } from "primeng/checkbox"; -import { TooltipModule } from "primeng/tooltip"; -import { checkAndParseLatLng, formatLatLng } from "../../shared/latlng-parser"; -import { InputNumberModule } from "primeng/inputnumber"; +import { Component } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputTextModule } from 'primeng/inputtext'; +import { SelectModule } from 'primeng/select'; +import { TextareaModule } from 'primeng/textarea'; +import { Observable } from 'rxjs'; +import { AsyncPipe } from '@angular/common'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { ApiService } from '../../services/api.service'; +import { UtilsService } from '../../services/utils.service'; +import { FocusTrapModule } from 'primeng/focustrap'; +import { Category, Place } from '../../types/poi'; +import { CheckboxModule } from 'primeng/checkbox'; +import { TooltipModule } from 'primeng/tooltip'; +import { checkAndParseLatLng, formatLatLng } from '../../shared/latlng-parser'; +import { InputNumberModule } from 'primeng/inputnumber'; @Component({ - selector: "app-place-create-modal", + selector: 'app-place-create-modal', imports: [ FloatLabelModule, InputTextModule, @@ -43,8 +38,8 @@ import { InputNumberModule } from "primeng/inputnumber"; FocusTrapModule, ], standalone: true, - templateUrl: "./place-create-modal.component.html", - styleUrl: "./place-create-modal.component.scss", + templateUrl: './place-create-modal.component.html', + styleUrl: './place-create-modal.component.scss', }) export class PlaceCreateModalComponent { placeForm: FormGroup; @@ -66,32 +61,27 @@ export class PlaceCreateModalComponent { this.placeForm = this.fb.group({ id: -1, - name: ["", Validators.required], - place: ["", { validators: Validators.required, updateOn: "blur" }], + name: ['', Validators.required], + place: ['', { validators: Validators.required, updateOn: 'blur' }], lat: [ - "", + '', { - validators: [ - Validators.required, - Validators.pattern("-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)"), - ], - updateOn: "blur", + validators: [Validators.required, Validators.pattern('-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)')], + updateOn: 'blur', }, ], lng: [ - "", + '', { validators: [ Validators.required, - Validators.pattern( - "-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)", - ), + Validators.pattern('-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)'), ], }, ], category: [null, Validators.required], description: null, - duration: [null, Validators.pattern("\\d+")], + duration: [null, Validators.pattern('\\d+')], price: null, allowdog: false, visited: false, @@ -104,12 +94,11 @@ export class PlaceCreateModalComponent { if (patchValue) this.placeForm.patchValue(patchValue); this.placeForm - .get("place") + .get('place') ?.valueChanges.pipe(takeUntilDestroyed()) .subscribe({ next: (value: string) => { - const isGoogleMapsURL = - /^(https?:\/\/)?(www\.)?google\.[a-z.]+\/maps/.test(value); + const isGoogleMapsURL = /^(https?:\/\/)?(www\.)?google\.[a-z.]+\/maps/.test(value); if (isGoogleMapsURL) { this.parseGoogleMapsUrl(value); } @@ -117,7 +106,7 @@ export class PlaceCreateModalComponent { }); this.placeForm - .get("lat") + .get('lat') ?.valueChanges.pipe(takeUntilDestroyed()) .subscribe({ next: (value: string) => { @@ -125,8 +114,8 @@ export class PlaceCreateModalComponent { if (!result) return; const [lat, lng] = result; - const latControl = this.placeForm.get("lat"); - const lngControl = this.placeForm.get("lng"); + const latControl = this.placeForm.get('lat'); + const lngControl = this.placeForm.get('lng'); latControl?.setValue(formatLatLng(lat).trim(), { emitEvent: false }); lngControl?.setValue(formatLatLng(lng).trim(), { emitEvent: false }); @@ -140,15 +129,15 @@ export class PlaceCreateModalComponent { closeDialog() { // Normalize data for API POST let ret = this.placeForm.value; - ret["category_id"] = ret["category"]; - delete ret["category"]; - if (ret["image_id"]) { - delete ret["image"]; - delete ret["image_id"]; + ret['category_id'] = ret['category']; + delete ret['category']; + if (ret['image_id']) { + delete ret['image']; + delete ret['image_id']; } - if (ret["gpx"] == "1") delete ret["gpx"]; - ret["lat"] = +ret["lat"]; - ret["lng"] = +ret["lng"]; + if (ret['gpx'] == '1') delete ret['gpx']; + ret['lat'] = +ret['lat']; + ret['lng'] = +ret['lng']; this.ref.close(ret); } @@ -156,13 +145,12 @@ export class PlaceCreateModalComponent { const [place, latlng] = this.utilsService.parseGoogleMapsUrl(url); if (!place || !latlng) return; - const [lat, lng] = latlng.split(","); - this.placeForm.get("place")?.setValue(place); - this.placeForm.get("lat")?.setValue(lat); - this.placeForm.get("lng")?.setValue(lng); + const [lat, lng] = latlng.split(','); + this.placeForm.get('place')?.setValue(place); + this.placeForm.get('lat')?.setValue(lat); + this.placeForm.get('lng')?.setValue(lng); - if (!this.placeForm.get("name")?.value) - this.placeForm.get("name")?.setValue(place); + if (!this.placeForm.get('name')?.value) this.placeForm.get('name')?.setValue(place); } onImageSelected(event: Event) { @@ -172,14 +160,14 @@ export class PlaceCreateModalComponent { const reader = new FileReader(); reader.onload = (e) => { - if (this.placeForm.get("image_id")?.value) { - this.previous_image_id = this.placeForm.get("image_id")?.value; - this.previous_image = this.placeForm.get("image")?.value; - this.placeForm.get("image_id")?.setValue(null); + if (this.placeForm.get('image_id')?.value) { + this.previous_image_id = this.placeForm.get('image_id')?.value; + this.previous_image = this.placeForm.get('image')?.value; + this.placeForm.get('image_id')?.setValue(null); } - this.placeForm.get("image")?.setValue(e.target?.result as string); - this.placeForm.get("image")?.markAsDirty(); + this.placeForm.get('image')?.setValue(e.target?.result as string); + this.placeForm.get('image')?.markAsDirty(); }; reader.readAsDataURL(file); @@ -187,12 +175,12 @@ export class PlaceCreateModalComponent { } clearImage() { - this.placeForm.get("image")?.setValue(null); - this.placeForm.get("image_id")?.setValue(null); + this.placeForm.get('image')?.setValue(null); + this.placeForm.get('image_id')?.setValue(null); if (this.previous_image && this.previous_image_id) { - this.placeForm.get("image_id")?.setValue(this.previous_image_id); - this.placeForm.get("image")?.setValue(this.previous_image); + this.placeForm.get('image_id')?.setValue(this.previous_image_id); + this.placeForm.get('image')?.setValue(this.previous_image); } } @@ -203,8 +191,8 @@ export class PlaceCreateModalComponent { const reader = new FileReader(); reader.onload = (e) => { - this.placeForm.get("gpx")?.setValue(e.target?.result as string); - this.placeForm.get("gpx")?.markAsDirty(); + this.placeForm.get('gpx')?.setValue(e.target?.result as string); + this.placeForm.get('gpx')?.markAsDirty(); }; reader.readAsText(file); @@ -212,7 +200,7 @@ export class PlaceCreateModalComponent { } clearGPX() { - this.placeForm.get("gpx")?.setValue(null); - this.placeForm.get("gpx")?.markAsDirty(); + this.placeForm.get('gpx')?.setValue(null); + this.placeForm.get('gpx')?.markAsDirty(); } } diff --git a/src/src/app/modals/trip-archive-modal/trip-archive-modal.component.ts b/src/src/app/modals/trip-archive-modal/trip-archive-modal.component.ts index 3f389a1..38c87c6 100644 --- a/src/src/app/modals/trip-archive-modal/trip-archive-modal.component.ts +++ b/src/src/app/modals/trip-archive-modal/trip-archive-modal.component.ts @@ -1,25 +1,20 @@ -import { Component } from "@angular/core"; -import { FormControl, ReactiveFormsModule } from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { TextareaModule } from "primeng/textarea"; -import { Trip } from "../../types/trip"; +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { TextareaModule } from 'primeng/textarea'; +import { Trip } from '../../types/trip'; @Component({ - selector: "app-trip-archive-modal", - imports: [ - FloatLabelModule, - TextareaModule, - ButtonModule, - ReactiveFormsModule, - ], + selector: 'app-trip-archive-modal', + imports: [FloatLabelModule, TextareaModule, ButtonModule, ReactiveFormsModule], standalone: true, - templateUrl: "./trip-archive-modal.component.html", - styleUrl: "./trip-archive-modal.component.scss", + templateUrl: './trip-archive-modal.component.html', + styleUrl: './trip-archive-modal.component.scss', }) export class TripArchiveModalComponent { - review = new FormControl(""); + review = new FormControl(''); constructor( private ref: DynamicDialogRef, @@ -34,16 +29,13 @@ export class TripArchiveModalComponent { return; } if (!trip.days.length) return; - let placeholder = "General feedback:\n\n"; + let placeholder = 'General feedback:\n\n'; trip.days.forEach((day, index) => { placeholder += `\nDay ${index + 1} (${day.label})\n`; - if (!day.items.length) placeholder += " No activities.\n"; - else - day.items.forEach( - (item) => (placeholder += ` - ${item.time} | ${item.text}\n`), - ); + if (!day.items.length) placeholder += ' No activities.\n'; + else day.items.forEach((item) => (placeholder += ` - ${item.time} | ${item.text}\n`)); }); - placeholder += "\nAnything else?"; + placeholder += '\nAnything else?'; this.review.setValue(placeholder); } diff --git a/src/src/app/modals/trip-create-checklist-modal/trip-create-checklist-modal.component.ts b/src/src/app/modals/trip-create-checklist-modal/trip-create-checklist-modal.component.ts index 787f465..ff80a4a 100644 --- a/src/src/app/modals/trip-create-checklist-modal/trip-create-checklist-modal.component.ts +++ b/src/src/app/modals/trip-create-checklist-modal/trip-create-checklist-modal.component.ts @@ -1,28 +1,17 @@ -import { Component } from "@angular/core"; -import { - FormBuilder, - FormGroup, - ReactiveFormsModule, - Validators, -} from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { InputTextModule } from "primeng/inputtext"; -import { FocusTrapModule } from "primeng/focustrap"; +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputTextModule } from 'primeng/inputtext'; +import { FocusTrapModule } from 'primeng/focustrap'; @Component({ - selector: "app-trip-create-checklist-modal", - imports: [ - FloatLabelModule, - InputTextModule, - ButtonModule, - ReactiveFormsModule, - FocusTrapModule, - ], + selector: 'app-trip-create-checklist-modal', + imports: [FloatLabelModule, InputTextModule, ButtonModule, ReactiveFormsModule, FocusTrapModule], standalone: true, - templateUrl: "./trip-create-checklist-modal.component.html", - styleUrl: "./trip-create-checklist-modal.component.scss", + templateUrl: './trip-create-checklist-modal.component.html', + styleUrl: './trip-create-checklist-modal.component.scss', }) export class TripCreateChecklistModalComponent { checklistForm: FormGroup; @@ -33,7 +22,7 @@ export class TripCreateChecklistModalComponent { ) { this.checklistForm = this.fb.group({ id: -1, - text: ["", { validators: Validators.required }], + text: ['', { validators: Validators.required }], }); const patchValue = this.config.data?.packing; diff --git a/src/src/app/modals/trip-create-day-item-modal/trip-create-day-item-modal.component.ts b/src/src/app/modals/trip-create-day-item-modal/trip-create-day-item-modal.component.ts index 26891bf..018d042 100644 --- a/src/src/app/modals/trip-create-day-item-modal/trip-create-day-item-modal.component.ts +++ b/src/src/app/modals/trip-create-day-item-modal/trip-create-day-item-modal.component.ts @@ -1,30 +1,25 @@ -import { Component, ViewChild } from "@angular/core"; -import { - FormBuilder, - FormGroup, - ReactiveFormsModule, - Validators, -} from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { InputTextModule } from "primeng/inputtext"; -import { TripDay, TripMember, TripStatus } from "../../types/trip"; -import { Place } from "../../types/poi"; -import { SelectModule } from "primeng/select"; -import { TextareaModule } from "primeng/textarea"; -import { InputMaskModule } from "primeng/inputmask"; -import { UtilsService } from "../../services/utils.service"; -import { checkAndParseLatLng, formatLatLng } from "../../shared/latlng-parser"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { InputNumberModule } from "primeng/inputnumber"; -import { MultiSelectModule } from "primeng/multiselect"; -import { InputGroupModule } from "primeng/inputgroup"; -import { InputGroupAddonModule } from "primeng/inputgroupaddon"; -import { Popover, PopoverModule } from "primeng/popover"; +import { Component, ViewChild } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputTextModule } from 'primeng/inputtext'; +import { TripDay, TripMember, TripStatus } from '../../types/trip'; +import { Place } from '../../types/poi'; +import { SelectModule } from 'primeng/select'; +import { TextareaModule } from 'primeng/textarea'; +import { InputMaskModule } from 'primeng/inputmask'; +import { UtilsService } from '../../services/utils.service'; +import { checkAndParseLatLng, formatLatLng } from '../../shared/latlng-parser'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { InputNumberModule } from 'primeng/inputnumber'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { Popover, PopoverModule } from 'primeng/popover'; @Component({ - selector: "app-trip-create-day-item-modal", + selector: 'app-trip-create-day-item-modal', imports: [ FloatLabelModule, InputTextModule, @@ -44,11 +39,11 @@ import { Popover, PopoverModule } from "primeng/popover"; PopoverModule, ], standalone: true, - templateUrl: "./trip-create-day-item-modal.component.html", - styleUrl: "./trip-create-day-item-modal.component.scss", + templateUrl: './trip-create-day-item-modal.component.html', + styleUrl: './trip-create-day-item-modal.component.scss', }) export class TripCreateDayItemModalComponent { - @ViewChild("op") op!: Popover; + @ViewChild('op') op!: Popover; members: TripMember[] = []; itemForm: FormGroup; days: TripDay[] = []; @@ -68,16 +63,13 @@ export class TripCreateDayItemModalComponent { this.itemForm = this.fb.group({ id: -1, time: [ - "", + '', { - validators: [ - Validators.required, - Validators.pattern(/^([01]\d|2[0-3])(:[0-5]\d)?$/), - ], + validators: [Validators.required, Validators.pattern(/^([01]\d|2[0-3])(:[0-5]\d)?$/)], }, ], - text: ["", Validators.required], - comment: "", + text: ['', Validators.required], + comment: '', day_id: [null, Validators.required], place: null, status: null, @@ -86,18 +78,16 @@ export class TripCreateDayItemModalComponent { image_id: null, gpx: null, lat: [ - "", + '', { - validators: Validators.pattern("-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)"), - updateOn: "blur", + validators: Validators.pattern('-?(90(\\.0+)?|[1-8]?\\d(\\.\\d+)?)'), + updateOn: 'blur', }, ], lng: [ - "", + '', { - validators: Validators.pattern( - "-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)", - ), + validators: Validators.pattern('-?(180(\\.0+)?|1[0-7]\\d(\\.\\d+)?|[1-9]?\\d(\\.\\d+)?)'), }, ], paid_by: null, @@ -115,36 +105,34 @@ export class TripCreateDayItemModalComponent { place: data.item.place?.id ?? null, }); - if (data.selectedDay) - this.itemForm.get("day_id")?.setValue([data.selectedDay]); + if (data.selectedDay) this.itemForm.get('day_id')?.setValue([data.selectedDay]); } this.itemForm - .get("place") + .get('place') ?.valueChanges.pipe(takeUntilDestroyed()) .subscribe({ next: (value?: number) => { if (!value) { - this.itemForm.get("lat")?.setValue(""); - this.itemForm.get("lng")?.setValue(""); + this.itemForm.get('lat')?.setValue(''); + this.itemForm.get('lng')?.setValue(''); return; } const p: Place = this.places.find((p) => p.id === value) as Place; if (p) { - this.itemForm.get("lat")?.setValue(p.lat); - this.itemForm.get("lng")?.setValue(p.lng); - this.itemForm.get("price")?.setValue(p.price || 0); - if (!this.itemForm.get("text")?.value) - this.itemForm.get("text")?.setValue(p.name); - if (p.description && !this.itemForm.get("comment")?.value) - this.itemForm.get("comment")?.setValue(p.description); + this.itemForm.get('lat')?.setValue(p.lat); + this.itemForm.get('lng')?.setValue(p.lng); + this.itemForm.get('price')?.setValue(p.price || 0); + if (!this.itemForm.get('text')?.value) this.itemForm.get('text')?.setValue(p.name); + if (p.description && !this.itemForm.get('comment')?.value) + this.itemForm.get('comment')?.setValue(p.description); } }, }); this.itemForm - .get("lat") + .get('lat') ?.valueChanges.pipe(takeUntilDestroyed()) .subscribe({ next: (value: string) => { @@ -152,8 +140,8 @@ export class TripCreateDayItemModalComponent { if (!result) return; const [lat, lng] = result; - const latControl = this.itemForm.get("lat"); - const lngControl = this.itemForm.get("lng"); + const latControl = this.itemForm.get('lat'); + const lngControl = this.itemForm.get('lng'); latControl?.setValue(formatLatLng(lat).trim(), { emitEvent: false }); lngControl?.setValue(formatLatLng(lng).trim(), { emitEvent: false }); @@ -167,16 +155,16 @@ export class TripCreateDayItemModalComponent { closeDialog() { // Normalize data for API POST let ret = this.itemForm.value; - if (!ret["lat"]) { - ret["lat"] = null; - ret["lng"] = null; + if (!ret['lat']) { + ret['lat'] = null; + ret['lng'] = null; } - if (ret["image_id"]) { - delete ret["image"]; - delete ret["image_id"]; + if (ret['image_id']) { + delete ret['image']; + delete ret['image_id']; } - if (ret["gpx"] == "1") delete ret["gpx"]; - if (!ret["place"]) delete ret["place"]; + if (ret['gpx'] == '1') delete ret['gpx']; + if (!ret['place']) delete ret['place']; this.ref.close(ret); } @@ -185,7 +173,7 @@ export class TripCreateDayItemModalComponent { } get paidByControl(): any { - return this.itemForm.get("paid_by"); + return this.itemForm.get('paid_by'); } selectPriceMember(member: any) { @@ -206,14 +194,14 @@ export class TripCreateDayItemModalComponent { const reader = new FileReader(); reader.onload = (e) => { - if (this.itemForm.get("image_id")?.value) { - this.previous_image_id = this.itemForm.get("image_id")?.value; - this.previous_image = this.itemForm.get("image")?.value; - this.itemForm.get("image_id")?.setValue(null); + if (this.itemForm.get('image_id')?.value) { + this.previous_image_id = this.itemForm.get('image_id')?.value; + this.previous_image = this.itemForm.get('image')?.value; + this.itemForm.get('image_id')?.setValue(null); } - this.itemForm.get("image")?.setValue(e.target?.result as string); - this.itemForm.get("image")?.markAsDirty(); + this.itemForm.get('image')?.setValue(e.target?.result as string); + this.itemForm.get('image')?.markAsDirty(); }; reader.readAsDataURL(file); @@ -221,13 +209,13 @@ export class TripCreateDayItemModalComponent { } clearImage() { - this.itemForm.get("image")?.setValue(null); - this.itemForm.get("image_id")?.setValue(null); + this.itemForm.get('image')?.setValue(null); + this.itemForm.get('image_id')?.setValue(null); this.itemForm.markAsDirty(); if (this.previous_image && this.previous_image_id) { - this.itemForm.get("image_id")?.setValue(this.previous_image_id); - this.itemForm.get("image")?.setValue(this.previous_image); + this.itemForm.get('image_id')?.setValue(this.previous_image_id); + this.itemForm.get('image')?.setValue(this.previous_image); } } @@ -238,8 +226,8 @@ export class TripCreateDayItemModalComponent { const reader = new FileReader(); reader.onload = (e) => { - this.itemForm.get("gpx")?.setValue(e.target?.result as string); - this.itemForm.get("gpx")?.markAsDirty(); + this.itemForm.get('gpx')?.setValue(e.target?.result as string); + this.itemForm.get('gpx')?.markAsDirty(); }; reader.readAsText(file); @@ -247,7 +235,7 @@ export class TripCreateDayItemModalComponent { } clearGPX() { - this.itemForm.get("gpx")?.setValue(null); - this.itemForm.get("gpx")?.markAsDirty(); + this.itemForm.get('gpx')?.setValue(null); + this.itemForm.get('gpx')?.markAsDirty(); } } diff --git a/src/src/app/modals/trip-create-day-modal/trip-create-day-modal.component.ts b/src/src/app/modals/trip-create-day-modal/trip-create-day-modal.component.ts index b865351..56f90f6 100644 --- a/src/src/app/modals/trip-create-day-modal/trip-create-day-modal.component.ts +++ b/src/src/app/modals/trip-create-day-modal/trip-create-day-modal.component.ts @@ -1,27 +1,17 @@ -import { Component } from "@angular/core"; -import { - FormBuilder, - FormGroup, - ReactiveFormsModule, - Validators, -} from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { InputTextModule } from "primeng/inputtext"; -import { TripDay } from "../../types/trip"; +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputTextModule } from 'primeng/inputtext'; +import { TripDay } from '../../types/trip'; @Component({ - selector: "app-trip-create-day-modal", - imports: [ - FloatLabelModule, - InputTextModule, - ButtonModule, - ReactiveFormsModule, - ], + selector: 'app-trip-create-day-modal', + imports: [FloatLabelModule, InputTextModule, ButtonModule, ReactiveFormsModule], standalone: true, - templateUrl: "./trip-create-day-modal.component.html", - styleUrl: "./trip-create-day-modal.component.scss", + templateUrl: './trip-create-day-modal.component.html', + styleUrl: './trip-create-day-modal.component.scss', }) export class TripCreateDayModalComponent { dayForm: FormGroup; @@ -34,7 +24,7 @@ export class TripCreateDayModalComponent { ) { this.dayForm = this.fb.group({ id: -1, - label: ["", Validators.required], + label: ['', Validators.required], }); if (this.config.data) { @@ -46,7 +36,7 @@ export class TripCreateDayModalComponent { closeDialog() { // Normalize data for API POST let ret = this.dayForm.value; - if (!ret["label"]) return; + if (!ret['label']) return; this.ref.close(ret); } } diff --git a/src/src/app/modals/trip-create-items-modal/trip-create-items-modal.component.ts b/src/src/app/modals/trip-create-items-modal/trip-create-items-modal.component.ts index f38a6aa..20984d4 100644 --- a/src/src/app/modals/trip-create-items-modal/trip-create-items-modal.component.ts +++ b/src/src/app/modals/trip-create-items-modal/trip-create-items-modal.component.ts @@ -1,31 +1,31 @@ -import { Component } from "@angular/core"; -import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { TextareaModule } from "primeng/textarea"; -import { TripDay, TripItem } from "../../types/trip"; -import { SelectModule } from "primeng/select"; +import { Component } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { TextareaModule } from 'primeng/textarea'; +import { TripDay, TripItem } from '../../types/trip'; +import { SelectModule } from 'primeng/select'; @Component({ - selector: "app-trip-create-items-modal", + selector: 'app-trip-create-items-modal', imports: [FloatLabelModule, ButtonModule, SelectModule, ReactiveFormsModule, TextareaModule], standalone: true, - templateUrl: "./trip-create-items-modal.component.html", - styleUrl: "./trip-create-items-modal.component.scss", + templateUrl: './trip-create-items-modal.component.html', + styleUrl: './trip-create-items-modal.component.scss', }) export class TripCreateItemsModalComponent { itemBatchForm: FormGroup; - pholder = "eg.\n14h Just an item example\n15:10 Another format for an item\n16h30 Another item here"; + pholder = 'eg.\n14h Just an item example\n15:10 Another format for an item\n16h30 Another item here'; days: TripDay[] = []; constructor( private ref: DynamicDialogRef, private fb: FormBuilder, - private config: DynamicDialogConfig + private config: DynamicDialogConfig, ) { this.itemBatchForm = this.fb.group({ - batch: ["", Validators.required], + batch: ['', Validators.required], day_id: [null, Validators.required], }); @@ -37,15 +37,15 @@ export class TripCreateItemsModalComponent { closeDialog() { const ret = this.itemBatchForm.value; const day_id = ret.day_id; - const lines: string[] = ret.batch.trim().split("\n"); + const lines: string[] = ret.batch.trim().split('\n'); const tripItems: Partial[] = []; lines.forEach((l) => { const match = l.match(/^(\d{1,2})(?:h|:)?(\d{0,2})?\s+(.+)$/); if (match) { - const [_, hoursStr, minutesStr = "", text] = match; - const hours = hoursStr.padStart(2, "0"); - const minutes = minutesStr.padStart(2, "0") || "00"; + const [_, hoursStr, minutesStr = '', text] = match; + const hours = hoursStr.padStart(2, '0'); + const minutes = minutesStr.padStart(2, '0') || '00'; const time = `${hours}:${minutes}`; tripItems.push({ time: time, text: text, day_id: day_id }); } diff --git a/src/src/app/modals/trip-create-modal/trip-create-modal.component.ts b/src/src/app/modals/trip-create-modal/trip-create-modal.component.ts index 3b3bf8a..a6a647f 100644 --- a/src/src/app/modals/trip-create-modal/trip-create-modal.component.ts +++ b/src/src/app/modals/trip-create-modal/trip-create-modal.component.ts @@ -1,30 +1,18 @@ -import { Component } from "@angular/core"; -import { - FormBuilder, - FormGroup, - ReactiveFormsModule, - Validators, -} from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { InputTextModule } from "primeng/inputtext"; -import { FocusTrapModule } from "primeng/focustrap"; -import { DatePickerModule } from "primeng/datepicker"; +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputTextModule } from 'primeng/inputtext'; +import { FocusTrapModule } from 'primeng/focustrap'; +import { DatePickerModule } from 'primeng/datepicker'; @Component({ - selector: "app-trip-create-modal", - imports: [ - FloatLabelModule, - InputTextModule, - DatePickerModule, - ButtonModule, - ReactiveFormsModule, - FocusTrapModule, - ], + selector: 'app-trip-create-modal', + imports: [FloatLabelModule, InputTextModule, DatePickerModule, ButtonModule, ReactiveFormsModule, FocusTrapModule], standalone: true, - templateUrl: "./trip-create-modal.component.html", - styleUrl: "./trip-create-modal.component.scss", + templateUrl: './trip-create-modal.component.html', + styleUrl: './trip-create-modal.component.scss', }) export class TripCreateModalComponent { tripForm: FormGroup; @@ -38,8 +26,8 @@ export class TripCreateModalComponent { ) { this.tripForm = this.fb.group({ id: -1, - name: ["", Validators.required], - image: "", + name: ['', Validators.required], + image: '', currency: null, image_id: null, from: null, @@ -48,7 +36,7 @@ export class TripCreateModalComponent { const patchValue = this.config.data?.trip; if (patchValue) { - if (!patchValue.image_id) delete patchValue["image"]; + if (!patchValue.image_id) delete patchValue['image']; this.tripForm.patchValue(patchValue); } } @@ -56,10 +44,10 @@ export class TripCreateModalComponent { closeDialog() { // Normalize data for API POST let ret = this.tripForm.value; - if (!ret["name"]) return; - if (ret["image_id"]) { - delete ret["image"]; - delete ret["image_id"]; + if (!ret['name']) return; + if (ret['image_id']) { + delete ret['image']; + delete ret['image_id']; } this.ref.close(ret); } @@ -71,14 +59,14 @@ export class TripCreateModalComponent { const reader = new FileReader(); reader.onload = (e) => { - if (this.tripForm.get("image_id")?.value) { - this.previous_image_id = this.tripForm.get("image_id")?.value; - this.previous_image = this.tripForm.get("image")?.value; - this.tripForm.get("image_id")?.setValue(null); + if (this.tripForm.get('image_id')?.value) { + this.previous_image_id = this.tripForm.get('image_id')?.value; + this.previous_image = this.tripForm.get('image')?.value; + this.tripForm.get('image_id')?.setValue(null); } - this.tripForm.get("image")?.setValue(e.target?.result as string); - this.tripForm.get("image")?.markAsDirty(); + this.tripForm.get('image')?.setValue(e.target?.result as string); + this.tripForm.get('image')?.markAsDirty(); }; reader.readAsDataURL(file); @@ -86,11 +74,11 @@ export class TripCreateModalComponent { } clearImage() { - this.tripForm.get("image")?.setValue(null); + this.tripForm.get('image')?.setValue(null); if (this.previous_image && this.previous_image_id) { - this.tripForm.get("image_id")?.setValue(this.previous_image_id); - this.tripForm.get("image")?.setValue(this.previous_image); + this.tripForm.get('image_id')?.setValue(this.previous_image_id); + this.tripForm.get('image')?.setValue(this.previous_image); } } } diff --git a/src/src/app/modals/trip-create-packing-modal/trip-create-packing-modal.component.ts b/src/src/app/modals/trip-create-packing-modal/trip-create-packing-modal.component.ts index a580e80..2b6acd0 100644 --- a/src/src/app/modals/trip-create-packing-modal/trip-create-packing-modal.component.ts +++ b/src/src/app/modals/trip-create-packing-modal/trip-create-packing-modal.component.ts @@ -1,20 +1,15 @@ -import { Component } from "@angular/core"; -import { - FormBuilder, - FormGroup, - ReactiveFormsModule, - Validators, -} from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { InputTextModule } from "primeng/inputtext"; -import { FocusTrapModule } from "primeng/focustrap"; -import { SelectModule } from "primeng/select"; -import { InputNumberModule } from "primeng/inputnumber"; +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputTextModule } from 'primeng/inputtext'; +import { FocusTrapModule } from 'primeng/focustrap'; +import { SelectModule } from 'primeng/select'; +import { InputNumberModule } from 'primeng/inputnumber'; @Component({ - selector: "app-trip-create-packing-modal", + selector: 'app-trip-create-packing-modal', imports: [ FloatLabelModule, InputTextModule, @@ -25,17 +20,17 @@ import { InputNumberModule } from "primeng/inputnumber"; InputNumberModule, ], standalone: true, - templateUrl: "./trip-create-packing-modal.component.html", - styleUrl: "./trip-create-packing-modal.component.scss", + templateUrl: './trip-create-packing-modal.component.html', + styleUrl: './trip-create-packing-modal.component.scss', }) export class TripCreatePackingModalComponent { packingForm: FormGroup; readonly packingCategories = [ - { value: "clothes", dispValue: "Clothes" }, - { value: "toiletries", dispValue: "Toiletries" }, - { value: "tech", dispValue: "Tech" }, - { value: "documents", dispValue: "Documents" }, - { value: "other", dispValue: "Other" }, + { value: 'clothes', dispValue: 'Clothes' }, + { value: 'toiletries', dispValue: 'Toiletries' }, + { value: 'tech', dispValue: 'Tech' }, + { value: 'documents', dispValue: 'Documents' }, + { value: 'other', dispValue: 'Other' }, ]; constructor( @@ -46,8 +41,8 @@ export class TripCreatePackingModalComponent { this.packingForm = this.fb.group({ id: -1, qt: null, - text: ["", { validators: Validators.required }], - category: ["", { validators: Validators.required }], + text: ['', { validators: Validators.required }], + category: ['', { validators: Validators.required }], }); const patchValue = this.config.data?.packing; diff --git a/src/src/app/modals/trip-invite-member-modal/trip-invite-member-modal.component.ts b/src/src/app/modals/trip-invite-member-modal/trip-invite-member-modal.component.ts index 5bd1f0f..f34bcaa 100644 --- a/src/src/app/modals/trip-invite-member-modal/trip-invite-member-modal.component.ts +++ b/src/src/app/modals/trip-invite-member-modal/trip-invite-member-modal.component.ts @@ -1,26 +1,20 @@ -import { Component } from "@angular/core"; -import { FormControl, ReactiveFormsModule } from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { InputTextModule } from "primeng/inputtext"; -import { FocusTrapModule } from "primeng/focustrap"; +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputTextModule } from 'primeng/inputtext'; +import { FocusTrapModule } from 'primeng/focustrap'; @Component({ - selector: "app-trip-invite-member-modal", - imports: [ - FloatLabelModule, - InputTextModule, - ButtonModule, - ReactiveFormsModule, - FocusTrapModule, - ], + selector: 'app-trip-invite-member-modal', + imports: [FloatLabelModule, InputTextModule, ButtonModule, ReactiveFormsModule, FocusTrapModule], standalone: true, - templateUrl: "./trip-invite-member-modal.component.html", - styleUrl: "./trip-invite-member-modal.component.scss", + templateUrl: './trip-invite-member-modal.component.html', + styleUrl: './trip-invite-member-modal.component.scss', }) export class TripInviteMemberModalComponent { - memberForm = new FormControl(""); + memberForm = new FormControl(''); constructor(private ref: DynamicDialogRef) {} closeDialog() { diff --git a/src/src/app/modals/trip-notes-modal/trip-notes-modal.component.ts b/src/src/app/modals/trip-notes-modal/trip-notes-modal.component.ts index d84eeef..941af82 100644 --- a/src/src/app/modals/trip-notes-modal/trip-notes-modal.component.ts +++ b/src/src/app/modals/trip-notes-modal/trip-notes-modal.component.ts @@ -1,24 +1,19 @@ -import { Component } from "@angular/core"; -import { FormControl, ReactiveFormsModule } from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { TextareaModule } from "primeng/textarea"; +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { TextareaModule } from 'primeng/textarea'; @Component({ - selector: "app-trip-notes-modal", - imports: [ - FloatLabelModule, - TextareaModule, - ButtonModule, - ReactiveFormsModule, - ], + selector: 'app-trip-notes-modal', + imports: [FloatLabelModule, TextareaModule, ButtonModule, ReactiveFormsModule], standalone: true, - templateUrl: "./trip-notes-modal.component.html", - styleUrl: "./trip-notes-modal.component.scss", + templateUrl: './trip-notes-modal.component.html', + styleUrl: './trip-notes-modal.component.scss', }) export class TripNotesModalComponent { - notes = new FormControl(""); + notes = new FormControl(''); isEditing: boolean = false; constructor( diff --git a/src/src/app/modals/trip-place-select-modal/trip-place-select-modal.component.ts b/src/src/app/modals/trip-place-select-modal/trip-place-select-modal.component.ts index b7c1299..2af65ed 100644 --- a/src/src/app/modals/trip-place-select-modal/trip-place-select-modal.component.ts +++ b/src/src/app/modals/trip-place-select-modal/trip-place-select-modal.component.ts @@ -1,28 +1,22 @@ -import { Component } from "@angular/core"; -import { FormControl, ReactiveFormsModule } from "@angular/forms"; -import { ButtonModule } from "primeng/button"; -import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; -import { FloatLabelModule } from "primeng/floatlabel"; -import { InputTextModule } from "primeng/inputtext"; -import { Place } from "../../types/poi"; -import { ApiService } from "../../services/api.service"; -import { SkeletonModule } from "primeng/skeleton"; +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputTextModule } from 'primeng/inputtext'; +import { Place } from '../../types/poi'; +import { ApiService } from '../../services/api.service'; +import { SkeletonModule } from 'primeng/skeleton'; @Component({ - selector: "app-trip-place-select-modal", - imports: [ - FloatLabelModule, - InputTextModule, - ButtonModule, - ReactiveFormsModule, - SkeletonModule, - ], + selector: 'app-trip-place-select-modal', + imports: [FloatLabelModule, InputTextModule, ButtonModule, ReactiveFormsModule, SkeletonModule], standalone: true, - templateUrl: "./trip-place-select-modal.component.html", - styleUrl: "./trip-place-select-modal.component.scss", + templateUrl: './trip-place-select-modal.component.html', + styleUrl: './trip-place-select-modal.component.scss', }) export class TripPlaceSelectModalComponent { - searchInput = new FormControl(""); + searchInput = new FormControl(''); selectedPlaces: Place[] = []; showSelectedPlaces: boolean = false; @@ -38,9 +32,7 @@ export class TripPlaceSelectModalComponent { ) { this.apiService.getPlaces().subscribe({ next: (places) => { - this.places = places.sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + this.places = places.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); this.displayedPlaces = places; }, }); @@ -60,9 +52,7 @@ export class TripPlaceSelectModalComponent { const v = value.toLowerCase(); this.displayedPlaces = this.places.filter( - (p) => - p.name.toLowerCase().includes(v) || - p.description?.toLowerCase().includes(v), + (p) => p.name.toLowerCase().includes(v) || p.description?.toLowerCase().includes(v), ); }, }); @@ -80,9 +70,7 @@ export class TripPlaceSelectModalComponent { this.selectedPlacesID.push(p.id); this.selectedPlaces.push(p); - this.selectedPlaces.sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ); + this.selectedPlaces.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); } closeDialog() { diff --git a/src/src/app/services/api.service.ts b/src/src/app/services/api.service.ts index 3cc948a..36e19a0 100644 --- a/src/src/app/services/api.service.ts +++ b/src/src/app/services/api.service.ts @@ -1,9 +1,9 @@ -import { inject, Injectable } from "@angular/core"; -import { HttpClient } from "@angular/common/http"; -import { Category, Place } from "../types/poi"; -import { BehaviorSubject, map, Observable, shareReplay, tap } from "rxjs"; -import { Info } from "../types/info"; -import { ImportResponse, Settings } from "../types/settings"; +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Category, Place } from '../types/poi'; +import { BehaviorSubject, map, Observable, shareReplay, tap } from 'rxjs'; +import { Info } from '../types/info'; +import { ImportResponse, Settings } from '../types/settings'; import { ChecklistItem, PackingItem, @@ -14,37 +14,31 @@ import { TripInvitation, TripItem, TripMember, -} from "../types/trip"; +} from '../types/trip'; const NO_AUTH_HEADER = { - no_auth: "1", + no_auth: '1', }; @Injectable({ - providedIn: "root", + providedIn: 'root', }) export class ApiService { - public readonly apiBaseUrl: string = "/api"; + public readonly apiBaseUrl: string = '/api'; private categoriesSubject = new BehaviorSubject(null); - public categories$: Observable = - this.categoriesSubject.asObservable(); + public categories$: Observable = this.categoriesSubject.asObservable(); private settingsSubject = new BehaviorSubject(null); - public settings$: Observable = - this.settingsSubject.asObservable(); + public settings$: Observable = this.settingsSubject.asObservable(); private httpClient = inject(HttpClient); getInfo(): Observable { - return this.httpClient.get(this.apiBaseUrl + "/info"); + return this.httpClient.get(this.apiBaseUrl + '/info'); } _categoriesSubjectNext(categories: Category[]) { - this.categoriesSubject.next( - [...categories].sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - ), - ); + this.categoriesSubject.next([...categories].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))); } getCategories(): Observable { @@ -58,46 +52,35 @@ export class ApiService { postCategory(c: Category): Observable { return this.httpClient - .post(this.apiBaseUrl + "/categories", c) - .pipe( - tap((category) => - this._categoriesSubjectNext([ - ...(this.categoriesSubject.value || []), - category, - ]), - ), - ); + .post(this.apiBaseUrl + '/categories', c) + .pipe(tap((category) => this._categoriesSubjectNext([...(this.categoriesSubject.value || []), category]))); } putCategory(c_id: number, c: Partial): Observable { - return this.httpClient - .put(this.apiBaseUrl + `/categories/${c_id}`, c) - .pipe( - tap((category) => { - const categories = this.categoriesSubject.value || []; - const idx = categories?.findIndex((c) => c.id == c_id) || -1; - if (idx > -1) { - const updated = [...categories]; - updated[idx] = category; - this._categoriesSubjectNext(updated); - } - }), - ); + return this.httpClient.put(this.apiBaseUrl + `/categories/${c_id}`, c).pipe( + tap((category) => { + const categories = this.categoriesSubject.value || []; + const idx = categories?.findIndex((c) => c.id == c_id) || -1; + if (idx > -1) { + const updated = [...categories]; + updated[idx] = category; + this._categoriesSubjectNext(updated); + } + }), + ); } deleteCategory(category_id: number): Observable<{}> { - return this.httpClient - .delete<{}>(this.apiBaseUrl + `/categories/${category_id}`) - .pipe( - tap(() => { - const categories = this.categoriesSubject.value || []; - const idx = categories?.findIndex((c) => c.id == category_id) || -1; - if (idx > -1) { - const updated = categories.filter((_, i) => i != idx); - this._categoriesSubjectNext(updated); - } - }), - ); + return this.httpClient.delete<{}>(this.apiBaseUrl + `/categories/${category_id}`).pipe( + tap(() => { + const categories = this.categoriesSubject.value || []; + const idx = categories?.findIndex((c) => c.id == category_id) || -1; + if (idx > -1) { + const updated = categories.filter((_, i) => i != idx); + this._categoriesSubjectNext(updated); + } + }), + ); } getPlaces(): Observable { @@ -109,23 +92,15 @@ export class ApiService { } postPlaces(places: Partial): Observable { - return this.httpClient.post( - `${this.apiBaseUrl}/places/batch`, - places, - ); + return this.httpClient.post(`${this.apiBaseUrl}/places/batch`, places); } putPlace(place_id: number, place: Partial): Observable { - return this.httpClient.put( - `${this.apiBaseUrl}/places/${place_id}`, - place, - ); + return this.httpClient.put(`${this.apiBaseUrl}/places/${place_id}`, place); } deletePlace(place_id: number): Observable { - return this.httpClient.delete( - `${this.apiBaseUrl}/places/${place_id}`, - ); + return this.httpClient.delete(`${this.apiBaseUrl}/places/${place_id}`); } getPlaceGPX(place_id: number): Observable { @@ -141,9 +116,7 @@ export class ApiService { } getTripBalance(id: number): Observable<{ [user: string]: number }> { - return this.httpClient.get<{ [user: string]: number }>( - `${this.apiBaseUrl}/trips/${id}/balance`, - ); + return this.httpClient.get<{ [user: string]: number }>(`${this.apiBaseUrl}/trips/${id}/balance`); } postTrip(trip: TripBase): Observable { @@ -155,79 +128,42 @@ export class ApiService { } putTrip(trip: Partial, trip_id: number): Observable { - return this.httpClient.put( - `${this.apiBaseUrl}/trips/${trip_id}`, - trip, - ); + return this.httpClient.put(`${this.apiBaseUrl}/trips/${trip_id}`, trip); } postTripDay(tripDay: TripDay, trip_id: number): Observable { - return this.httpClient.post( - `${this.apiBaseUrl}/trips/${trip_id}/days`, - tripDay, - ); + return this.httpClient.post(`${this.apiBaseUrl}/trips/${trip_id}/days`, tripDay); } putTripDay(tripDay: Partial, trip_id: number): Observable { - return this.httpClient.put( - `${this.apiBaseUrl}/trips/${trip_id}/days/${tripDay.id}`, - tripDay, - ); + return this.httpClient.put(`${this.apiBaseUrl}/trips/${trip_id}/days/${tripDay.id}`, tripDay); } deleteTripDay(trip_id: number, day_id: number): Observable { - return this.httpClient.delete( - `${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}`, - ); + return this.httpClient.delete(`${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}`); } - postTripDayItem( - item: TripItem, - trip_id: number, - day_id: number, - ): Observable { - return this.httpClient.post( - `${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items`, - item, - ); + postTripDayItem(item: TripItem, trip_id: number, day_id: number): Observable { + return this.httpClient.post(`${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items`, item); } - putTripDayItem( - item: Partial, - trip_id: number, - day_id: number, - item_id: number, - ): Observable { - return this.httpClient.put( - `${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items/${item_id}`, - item, - ); + putTripDayItem(item: Partial, trip_id: number, day_id: number, item_id: number): Observable { + return this.httpClient.put(`${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items/${item_id}`, item); } - deleteTripDayItem( - trip_id: number, - day_id: number, - item_id: number, - ): Observable { - return this.httpClient.delete( - `${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items/${item_id}`, - ); + deleteTripDayItem(trip_id: number, day_id: number, item_id: number): Observable { + return this.httpClient.delete(`${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items/${item_id}`); } getSharedTrip(token: string): Observable { - return this.httpClient.get( - `${this.apiBaseUrl}/trips/shared/${token}`, - { headers: NO_AUTH_HEADER }, - ); + return this.httpClient.get(`${this.apiBaseUrl}/trips/shared/${token}`, { headers: NO_AUTH_HEADER }); } getSharedTripURL(trip_id: number): Observable { - return this.httpClient - .get(`${this.apiBaseUrl}/trips/${trip_id}/share`) - .pipe( - map((t) => window.location.origin + t.url), - shareReplay(), - ); + return this.httpClient.get(`${this.apiBaseUrl}/trips/${trip_id}/share`).pipe( + map((t) => window.location.origin + t.url), + shareReplay(), + ); } createSharedTrip(trip_id: number): Observable { @@ -237,138 +173,79 @@ export class ApiService { } deleteSharedTrip(trip_id: number): Observable { - return this.httpClient.delete( - `${this.apiBaseUrl}/trips/${trip_id}/share`, - ); + return this.httpClient.delete(`${this.apiBaseUrl}/trips/${trip_id}/share`); } getPackingList(trip_id: number): Observable { - return this.httpClient.get( - `${this.apiBaseUrl}/trips/${trip_id}/packing`, - ); + return this.httpClient.get(`${this.apiBaseUrl}/trips/${trip_id}/packing`); } getSharedTripPackingList(token: string): Observable { - return this.httpClient.get( - `${this.apiBaseUrl}/trips/shared/${token}/packing`, - ); + return this.httpClient.get(`${this.apiBaseUrl}/trips/shared/${token}/packing`); } - postPackingItem( - trip_id: number, - p_item: PackingItem, - ): Observable { - return this.httpClient.post( - `${this.apiBaseUrl}/trips/${trip_id}/packing`, - p_item, - ); + postPackingItem(trip_id: number, p_item: PackingItem): Observable { + return this.httpClient.post(`${this.apiBaseUrl}/trips/${trip_id}/packing`, p_item); } - putPackingItem( - trip_id: number, - p_id: number, - p_item: Partial, - ): Observable { - return this.httpClient.put( - `${this.apiBaseUrl}/trips/${trip_id}/packing/${p_id}`, - p_item, - ); + putPackingItem(trip_id: number, p_id: number, p_item: Partial): Observable { + return this.httpClient.put(`${this.apiBaseUrl}/trips/${trip_id}/packing/${p_id}`, p_item); } deletePackingItem(trip_id: number, p_id: number): Observable { - return this.httpClient.delete( - `${this.apiBaseUrl}/trips/${trip_id}/packing/${p_id}`, - ); + return this.httpClient.delete(`${this.apiBaseUrl}/trips/${trip_id}/packing/${p_id}`); } getChecklist(trip_id: number): Observable { - return this.httpClient.get( - `${this.apiBaseUrl}/trips/${trip_id}/checklist`, - ); + return this.httpClient.get(`${this.apiBaseUrl}/trips/${trip_id}/checklist`); } getSharedTripChecklist(token: string): Observable { - return this.httpClient.get( - `${this.apiBaseUrl}/trips/shared/${token}/checklist`, - ); + return this.httpClient.get(`${this.apiBaseUrl}/trips/shared/${token}/checklist`); } - postChecklistItem( - trip_id: number, - item: ChecklistItem, - ): Observable { - return this.httpClient.post( - `${this.apiBaseUrl}/trips/${trip_id}/checklist`, - item, - ); + postChecklistItem(trip_id: number, item: ChecklistItem): Observable { + return this.httpClient.post(`${this.apiBaseUrl}/trips/${trip_id}/checklist`, item); } - putChecklistItem( - trip_id: number, - id: number, - item: Partial, - ): Observable { - return this.httpClient.put( - `${this.apiBaseUrl}/trips/${trip_id}/checklist/${id}`, - item, - ); + putChecklistItem(trip_id: number, id: number, item: Partial): Observable { + return this.httpClient.put(`${this.apiBaseUrl}/trips/${trip_id}/checklist/${id}`, item); } deleteChecklistItem(trip_id: number, id: number): Observable { - return this.httpClient.delete( - `${this.apiBaseUrl}/trips/${trip_id}/checklist/${id}`, - ); + return this.httpClient.delete(`${this.apiBaseUrl}/trips/${trip_id}/checklist/${id}`); } getHasTripsInvitations(): Observable { - return this.httpClient.get( - `${this.apiBaseUrl}/trips/invitations/pending`, - ); + return this.httpClient.get(`${this.apiBaseUrl}/trips/invitations/pending`); } getTripsInvitations(): Observable { - return this.httpClient.get( - `${this.apiBaseUrl}/trips/invitations`, - ); + return this.httpClient.get(`${this.apiBaseUrl}/trips/invitations`); } getTripMembers(trip_id: number): Observable { - return this.httpClient.get( - `${this.apiBaseUrl}/trips/${trip_id}/members`, - ); + return this.httpClient.get(`${this.apiBaseUrl}/trips/${trip_id}/members`); } deleteTripMember(trip_id: number, username: string): Observable { - return this.httpClient.delete( - `${this.apiBaseUrl}/trips/${trip_id}/members/${username}`, - ); + return this.httpClient.delete(`${this.apiBaseUrl}/trips/${trip_id}/members/${username}`); } inviteTripMember(trip_id: number, user: string): Observable { - return this.httpClient.post( - `${this.apiBaseUrl}/trips/${trip_id}/members`, - { user }, - ); + return this.httpClient.post(`${this.apiBaseUrl}/trips/${trip_id}/members`, { user }); } acceptTripMemberInvite(trip_id: number): Observable { - return this.httpClient.post( - `${this.apiBaseUrl}/trips/${trip_id}/members/accept`, - {}, - ); + return this.httpClient.post(`${this.apiBaseUrl}/trips/${trip_id}/members/accept`, {}); } declineTripMemberInvite(trip_id: number): Observable { - return this.httpClient.post( - `${this.apiBaseUrl}/trips/${trip_id}/members/decline`, - {}, - ); + return this.httpClient.post(`${this.apiBaseUrl}/trips/${trip_id}/members/decline`, {}); } checkVersion(): Observable { - return this.httpClient.get( - `${this.apiBaseUrl}/settings/checkversion`, - ); + return this.httpClient.get(`${this.apiBaseUrl}/settings/checkversion`); } getSettings(): Observable { @@ -392,7 +269,7 @@ export class ApiService { } settingsUserImport(formdata: FormData): Observable { - const headers = { enctype: "multipart/form-data" }; + const headers = { enctype: 'multipart/form-data' }; return this.httpClient .post(`${this.apiBaseUrl}/settings/import`, formdata, { headers: headers, diff --git a/src/src/app/services/auth.guard.ts b/src/src/app/services/auth.guard.ts index d3d7f76..a982235 100644 --- a/src/src/app/services/auth.guard.ts +++ b/src/src/app/services/auth.guard.ts @@ -1,8 +1,8 @@ -import { inject } from "@angular/core"; -import { CanActivateFn, Router } from "@angular/router"; -import { UtilsService } from "./utils.service"; -import { AuthService } from "./auth.service"; -import { of, switchMap, take } from "rxjs"; +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { UtilsService } from './utils.service'; +import { AuthService } from './auth.service'; +import { of, switchMap, take } from 'rxjs'; export const AuthGuard: CanActivateFn = (_, state) => { const router: Router = inject(Router); @@ -14,14 +14,9 @@ export const AuthGuard: CanActivateFn = (_, state) => { take(1), switchMap((authenticated) => { if (!authenticated) { - const redirectURL = - state.url === "/auth" ? "" : `redirectURL=${state.url}`; + const redirectURL = state.url === '/auth' ? '' : `redirectURL=${state.url}`; const urlTree = router.parseUrl(`auth?${redirectURL}`); - utilsService.toast( - "warn", - "Authentication required", - "You must be authenticated", - ); + utilsService.toast('warn', 'Authentication required', 'You must be authenticated'); return of(urlTree); } diff --git a/src/src/app/services/auth.service.ts b/src/src/app/services/auth.service.ts index d8dcbd1..f6e079b 100644 --- a/src/src/app/services/auth.service.ts +++ b/src/src/app/services/auth.service.ts @@ -1,10 +1,10 @@ -import { HttpClient } from "@angular/common/http"; -import { Injectable } from "@angular/core"; -import { Router } from "@angular/router"; -import { Observable, of, ReplaySubject } from "rxjs"; -import { tap } from "rxjs/operators"; -import { ApiService } from "./api.service"; -import { UtilsService } from "./utils.service"; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { Observable, of, ReplaySubject } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { ApiService } from './api.service'; +import { UtilsService } from './utils.service'; export interface Token { refresh_token: string; @@ -16,11 +16,11 @@ export interface AuthParams { oidc?: string; } -const JWT_TOKEN = "TRIP_AT"; -const REFRESH_TOKEN = "TRIP_RT"; -const JWT_USER = "TRIP_USER"; +const JWT_TOKEN = 'TRIP_AT'; +const REFRESH_TOKEN = 'TRIP_RT'; +const JWT_USER = 'TRIP_USER'; -@Injectable({ providedIn: "root" }) +@Injectable({ providedIn: 'root' }) export class AuthService { public readonly apiBaseUrl: string; private refreshInProgressLock$: ReplaySubject | null = null; @@ -39,7 +39,7 @@ export class AuthService { } get loggedUser(): string { - return localStorage.getItem(JWT_USER) ?? ""; + return localStorage.getItem(JWT_USER) ?? ''; } set accessToken(token: string) { @@ -47,7 +47,7 @@ export class AuthService { } get accessToken(): string { - return localStorage.getItem(JWT_TOKEN) ?? ""; + return localStorage.getItem(JWT_TOKEN) ?? ''; } set refreshToken(token: string) { @@ -55,11 +55,11 @@ export class AuthService { } get refreshToken(): string { - return localStorage.getItem(REFRESH_TOKEN) ?? ""; + return localStorage.getItem(REFRESH_TOKEN) ?? ''; } authParams(): Observable { - return this.httpClient.get(this.apiBaseUrl + "/auth/params"); + return this.httpClient.get(this.apiBaseUrl + '/auth/params'); } storeTokens(tokens: Token): void { @@ -81,7 +81,7 @@ export class AuthService { this.refreshInProgressLock$ = new ReplaySubject(1); this.httpClient - .post(this.apiBaseUrl + "/auth/refresh", { + .post(this.apiBaseUrl + '/auth/refresh', { refresh_token: this.refreshToken, }) .pipe( @@ -103,60 +103,47 @@ export class AuthService { } login(authForm: { username: string; password: string }): Observable { - return this.httpClient - .post(this.apiBaseUrl + "/auth/login", authForm) - .pipe( - tap((tokens: Token) => { - this.loggedUser = authForm.username; - this.storeTokens(tokens); - }), - ); + return this.httpClient.post(this.apiBaseUrl + '/auth/login', authForm).pipe( + tap((tokens: Token) => { + this.loggedUser = authForm.username; + this.storeTokens(tokens); + }), + ); } - register(authForm: { - username: string; - password: string; - }): Observable { - return this.httpClient - .post(this.apiBaseUrl + "/auth/register", authForm) - .pipe( - tap((tokens: Token) => { - this.loggedUser = authForm.username; - this.storeTokens(tokens); - }), - ); + register(authForm: { username: string; password: string }): Observable { + return this.httpClient.post(this.apiBaseUrl + '/auth/register', authForm).pipe( + tap((tokens: Token) => { + this.loggedUser = authForm.username; + this.storeTokens(tokens); + }), + ); } oidcLogin(code: string, state: string): Observable { - return this.httpClient - .post(this.apiBaseUrl + "/auth/oidc/login", { code, state }) - .pipe( - tap((data: any) => { - if (data.access_token && data.refresh_token) { - this.loggedUser = this._getTokenUsername(data.access_token); - this.storeTokens(data); - } - }), - ); + return this.httpClient.post(this.apiBaseUrl + '/auth/oidc/login', { code, state }).pipe( + tap((data: any) => { + if (data.access_token && data.refresh_token) { + this.loggedUser = this._getTokenUsername(data.access_token); + this.storeTokens(data); + } + }), + ); } - logout(custom_msg: string = "", is_error = false): void { - this.loggedUser = ""; + logout(custom_msg: string = '', is_error = false): void { + this.loggedUser = ''; this.removeTokens(); if (custom_msg) { if (is_error) { - this.utilsService.toast( - "error", - "You must be authenticated", - custom_msg, - ); + this.utilsService.toast('error', 'You must be authenticated', custom_msg); } else { - this.utilsService.toast("success", "Success", custom_msg); + this.utilsService.toast('success', 'Success', custom_msg); } } - this.router.navigate(["/auth"]); + this.router.navigate(['/auth']); } private removeTokens(): void { @@ -167,7 +154,7 @@ export class AuthService { isTokenExpired(token: string, offsetSeconds?: number): boolean { // Return if there is no token - if (!token || token === "") { + if (!token || token === '') { return true; } @@ -186,25 +173,19 @@ export class AuthService { private _b64DecodeUnicode(str: any): string { return decodeURIComponent( Array.prototype.map - .call( - this._b64decode(str), - (c: any) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2), - ) - .join(""), + .call(this._b64decode(str), (c: any) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) + .join(''), ); } private _b64decode(str: string): string { - const chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - let output = ""; + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + let output = ''; - str = String(str).replace(/=+$/, ""); + str = String(str).replace(/=+$/, ''); if (str.length % 4 === 1) { - throw new Error( - "'atob' failed: The string to be decoded is not correctly encoded.", - ); + throw new Error("'atob' failed: The string to be decoded is not correctly encoded."); } /* eslint-disable */ @@ -223,21 +204,21 @@ export class AuthService { } private _urlBase64Decode(str: string): string { - let output = str.replace(/-/g, "+").replace(/_/g, "/"); + let output = str.replace(/-/g, '+').replace(/_/g, '/'); switch (output.length % 4) { case 0: { break; } case 2: { - output += "=="; + output += '=='; break; } case 3: { - output += "="; + output += '='; break; } default: { - throw Error("Illegal base64url string!"); + throw Error('Illegal base64url string!'); } } return this._b64DecodeUnicode(output); @@ -247,11 +228,11 @@ export class AuthService { const decodedToken = this._decodeToken(token); if (decodedToken === null) { - return ""; + return ''; } - if (!decodedToken.hasOwnProperty("sub")) { - return ""; + if (!decodedToken.hasOwnProperty('sub')) { + return ''; } return decodedToken.sub; @@ -262,7 +243,7 @@ export class AuthService { return null; } - const parts = token.split("."); + const parts = token.split('.'); if (parts.length !== 3) { return null; @@ -284,7 +265,7 @@ export class AuthService { return null; } - if (!decodedToken.hasOwnProperty("exp")) { + if (!decodedToken.hasOwnProperty('exp')) { return null; } diff --git a/src/src/app/services/interceptor.service.ts b/src/src/app/services/interceptor.service.ts index b4aaa92..a09b34f 100644 --- a/src/src/app/services/interceptor.service.ts +++ b/src/src/app/services/interceptor.service.ts @@ -1,63 +1,52 @@ -import { - HttpErrorResponse, - HttpEvent, - HttpHandlerFn, - HttpRequest, -} from "@angular/common/http"; -import { inject } from "@angular/core"; -import { catchError, Observable, switchMap, take, throwError } from "rxjs"; -import { AuthService } from "./auth.service"; -import { UtilsService } from "./utils.service"; +import { HttpErrorResponse, HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http'; +import { inject } from '@angular/core'; +import { catchError, Observable, switchMap, take, throwError } from 'rxjs'; +import { AuthService } from './auth.service'; +import { UtilsService } from './utils.service'; const ERROR_CONFIG: Record = { 400: { - title: "Bad Request", - detail: "Unknown error, check console for details", + title: 'Bad Request', + detail: 'Unknown error, check console for details', }, - 403: { title: "Forbidden", detail: "You are not allowed to do this" }, - 409: { title: "Conflict", detail: "Conflict on resource" }, - 413: { title: "Request Entity Too Large", detail: "The resource is too big" }, + 403: { title: 'Forbidden', detail: 'You are not allowed to do this' }, + 409: { title: 'Conflict', detail: 'Conflict on resource' }, + 413: { title: 'Request Entity Too Large', detail: 'The resource is too big' }, 422: { - title: "Unprocessable Entity", - detail: "The resource you sent was unprocessable", + title: 'Unprocessable Entity', + detail: 'The resource you sent was unprocessable', }, 502: { - title: "Bad Gateway", - detail: "Check your connectivity and ensure the server is up", + title: 'Bad Gateway', + detail: 'Check your connectivity and ensure the server is up', }, - 503: { title: "Service Unavailable", detail: "Resource not available" }, + 503: { title: 'Service Unavailable', detail: 'Resource not available' }, }; -export const Interceptor = ( - req: HttpRequest, - next: HttpHandlerFn, -): Observable> => { +export const Interceptor = (req: HttpRequest, next: HttpHandlerFn): Observable> => { const authService = inject(AuthService); const utilsService = inject(UtilsService); function showAndThrowError(title: string, details: string) { - utilsService.toast("error", title, details); + utilsService.toast('error', title, details); return throwError(() => details); } - if (req.headers.has("no_auth")) { + if (req.headers.has('no_auth')) { // Shared Trip must be anonymous return next(req); } - if (!req.headers.has("enctype") && !req.headers.has("Content-Type")) { + if (!req.headers.has('enctype') && !req.headers.has('Content-Type')) { req = req.clone({ setHeaders: { - "Content-Type": "application/json", - "Accept-Language": "en-US;q=0.9,en-US,en;q=0.8", + 'Content-Type': 'application/json', + 'Accept-Language': 'en-US;q=0.9,en-US,en;q=0.8', }, }); } - if ( - authService.accessToken && - !authService.isTokenExpired(authService.accessToken) - ) { + if (authService.accessToken && !authService.isTokenExpired(authService.accessToken)) { if (req.url.startsWith(authService.apiBaseUrl)) { req = req.clone({ setHeaders: { Authorization: `Bearer ${authService.accessToken}` }, @@ -70,17 +59,14 @@ export const Interceptor = ( const errDetails = ERROR_CONFIG[err.status]; if (errDetails) { console.error(err); - return showAndThrowError( - errDetails.title, - `${err.error?.detail || err.message || errDetails.detail}`, - ); + return showAndThrowError(errDetails.title, `${err.error?.detail || err.message || errDetails.detail}`); } if (err.status == 401 && authService.accessToken) { // Handle 401 on Refresh (RT expired) - if (req.url.endsWith("/refresh")) { - authService.logout("Your session has expired", true); - return throwError(() => "Your session has expired"); + if (req.url.endsWith('/refresh')) { + authService.logout('Your session has expired', true); + return throwError(() => 'Your session has expired'); } // Unauthenticated, AT exists but is expired (authServices.accessToken truethy), we refresh it @@ -95,18 +81,15 @@ export const Interceptor = ( return next(refreshedReq); }), ); - } else if (err.status == 401 && !req.url.endsWith("/refresh")) { + } else if (err.status == 401 && !req.url.endsWith('/refresh')) { // If any API route 401 -> redirect to login. We skip /refresh/ to prevent toast on login errors. - authService.logout( - `${err.error?.detail || err.message || "You must be authenticated"}`, - true, - ); + authService.logout(`${err.error?.detail || err.message || 'You must be authenticated'}`, true); } console.error(err); return showAndThrowError( - "Request Error", - `${err.error?.detail || err.message || "Unknown error, check console for details"}`, + 'Request Error', + `${err.error?.detail || err.message || 'Unknown error, check console for details'}`, ); }), ); diff --git a/src/src/app/services/utils.service.ts b/src/src/app/services/utils.service.ts index 01c10a7..634304b 100644 --- a/src/src/app/services/utils.service.ts +++ b/src/src/app/services/utils.service.ts @@ -1,47 +1,42 @@ -import { inject, Injectable } from "@angular/core"; -import { MessageService } from "primeng/api"; -import { TripStatus } from "../types/trip"; -import { ApiService } from "./api.service"; -import { map } from "rxjs"; +import { inject, Injectable } from '@angular/core'; +import { MessageService } from 'primeng/api'; +import { TripStatus } from '../types/trip'; +import { ApiService } from './api.service'; +import { map } from 'rxjs'; -type ToastSeverity = "info" | "warn" | "error" | "success"; +type ToastSeverity = 'info' | 'warn' | 'error' | 'success'; @Injectable({ - providedIn: "root", + providedIn: 'root', }) export class UtilsService { private apiService = inject(ApiService); - currency$ = this.apiService.settings$.pipe(map((s) => s?.currency ?? "€")); + currency$ = this.apiService.settings$.pipe(map((s) => s?.currency ?? '€')); readonly statuses: TripStatus[] = [ - { label: "pending", color: "#3258A8" }, - { label: "booked", color: "#00A341" }, - { label: "constraint", color: "#FFB900" }, - { label: "optional", color: "#625A84" }, + { label: 'pending', color: '#3258A8' }, + { label: 'booked', color: '#00A341' }, + { label: 'constraint', color: '#FFB900' }, + { label: 'optional', color: '#625A84' }, ]; constructor(private ngMessageService: MessageService) {} toGithubTRIP() { - window.open("https://github.com/itskovacs/trip", "_blank"); + window.open('https://github.com/itskovacs/trip', '_blank'); } toggleDarkMode() { - const element = document.querySelector("html"); - element?.classList.toggle("dark"); + const element = document.querySelector('html'); + element?.classList.toggle('dark'); } enableDarkMode() { - const element = document.querySelector("html"); - element?.classList.toggle("dark", true); + const element = document.querySelector('html'); + element?.classList.toggle('dark', true); } - toast( - severity: ToastSeverity = "info", - summary = "Info", - detail = "", - life = 3000, - ): void { + toast(severity: ToastSeverity = 'info', summary = 'Info', detail = '', life = 3000): void { this.ngMessageService.add({ severity, summary, @@ -57,12 +52,12 @@ export class UtilsService { const lngMatch = url.match(/!4d([\d\-.]+)/); if (!placeMatch || !latMatch || !lngMatch) { - this.toast("error", "Error", "Unrecognized Google Maps URL format"); - console.error("Unrecognized Google Maps URL format"); - return ["", ""]; + this.toast('error', 'Error', 'Unrecognized Google Maps URL format'); + console.error('Unrecognized Google Maps URL format'); + return ['', '']; } - const place = decodeURIComponent(placeMatch[1].replace(/\+/g, " ").trim()); + const place = decodeURIComponent(placeMatch[1].replace(/\+/g, ' ').trim()); const latlng = `${latMatch[1]},${lngMatch[1]}`; return [place, latlng]; } diff --git a/src/src/app/shared/haversine.ts b/src/src/app/shared/haversine.ts index 7bd7d79..5260bb5 100644 --- a/src/src/app/shared/haversine.ts +++ b/src/src/app/shared/haversine.ts @@ -1,9 +1,4 @@ -export function calculateDistanceBetween( - lat1: number, - lon1: number, - lat2: number, - lon2: number, -) { +export function calculateDistanceBetween(lat1: number, lon1: number, lat2: number, lon2: number) { // returns d in meter const toRad = (deg: number) => (deg * Math.PI) / 180; const dLat = toRad(lat2 - lat1); diff --git a/src/src/app/shared/latlng-parser.ts b/src/src/app/shared/latlng-parser.ts index 29f500d..3f474bf 100644 --- a/src/src/app/shared/latlng-parser.ts +++ b/src/src/app/shared/latlng-parser.ts @@ -1,19 +1,13 @@ -import OpenLocationCode from "open-location-code-typescript"; +import OpenLocationCode from 'open-location-code-typescript'; const patternDEC = /^\s*(-?\d{1,3}(?:\.\d+)?)\s*,\s*(-?\d{1,3}(?:\.\d+)?)\s*$/; -const patternDD = - /^\s*(\d{1,3}(?:\.\d+)?)°?\s*([NS])\s*,\s*(\d{1,3}(?:\.\d+)?)°?\s*([EW])\s*$/i; +const patternDD = /^\s*(\d{1,3}(?:\.\d+)?)°?\s*([NS])\s*,\s*(\d{1,3}(?:\.\d+)?)°?\s*([EW])\s*$/i; const patternDMS = /^\s*(\d{1,3})°\s*(\d{1,2})['′]\s*(\d{1,2}(?:\.\d+)?)["″]?\s*([NS])\s*,\s*(\d{1,3})°\s*(\d{1,2})['′]\s*(\d{1,2}(?:\.\d+)?)["″]?\s*([EW])\s*$/i; const patternDMM = /^\s*(\d{1,3})°\s*(\d{1,2}(?:\.\d+)?)['′]?\s*([NS])\s*,\s*(\d{1,3})°\s*(\d{1,2}(?:\.\d+)?)['′]?\s*([EW])\s*$/i; -function _dmsToDecimal( - deg: number, - min: number, - sec: number, - dir: string, -): number { +function _dmsToDecimal(deg: number, min: number, sec: number, dir: string): number { const dec = deg + min / 60 + sec / 3600; return /[SW]/i.test(dir) ? -dec : dec; } @@ -24,14 +18,12 @@ function _dmmToDecimal(deg: number, min: number, dir: string): number { } export function formatLatLng(num: number): string { - const decimals = num.toString().split(".")[1]?.length || 0; + const decimals = num.toString().split('.')[1]?.length || 0; return num.toFixed(Math.min(decimals, 5)); } -export function checkAndParseLatLng( - value: string | number, -): [number, number] | undefined { - if (typeof value !== "string") return undefined; +export function checkAndParseLatLng(value: string | number): [number, number] | undefined { + if (typeof value !== 'string') return undefined; // Parse PlusCode if (OpenLocationCode.isValid(value)) { diff --git a/src/src/app/shared/linkify.pipe.ts b/src/src/app/shared/linkify.pipe.ts index fee2bfc..ea95395 100644 --- a/src/src/app/shared/linkify.pipe.ts +++ b/src/src/app/shared/linkify.pipe.ts @@ -1,7 +1,7 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -@Pipe({ name: "linkify", standalone: true }) +@Pipe({ name: 'linkify', standalone: true }) export class LinkifyPipe implements PipeTransform { constructor(private sanitizer: DomSanitizer) {} @@ -10,11 +10,11 @@ export class LinkifyPipe implements PipeTransform { /[&<>"']/g, (char) => ({ - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', })[char]!, ); } @@ -26,7 +26,7 @@ export class LinkifyPipe implements PipeTransform { const safeText = this.basicEscape(text); const html = safeText.replace(urlRegex, (url) => { - const href = url.startsWith("http") ? url : `https://${url}`; + const href = url.startsWith('http') ? url : `https://${url}`; return `${url}`; }); diff --git a/src/src/app/shared/map.ts b/src/src/app/shared/map.ts index e10b693..020cb74 100644 --- a/src/src/app/shared/map.ts +++ b/src/src/app/shared/map.ts @@ -1,10 +1,9 @@ -import * as L from "leaflet"; -import "leaflet.markercluster"; -import "leaflet-contextmenu"; -import { Place } from "../types/poi"; +import * as L from 'leaflet'; +import 'leaflet.markercluster'; +import 'leaflet-contextmenu'; +import { Place } from '../types/poi'; -export const DEFAULT_TILE_URL = - "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png"; +export const DEFAULT_TILE_URL = 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png'; export interface ContextMenuItem { text: string; index?: number; @@ -20,15 +19,12 @@ export interface MarkerOptions extends L.MarkerOptions { contextmenuItems: ContextMenuItem[]; } -export function createMap( - contextMenuItems: ContextMenuItem[] = [], - tilelayer: string = DEFAULT_TILE_URL, -): L.Map { +export function createMap(contextMenuItems: ContextMenuItem[] = [], tilelayer: string = DEFAULT_TILE_URL): L.Map { const southWest = L.latLng(-89.99, -180); const northEast = L.latLng(89.99, 180); const bounds = L.latLngBounds(southWest, northEast); - const map = L.map("map", { + const map = L.map('map', { maxBoundsViscosity: 1.0, zoomControl: false, contextmenu: true, @@ -61,34 +57,29 @@ export function createClusterGroup(): L.MarkerClusterGroup { const count = cluster.getChildCount(); return L.divIcon({ html: `
${count}
`, - className: "", + className: '', iconSize: [40, 40], }); }, }); } -export function tripDayMarker(item: { - text: string; - lat: number; - lng: number; - time?: string; -}): L.Marker { +export function tripDayMarker(item: { text: string; lat: number; lng: number; time?: string }): L.Marker { const marker = new L.Marker([item.lat!, item.lng], { icon: L.divIcon({ - className: "bg-black rounded-full", + className: 'bg-black rounded-full', iconSize: [14, 14], }), }); - const touchDevice = "ontouchstart" in window; + const touchDevice = 'ontouchstart' in window; if (!touchDevice) { marker.bindTooltip( `
${item.time}
${item.text}
`, { - direction: "right", + direction: 'right', offset: [10, 0], - className: "class-tooltip", + className: 'class-tooltip', }, ); } @@ -104,28 +95,25 @@ export function placeToMarker( const options: Partial = { riseOnHover: true, title: place.name, - alt: "", + alt: '', }; - const markerImage = isLowNet - ? place.category.image - : (place.image ?? place.category.image); + const markerImage = isLowNet ? place.category.image : (place.image ?? place.category.image); - let markerClasses = - "w-full h-full rounded-full bg-center bg-cover bg-white dark:bg-surface-900"; - if (grayscale) markerClasses += " grayscale"; + let markerClasses = 'w-full h-full rounded-full bg-center bg-cover bg-white dark:bg-surface-900'; + if (grayscale) markerClasses += ' grayscale'; const iconHtml = `
- ${gpxInBubble && place.gpx ? '
' : ""} + ${gpxInBubble && place.gpx ? '
' : ''}
`; const icon = L.divIcon({ html: iconHtml.trim(), iconSize: [56, 56], - className: "", + className: '', }); const marker = new L.Marker([+place.lat, +place.lng], { @@ -133,12 +121,12 @@ export function placeToMarker( icon, }); - const touchDevice = "ontouchstart" in window; + const touchDevice = 'ontouchstart' in window; if (!touchDevice) { marker.bindTooltip(placeHoverTooltip(place), { - direction: "right", + direction: 'right', offset: [28, 0], - className: "class-tooltip", + className: 'class-tooltip', }); } return marker; @@ -146,16 +134,12 @@ export function placeToMarker( export function gpxToPolyline(gpx: string): L.Polyline { const parser = new DOMParser(); - const gpxDoc = parser.parseFromString(gpx, "application/xml"); + const gpxDoc = parser.parseFromString(gpx, 'application/xml'); - const trkpts = Array.from(gpxDoc.querySelectorAll("trkpt")); + const trkpts = Array.from(gpxDoc.querySelectorAll('trkpt')); const latlngs = trkpts.map( - (pt) => - [ - parseFloat(pt.getAttribute("lat")!), - parseFloat(pt.getAttribute("lon")!), - ] as [number, number], + (pt) => [parseFloat(pt.getAttribute('lat')!), parseFloat(pt.getAttribute('lon')!)] as [number, number], ); - return L.polyline(latlngs, { color: "blue" }); + return L.polyline(latlngs, { color: 'blue' }); } diff --git a/src/src/app/shared/order-by.pipe.ts b/src/src/app/shared/order-by.pipe.ts index 33e4a90..9546a53 100644 --- a/src/src/app/shared/order-by.pipe.ts +++ b/src/src/app/shared/order-by.pipe.ts @@ -1,6 +1,6 @@ -import { Pipe, PipeTransform } from "@angular/core"; +import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ - name: "orderBy", + name: 'orderBy', pure: true, standalone: true, }) @@ -9,8 +9,6 @@ export class orderByPipe implements PipeTransform { if (!items || items.length === 0) { return items; } - return items - .slice() - .sort((a, b) => (a[field] < b[field] ? -1 : a[field] > b[field] ? 1 : 0)); + return items.slice().sort((a, b) => (a[field] < b[field] ? -1 : a[field] > b[field] ? 1 : 0)); } } diff --git a/src/src/app/shared/place-box/place-box.component.ts b/src/src/app/shared/place-box/place-box.component.ts index 6895d59..5211c0b 100644 --- a/src/src/app/shared/place-box/place-box.component.ts +++ b/src/src/app/shared/place-box/place-box.component.ts @@ -1,26 +1,19 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Input, - OnInit, - Output, -} from "@angular/core"; -import { ButtonModule } from "primeng/button"; -import { MenuModule } from "primeng/menu"; -import { Place } from "../../types/poi"; -import { MenuItem } from "primeng/api"; -import { UtilsService } from "../../services/utils.service"; -import { Observable } from "rxjs"; -import { AsyncPipe } from "@angular/common"; -import { LinkifyPipe } from "../linkify.pipe"; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ButtonModule } from 'primeng/button'; +import { MenuModule } from 'primeng/menu'; +import { Place } from '../../types/poi'; +import { MenuItem } from 'primeng/api'; +import { UtilsService } from '../../services/utils.service'; +import { Observable } from 'rxjs'; +import { AsyncPipe } from '@angular/common'; +import { LinkifyPipe } from '../linkify.pipe'; @Component({ - selector: "app-place-box", + selector: 'app-place-box', standalone: true, imports: [ButtonModule, MenuModule, AsyncPipe, LinkifyPipe], - templateUrl: "./place-box.component.html", - styleUrls: ["./place-box.component.scss"], + templateUrl: './place-box.component.html', + styleUrls: ['./place-box.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class PlaceBoxComponent implements OnInit { @@ -43,33 +36,33 @@ export class PlaceBoxComponent implements OnInit { ngOnInit() { const items = [ { - label: "Edit", - icon: "pi pi-pencil", - iconClass: "text-blue-500!", + label: 'Edit', + icon: 'pi pi-pencil', + iconClass: 'text-blue-500!', command: () => { this.editPlace(); }, }, { - label: "Favorite", - icon: "pi pi-star", - iconClass: "text-yellow-500!", + label: 'Favorite', + icon: 'pi pi-star', + iconClass: 'text-yellow-500!', command: () => { this.favoritePlace(); }, }, { - label: "Mark", - icon: "pi pi-check", - iconClass: "text-green-500!", + label: 'Mark', + icon: 'pi pi-check', + iconClass: 'text-green-500!', command: () => { this.visitPlace(); }, }, { - label: "Delete", - icon: "pi pi-trash", - iconClass: "text-red-500!", + label: 'Delete', + icon: 'pi pi-trash', + iconClass: 'text-red-500!', command: () => { this.deletePlace(); }, @@ -78,9 +71,9 @@ export class PlaceBoxComponent implements OnInit { if (this.selectedPlace?.gpx) { items.unshift({ - label: "Display GPX", - icon: "pi pi-compass", - iconClass: "text-gray-500!", + label: 'Display GPX', + icon: 'pi pi-compass', + iconClass: 'text-gray-500!', command: () => { this.displayGPX(); }, @@ -89,7 +82,7 @@ export class PlaceBoxComponent implements OnInit { this.menuItems = [ { - label: "Place", + label: 'Place', items: items, }, ]; diff --git a/src/src/app/shared/place-gpx/place-gpx.component.ts b/src/src/app/shared/place-gpx/place-gpx.component.ts index 6a49da8..923f696 100644 --- a/src/src/app/shared/place-gpx/place-gpx.component.ts +++ b/src/src/app/shared/place-gpx/place-gpx.component.ts @@ -1,19 +1,13 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Input, - Output, -} from "@angular/core"; -import { ButtonModule } from "primeng/button"; -import { Place } from "../../types/poi"; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ButtonModule } from 'primeng/button'; +import { Place } from '../../types/poi'; @Component({ - selector: "app-place-gpx", + selector: 'app-place-gpx', standalone: true, imports: [ButtonModule], - templateUrl: "./place-gpx.component.html", - styleUrls: ["./place-gpx.component.scss"], + templateUrl: './place-gpx.component.html', + styleUrls: ['./place-gpx.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class PlaceGPXComponent { @@ -33,6 +27,6 @@ export class PlaceGPXComponent { } downloadTrace() { - this.downloadEmitter.emit() + this.downloadEmitter.emit(); } } diff --git a/src/src/app/types/settings.ts b/src/src/app/types/settings.ts index d082d3f..1bbfcc1 100644 --- a/src/src/app/types/settings.ts +++ b/src/src/app/types/settings.ts @@ -1,4 +1,4 @@ -import { Category, Place } from "./poi"; +import { Category, Place } from './poi'; export interface Settings { username: string; diff --git a/src/src/app/types/trip.ts b/src/src/app/types/trip.ts index afaa30f..ef2f536 100644 --- a/src/src/app/types/trip.ts +++ b/src/src/app/types/trip.ts @@ -1,4 +1,4 @@ -import { Place } from "./poi"; +import { Place } from './poi'; export interface TripBase { id: number; diff --git a/src/src/main.ts b/src/src/main.ts index 8882c45..17447a5 100644 --- a/src/src/main.ts +++ b/src/src/main.ts @@ -2,6 +2,4 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; import { AppComponent } from './app/app.component'; -bootstrapApplication(AppComponent, appConfig).catch((err) => - console.error(err), -); +bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err)); diff --git a/src/src/mytheme.ts b/src/src/mytheme.ts index 6179ee9..bffff07 100644 --- a/src/src/mytheme.ts +++ b/src/src/mytheme.ts @@ -388,24 +388,20 @@ export const TripThemePreset = definePreset(Aura, { overlay: { select: { borderRadius: '{border.radius.md}', - shadow: - '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', + shadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', }, popover: { borderRadius: '{border.radius.md}', padding: '0.75rem', - shadow: - '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', + shadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', }, modal: { borderRadius: '{border.radius.xl}', padding: '1.25rem', - shadow: - '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)', + shadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)', }, navigation: { - shadow: - '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', + shadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', }, }, colorScheme: { diff --git a/src/src/styles.scss b/src/src/styles.scss index 4dd4911..3eceb59 100644 --- a/src/src/styles.scss +++ b/src/src/styles.scss @@ -1,9 +1,9 @@ -@use "primeicons/primeicons.css"; +@use 'primeicons/primeicons.css'; @plugin 'tailwindcss-primeui'; @variant dark (&:where(.dark, .dark *)); @layer tailwind { - @import "tailwindcss"; + @import 'tailwindcss'; } * { @@ -30,17 +30,17 @@ html { font-size: 15px; font-family: - "Inter", + 'Inter', -apple-system, BlinkMacSystemFont, - "Segoe UI", + 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol"; + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol'; line-height: normal; } @@ -82,7 +82,7 @@ html { -webkit-user-select: none; -moz-user-select: none; user-select: none; - font-family: "Inter", sans-serif; + font-family: 'Inter', sans-serif; font-size: 15px; }