Files
TREK/client/src/store/settingsStore.ts
T
Maurice 49b3af8b0d feat: optimize routes around accommodation, confirm note deletions (#1123)
Optimize day routes around the accommodation

When a day has an accommodation set, the route optimizer now treats it as
the day's home base: it optimizes a loop that leaves the hotel and returns
to it, so the stop nearest the hotel comes first. On a transfer day -
checking out of one hotel and into another - the route runs from the first
hotel to the second instead.

The optimizer also gained a 2-opt pass on top of the nearest-neighbor
ordering, which removes the crossings the greedy pass used to leave behind.
A new display setting ("optimize route from accommodation", on by default)
lets you turn the anchoring off.

Confirm before deleting notes

Deleting a plan note or a collab note now asks for confirmation first. On
phones and tablets the edit and delete icons sit close together and were
easy to mis-tap, which deleted notes with no way back.
2026-06-07 12:52:06 +02:00

95 lines
3.2 KiB
TypeScript

import { create } from 'zustand'
import { settingsApi } from '../api/client'
import type { Settings } from '../types'
import { getApiErrorMessage } from '../types'
import { SUPPORTED_LANGUAGE_CODES } from '../i18n/supportedLanguages'
interface SettingsState {
settings: Settings
isLoaded: boolean
loadSettings: () => Promise<void>
updateSetting: (key: keyof Settings, value: Settings[keyof Settings]) => Promise<void>
setLanguageLocal: (lang: string) => void
setLanguageTransient: (lang: string) => void
updateSettings: (settingsObj: Partial<Settings>) => Promise<void>
}
// Returns true when the user has explicitly chosen a language (persisted in localStorage).
// Use this instead of reading localStorage directly so the key stays encapsulated here.
export const hasStoredLanguage = (): boolean =>
typeof localStorage !== 'undefined' && !!localStorage.getItem('app_language')
export const useSettingsStore = create<SettingsState>((set, get) => ({
settings: {
map_tile_url: '',
default_lat: 48.8566,
default_lng: 2.3522,
default_zoom: 10,
dark_mode: false,
default_currency: 'USD',
language: localStorage.getItem('app_language') || 'en',
temperature_unit: 'fahrenheit',
time_format: '12h',
show_place_description: false,
optimize_from_accommodation: true,
map_provider: 'leaflet',
mapbox_access_token: '',
mapbox_style: 'mapbox://styles/mapbox/standard',
mapbox_3d_enabled: true,
mapbox_quality_mode: false,
},
isLoaded: false,
loadSettings: async () => {
try {
const data = await settingsApi.get()
set((state) => ({
settings: { ...state.settings, ...data.settings },
isLoaded: true,
}))
} catch (err: unknown) {
set({ isLoaded: true })
console.error('Failed to load settings:', err)
}
},
updateSetting: async (key: keyof Settings, value: Settings[keyof Settings]) => {
set((state) => ({
settings: { ...state.settings, [key]: value },
}))
if (key === 'language') localStorage.setItem('app_language', value as string)
try {
await settingsApi.set(key, value)
} catch (err: unknown) {
console.error('Failed to save setting:', err)
throw new Error(getApiErrorMessage(err, 'Error saving setting'))
}
},
setLanguageLocal: (lang: string) => {
localStorage.setItem('app_language', lang)
set((state) => ({ settings: { ...state.settings, language: lang } }))
},
// Applies a language for the current session without persisting to localStorage.
// Used for automatic detection (browser/server default) — only explicit user
// choices via the UI should be persisted.
setLanguageTransient: (lang: string) => {
if (!SUPPORTED_LANGUAGE_CODES.includes(lang)) return
set((state) => ({ settings: { ...state.settings, language: lang } }))
},
updateSettings: async (settingsObj: Partial<Settings>) => {
set((state) => ({
settings: { ...state.settings, ...settingsObj },
}))
try {
await settingsApi.setBulk(settingsObj)
} catch (err: unknown) {
console.error('Failed to save settings:', err)
throw new Error(getApiErrorMessage(err, 'Error saving settings'))
}
},
}))