Files
TREK/client/src/components/Planner/DayAddFooter.tsx
T
Maurice 0dd07befa7 feat(planner): bring the plan tab and every trip dialog into the new look
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.
2026-09-27 21:00:19 +02:00

95 lines
3.5 KiB
TypeScript

import { useId, useState } from 'react'
import { CalendarPlus, Plus } from 'lucide-react'
import { DialogButton, FooterSpacer, fs } from '../shared/DialogShell'
import { dayDate } from '../../utils/dayLabel'
import type { DayAddControls } from '../../utils/dayAdd'
interface DayAddFooterProps {
/** Without it, or on a trip without dates, the footer keeps its single "Add day" button. */
dayAdd?: DayAddControls
onAddDay: () => void
onClose: () => void
t: (key: string, params?: Record<string, string | number>) => string
locale: string
}
/**
* The bar of a dialog with a line over its buttons. DialogFooter is one row;
* this is the same bar (border, inset) holding the line and the row under it.
*/
const FOOTER_COLUMN = 'flex flex-none flex-col gap-2.5 border-t border-edge-faint px-6 py-3.5'
const HINT = 'm-0 text-content-muted'
/** Should a language run long, the row wraps between buttons, never inside one. */
const ROW = 'flex flex-wrap items-center gap-2'
/**
* The foot of the reorder dialog: close it, or add a day.
*
* On a trip with dates there are two ways to add one. The next calendar day,
* named on its button, extends the trip by one; a day without a date goes to
* the end and leaves the trip dates alone. The line above them says what the
* button in view does, and switches while the pointer or the focus rests on
* the other one. When no day can be added it says why instead.
*/
export function DayAddFooter({ dayAdd, onAddDay, onClose, t, locale }: DayAddFooterProps) {
const [aboutUndated, setAboutUndated] = useState(false)
const hintId = useId()
const busy = dayAdd?.busy ?? false
const blocked = dayAdd?.blocked ?? null
const addOff = busy || !!blocked
const closeButton = <DialogButton onClick={onClose}>{t('common.close')}</DialogButton>
if (!dayAdd?.nextDate) {
return (
<footer className={FOOTER_COLUMN}>
{blocked && <p className={HINT} style={fs(12)}>{blocked}</p>}
<div className={ROW}>
<FooterSpacer />
{closeButton}
<DialogButton variant="primary" onClick={onAddDay} disabled={addOff} icon={<Plus size={14} strokeWidth={2} aria-hidden="true" />}>
{t('dayplan.addDay')}
</DialogButton>
</div>
</footer>
)
}
const date = dayDate(dayAdd.nextDate, locale) ?? dayAdd.nextDate
const hint = blocked
?? (aboutUndated ? t('dayplan.addUndatedDayHint') : dayAdd.datedBlocked ?? t('dayplan.addDatedDayHint', { date }))
return (
<footer className={FOOTER_COLUMN}>
<p id={hintId} aria-live="polite" className={HINT} style={fs(12)}>
{hint}
</p>
<div className={ROW}>
<DialogButton
onClick={onAddDay}
disabled={addOff}
aria-describedby={hintId}
icon={<Plus size={14} strokeWidth={2} aria-hidden="true" />}
onMouseEnter={() => setAboutUndated(true)}
onMouseLeave={() => setAboutUndated(false)}
onFocus={() => setAboutUndated(true)}
onBlur={() => setAboutUndated(false)}
>
{t('dayplan.addUndatedDay')}
</DialogButton>
<FooterSpacer />
{closeButton}
<DialogButton
variant="primary"
onClick={dayAdd.onAddDated}
disabled={addOff || !!dayAdd.datedBlocked}
aria-describedby={hintId}
icon={<CalendarPlus size={14} strokeWidth={2} aria-hidden="true" />}
>
{t('dayplan.addDatedDay', { date })}
</DialogButton>
</div>
</footer>
)
}