mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
a3fc696510
The cards were rows of badges in badges with no head of their own; they now follow the frame #2541 gave the trip's dialogs and booking cards. - Every answer card and the change card open on a tinted head band: a raised tile with the icon (a route its sketch), the kind as an eyebrow, the title, one quiet line and the count; the change card turns green once decided. - Figures are framed fields under their labels (a route's distance, climb, descent, time), the parts of a card sit under eyebrows (height profile, stops), and the ways onward sit in a foot bar across the card. - A route's stops are a timeline down a line, each walk on the line between two stops; the ways of a route are calm choices with their figures in one line and how they differ from the chosen one in another. - Packing, to-dos, the journal, Vacay and the Atlas get the band; quantities and weights are quiet figures, an expense's place and split share one line.
96 lines
3.9 KiB
TypeScript
96 lines
3.9 KiB
TypeScript
import type { CSSProperties } from 'react';
|
|
|
|
import { Palmtree } from 'lucide-react';
|
|
|
|
import { useTranslation } from '../../../../i18n';
|
|
import { safeHexColor } from '../../../../utils/safeColor';
|
|
import type { CardEntry } from '../cardTypes';
|
|
import CardFrame from '../parts/CardFrame';
|
|
import CardHeader from '../parts/CardHeader';
|
|
import { CardOpenButton } from '../parts/CardOpen';
|
|
import { CardFoot } from '../parts/CardSection';
|
|
import { useViewerId } from '../parts/useViewerId';
|
|
import VacayBalance from './VacayBalance';
|
|
import { balanceLines, mergeVacayCards, type VacayView } from './vacayCardModel';
|
|
import { personColor } from './vacayCellLook';
|
|
import { vacayPathOf } from './vacayLook';
|
|
import VacayMonthStrip from './VacayMonthStrip';
|
|
|
|
/**
|
|
* What the vacation planner's reads found, as one card: the months with the
|
|
* days off, a legend once more than one thing is drawn, the days off outside
|
|
* the months shown, the days left per person, and the way into Vacay on that
|
|
* year. Several reads in one answer read as one calendar.
|
|
*/
|
|
|
|
const LEGEND = 'mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-caption text-content-muted';
|
|
const LEGEND_ITEM = 'inline-flex min-w-0 items-center gap-1.5';
|
|
const SWATCH = 'size-2 flex-none rounded-full';
|
|
const SQUARE = 'size-2 flex-none rounded-sm';
|
|
const COMPANY = 'size-2 flex-none rounded-sm bg-warning-soft ring-1 ring-inset ring-warning';
|
|
const TRIP = 'size-1.5 flex-none rounded-full bg-info';
|
|
const OTHER = 'mt-1.5 text-caption text-content-muted';
|
|
|
|
interface LegendItem {
|
|
key: string;
|
|
label: string;
|
|
className: string;
|
|
style?: CSSProperties;
|
|
}
|
|
|
|
/** What the calendar draws, named once it draws more than one thing. */
|
|
function Legend({ view }: { view: VacayView }) {
|
|
const { t } = useTranslation();
|
|
const viewerId = useViewerId();
|
|
const people = view.people.filter((person) => view.days.some((day) => day.personId === person.id));
|
|
const holiday = view.holidays[0];
|
|
const items: LegendItem[] = people.map((person) => ({
|
|
key: `person-${person.id}`,
|
|
label: person.id === viewerId ? t('assistant.card.common.you') : person.name,
|
|
className: SWATCH,
|
|
style: { background: personColor(view, person.id) },
|
|
}));
|
|
if (holiday) {
|
|
const color = safeHexColor(holiday.color, 'var(--danger)');
|
|
items.push({
|
|
key: 'holiday',
|
|
label: t('vacay.publicHoliday'),
|
|
className: SQUARE,
|
|
style: { background: `color-mix(in srgb, ${color} 28%, transparent)` },
|
|
});
|
|
}
|
|
if (view.company.length > 0) items.push({ key: 'company', label: t('vacay.companyHoliday'), className: COMPANY });
|
|
if (view.tripDates.size > 0) items.push({ key: 'trip', label: t('assistant.card.vacay.trip'), className: TRIP });
|
|
if (items.length < 2) return null;
|
|
return (
|
|
<ul className={LEGEND}>
|
|
{items.map((item) => (
|
|
<li key={item.key} className={LEGEND_ITEM}>
|
|
<span aria-hidden="true" className={item.className} style={item.style} />
|
|
<bdi className="truncate">{item.label}</bdi>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
}
|
|
|
|
export default function VacayAnswerBlock({ cards }: { cards: readonly CardEntry[] }) {
|
|
const { t } = useTranslation();
|
|
const own = cards.flatMap((entry) => (entry.card.kind === 'vacay' ? [entry.card] : []));
|
|
const view = mergeVacayCards(own);
|
|
const lines = balanceLines(view, []);
|
|
const first = own[0] ?? null;
|
|
return (
|
|
<CardFrame label={t('assistant.card.vacay.label')}>
|
|
<CardHeader icon={Palmtree} tone="info" title={t('assistant.card.vacay.label')} />
|
|
{view.months.length > 0 && <VacayMonthStrip view={view} placed={[]} labelOf={() => ''} />}
|
|
<Legend view={view} />
|
|
{view.otherDays > 0 && <p className={OTHER}>{t('assistant.card.vacay.otherDays', { count: view.otherDays })}</p>}
|
|
<VacayBalance lines={lines} view={view} mode="read" />
|
|
<CardFoot>
|
|
<CardOpenButton target={{ kind: 'page', path: vacayPathOf(first) }} labelKey="assistant.card.vacay.open" />
|
|
</CardFoot>
|
|
</CardFrame>
|
|
);
|
|
}
|