feat(client): show Dawarich stays, routes and marks where they belong

The trip planner and the journal get a "From Dawarich" list: the stays
recorded for those dates, each one a row that can be dismissed or turned
into a place or an entry after a review step that shows time, name, note
and day. A place that came from a recording carries the Dawarich mark
next to its name so its origin stays visible afterwards.

The recorded route lies over the trip and journal maps as an overlay,
coloured per day and cut along the reader's own timezone rather than
UTC. It is fetched for the window on screen and never stored.

The Atlas gets its own panel beside the map for the countries and cities
the recordings prove, and the wish list is ticked off there, on the day
of the visit, undoably. The phone has its own take on all of it, in the
mobile shell's colours and with native sheets.
This commit is contained in:
Maurice
2026-09-12 23:35:43 +02:00
parent 5678eed55c
commit 5b4a7e82ff
108 changed files with 8945 additions and 295 deletions
+18 -2
View File
@@ -7,6 +7,8 @@ import { useToast } from '../shared/Toast'
import { Puzzle, ListChecks, Wallet, FileText, CalendarDays, Globe, Briefcase, Image, Terminal, Link2, Compass, BookOpen, MessageCircle, StickyNote, BarChart3, Sparkles, Luggage, Plane, Server, Cloud, Bookmark, Users, Loader2 } from 'lucide-react'
import CustomSelect from '../shared/CustomSelect'
import EmptyState from '../shared/EmptyState'
import DawarichIcon from '../shared/DawarichIcon'
import AirTrailIcon from '../shared/AirTrailIcon'
import AddonTile from './AddonTile'
import AddonSubRow from './AddonSubRow'
@@ -15,6 +17,9 @@ import AddonSubRow from './AddonSubRow'
// used to land on the fallback.
const ICON_MAP = {
ListChecks, Wallet, FileText, CalendarDays, Puzzle, Globe, Briefcase, Image, Terminal, Link2, Compass, BookOpen, Plane, Bookmark, Users, Sparkles,
// Dawarich ships a brand mark rather than a lucide name, the same way the photo
// providers below do. Keyed by the string in the addons table.
Dawarich: DawarichIcon,
}
function ImmichIcon({ size = 14 }: { size?: number }) {
@@ -59,9 +64,20 @@ interface ProviderOption {
interface AddonIconProps {
name: string
size?: number
/** A switched-off addon greys its icon out, brand marks included. */
enabled?: boolean
}
function AddonIcon({ name, size = 20 }: AddonIconProps) {
function AddonIcon({ name, size = 20, enabled = true }: AddonIconProps) {
// The brand marks are badges, not glyphs: they fill the framed slot instead of
// sitting in the middle of it at glyph size, and they grey out when the addon
// is off — the lucide glyphs get that from the slot's text colour, an <img>
// has to be told.
if (name === 'Dawarich') return <DawarichIcon fill muted={!enabled} />
// 'Plane' is the icon string airtrail was seeded with, and INSERT OR IGNORE
// means every existing install still carries it — so the brand is keyed on
// that rather than on a new name no row would ever have.
if (name === 'Plane') return <AirTrailIcon fill muted={!enabled} />
const Icon = ICON_MAP[name] || Puzzle
return <Icon size={size} />
}
@@ -212,7 +228,7 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking,
return (
<AddonTile
key={addon.id}
icon={<AddonIcon name={addon.icon} size={18} />}
icon={<AddonIcon name={addon.icon} size={18} enabled={addon.enabled} />}
name={label.name}
description={label.description}
enabled={addon.enabled}
+3 -1
View File
@@ -44,7 +44,9 @@ export default function AddonTile({
>
<div className="flex items-start gap-3 p-4">
<div
className={`grid h-10 w-10 shrink-0 place-items-center rounded-xl ${
// overflow-hidden so a brand mark that fills the slot keeps the slot's
// rounded corners instead of squaring them off.
className={`grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-xl ${
enabled ? 'border border-accent bg-accent-subtle text-accent-on' : 'bg-surface-tertiary text-content-faint'
}`}
>