mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
0dd07befa7
The Plan tab was the last trip tab still in the old design. It now reads like Bookings, Costs, Lists, Files and Collab, without losing a function: - Day plan: each day is a card with a head band (day tile with weather, stay and rental pills, a "+" menu for place, stay, transport, transit and note), rows with "..." menus, white badges and a white route bar. - Places column: add, search and filters in three rows, a show dropdown, category and rating filters, and a selection bar at the foot. - Place inspector, day details and the stay editor on the shared dialog frame; the place form in the editor look with the name in the head band. - Clicking a booking or transport anywhere on the plan opens the booking detail popup first; Edit leads to the editor. The popup is one host shared with the Bookings and Transports tabs. - Files and Collab dialogs, notes, polls, links and chat moved to the same frame; shared DialogShell and dialogParts hold the building blocks. - The map hover card shows the place photo and follows the theme. Every new and reworded string is translated into all locales, and a few older English leftovers (help center, cost split, import hints) with them. Phone screens keep their own look.
84 lines
3.3 KiB
TypeScript
84 lines
3.3 KiB
TypeScript
import { useId } from 'react'
|
|
import { MapPin, Tag } from 'lucide-react'
|
|
import { getCategoryIcon } from '../shared/categoryIcons'
|
|
import { DialogButton, DialogFooter, DialogHeader, DialogShell, DialogTile, FooterSpacer, NEUTRAL_TINT, fs } from '../shared/DialogShell'
|
|
import { useTranslation } from '../../i18n'
|
|
import { tintOf } from './planParts'
|
|
import type { Category } from '../../types'
|
|
|
|
interface PlacesBulkCategoryModalProps {
|
|
count: number
|
|
categories: Category[]
|
|
onPick: (categoryId: number | null) => void
|
|
onClose: () => void
|
|
}
|
|
|
|
/**
|
|
* A category's icon on a wash of its colour, the square the category lists of the
|
|
* places column are made of. Without a category it is the neutral pin.
|
|
*/
|
|
export function CategoryTile({ category, size = 24 }: { category?: Pick<Category, 'icon' | 'color'> | null; size?: number }) {
|
|
if (!category) {
|
|
return (
|
|
<span className="grid flex-none place-items-center rounded-[7px] bg-surface-tertiary text-content-faint" style={{ width: size, height: size }}>
|
|
<MapPin size={Math.round(size * 0.5)} strokeWidth={2.2} />
|
|
</span>
|
|
)
|
|
}
|
|
const Icon = getCategoryIcon(category.icon)
|
|
const color = category.color || 'var(--text-muted)'
|
|
return (
|
|
<span className="grid flex-none place-items-center rounded-[7px]" style={{ width: size, height: size, background: tintOf(color, 16) }}>
|
|
<Icon size={Math.round(size * 0.5)} strokeWidth={2.2} style={{ color }} />
|
|
</span>
|
|
)
|
|
}
|
|
|
|
const ROW = 'flex w-full items-center gap-2.5 rounded-[10px] px-2 py-1.5 text-left hover:bg-surface-hover'
|
|
|
|
/**
|
|
* Popup for the Places selection toolbar: pick one category to apply to every
|
|
* currently-selected place. Clicking a row applies it at once and closes.
|
|
*/
|
|
export function PlacesBulkCategoryModal({ count, categories, onPick, onClose }: PlacesBulkCategoryModalProps) {
|
|
const { t } = useTranslation()
|
|
const titleId = useId()
|
|
return (
|
|
<DialogShell
|
|
onClose={onClose}
|
|
labelledBy={titleId}
|
|
width="narrow"
|
|
header={(
|
|
<DialogHeader
|
|
tile={<DialogTile><Tag size={20} strokeWidth={1.9} className="text-content" /></DialogTile>}
|
|
tint={NEUTRAL_TINT}
|
|
labelId={titleId}
|
|
onClose={onClose}
|
|
title={t('places.changeCategory')}
|
|
sub={t('places.selectionCount', { count })}
|
|
/>
|
|
)}
|
|
footer={(
|
|
<DialogFooter>
|
|
<FooterSpacer />
|
|
<DialogButton onClick={onClose}>{t('common.cancel')}</DialogButton>
|
|
</DialogFooter>
|
|
)}
|
|
>
|
|
<div className="flex flex-col gap-0.5 rounded-[14px] border border-edge-faint bg-surface-secondary p-1.5">
|
|
{categories.map(c => (
|
|
<button type="button" key={c.id} onClick={() => onPick(c.id)} className={ROW} style={fs(13, 'body')}>
|
|
<CategoryTile category={c} size={28} />
|
|
<span className="min-w-0 flex-1 truncate font-medium text-content">{c.name}</span>
|
|
</button>
|
|
))}
|
|
{categories.length > 0 && <div className="mx-2 my-1 h-px bg-edge-faint" />}
|
|
<button type="button" onClick={() => onPick(null)} className={ROW} style={fs(13, 'body')}>
|
|
<CategoryTile size={28} />
|
|
<span className="min-w-0 flex-1 truncate font-medium text-content-muted">{t('places.noCategory')}</span>
|
|
</button>
|
|
</div>
|
|
</DialogShell>
|
|
)
|
|
}
|