import type { AssistantChangePreview, AssistantChangeRow, AssistantChangeVerbKey } from '@trek/shared'; import { ArrowRight, CalendarArrowUp, CalendarMinus, CalendarPlus, Check, CircleSlash, ListTodo, LoaderCircle, Luggage, MapPin, MapPinPlus, PencilLine, StickyNote, Ticket, TriangleAlert, Undo2, Wallet, } from 'lucide-react'; import { type ReactNode, useEffect, useMemo, useState } from 'react'; import { useTranslation } from '../../i18n'; import { blockKeyOf } from '../../store/assistantMapModel'; import { chatOf, useAssistantStore } from '../../store/assistantStore'; import { useTripStore } from '../../store/tripStore'; import ToggleSwitch from '../Settings/ToggleSwitch'; import { useToast } from '../shared/Toast'; import { Tooltip } from '../shared/Tooltip'; import AssistantCompareSwitch from './AssistantCompareSwitch'; import AssistantDayBand from './AssistantDayBand'; import AssistantWeekOverview from './AssistantWeekOverview'; import { CHANGE_LABELS, type ChangeItem, type ChangeState, undoLeftMs } from './assistantParts'; import { useAssistantView } from './assistantViewContext'; import { changePoints, type DayChange } from './dayAfterModel'; import { useChangeHover } from './useChangeHover'; import { useWeekDays } from './useWeekDays'; /** * The changes the assistant proposes in one go, as one card in plain words: * what happens to which place, note or item, field by field. Nothing is * written before the user applies it. Each change keeps its own decision, so * when the card holds several, each has a switch and the button applies the * ones left on. Once decided, the card turns into a receipt per change, with * a way to the change on the map and, for a while, undo. * * Every change wears its kind: a tinted tile with the icon of what it touches * and a badge naming the verb, in the tone of what happens (green adds, blue * changes, amber moves, red takes away). Its fields sit as badges holding a * label badge, so a row of them reads at a glance. * * While it waits, the card shows the days it touches as they would read * afterwards, and each row lights its pin on the map (and the pin its row). * Once applied, the head offers the plan before and after the block on the * map, and taking the whole block back at once. */ type Tone = 'success' | 'info' | 'warning' | 'danger'; const TILE: Record = { success: 'bg-success-soft text-success', info: 'bg-info-soft text-info', warning: 'bg-warning-soft text-warning', danger: 'bg-danger-soft text-danger', }; /** What each kind of change looks like. A table, so a new verb fails to compile until it has a look. */ const VERB_LOOK: Record = { addPlace: { Icon: MapPinPlus, tone: 'success' }, addPlaceToDay: { Icon: CalendarPlus, tone: 'success' }, updatePlace: { Icon: PencilLine, tone: 'info' }, assignPlace: { Icon: CalendarPlus, tone: 'success' }, moveAssignment: { Icon: CalendarArrowUp, tone: 'warning' }, removeFromDay: { Icon: CalendarMinus, tone: 'danger' }, addDayNote: { Icon: StickyNote, tone: 'success' }, updateDayNote: { Icon: StickyNote, tone: 'info' }, addPackingItem: { Icon: Luggage, tone: 'success' }, updatePackingItem: { Icon: Luggage, tone: 'info' }, addTodo: { Icon: ListTodo, tone: 'success' }, addBudgetItem: { Icon: Wallet, tone: 'success' }, addReservation: { Icon: Ticket, tone: 'success' }, importBooking: { Icon: Ticket, tone: 'success' }, }; /** How a decided change reads: one quiet mark, its words in the tooltip. */ const RECEIPT: Record, { labelKey: string; Icon: typeof Check; tone: string }> = { applying: { labelKey: 'assistant.change.applying', Icon: LoaderCircle, tone: 'text-content-muted' }, applied: { labelKey: 'assistant.change.applied', Icon: Check, tone: 'text-success' }, failed: { labelKey: 'assistant.change.failed', Icon: TriangleAlert, tone: 'text-danger' }, discarded: { labelKey: 'assistant.change.discarded', Icon: CircleSlash, tone: 'text-content-faint' }, expired: { labelKey: 'assistant.change.expired', Icon: CircleSlash, tone: 'text-content-faint' }, }; const ICON_BUTTON = 'flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md text-content-muted transition-colors hover:bg-surface-hover hover:text-content disabled:opacity-50'; function KindTile({ verbKey, size = 'md', dimmed = false, }: { verbKey: AssistantChangeVerbKey | null; size?: 'sm' | 'md'; dimmed?: boolean; }) { const box = size === 'sm' ? 'h-7 w-7 rounded-md' : 'h-8 w-8 rounded-lg'; if (!verbKey) return