import { Switch } from '@astryxdesign/core/Switch'; import { DEFAULT_SLOTS, slotProblems, type DeliverySlot, type SlotKey } from '@/api/deliverySlots'; import { Note } from './drawerKit'; /** * The three delivery windows a branch offers, as an editable block. * * Used in two places that look the same to a shopkeeper and are quite different * underneath: onboarding, where the windows are part of a form that creates a * branch, and the shop profile, where they are saved on their own. This * component owns neither the saving nor the fetching — it is handed a value and * reports changes, so both callers can decide what "save" means for them. * * ── Three fixed rows, not a list ──────────────────────────────────────────── * * There is no add or remove. A shop has a morning, an afternoon and an evening, * and the app has exactly three places to show them. Building this as a generic * schedule editor would offer a shopkeeper a fourth window that nothing can * render, and the cost of that discovery lands on a customer who picked it. * * ── Why each row has an on/off ────────────────────────────────────────────── * * A shop that does not do evenings needs a way to say so that is not deleting * the row. Orders already placed against a window still have to resolve to * something with a name, so a window is switched off and kept, never removed. */ export function DeliverySlotsEditor({ slots, onChange, isDisabled, /** * Shown above the rows, and only when a caller asks for it. * * No default: onboarding explains what a window is because it is new to the * person reading, while the shop profile does not, because they are editing * something they already set. A paragraph that cannot be turned off is one * every screen has to live with. */ intro, }: { slots: DeliverySlot[]; onChange: (slots: DeliverySlot[]) => void; isDisabled?: boolean; intro?: string; }) { const problems = slotProblems(slots); function update(key: SlotKey, patch: Partial) { onChange(slots.map((slot) => (slot.slotkey === key ? { ...slot, ...patch } : slot))); } // Ordered by the constant, not by whatever order the server returned, so the // rows do not reshuffle between a fresh branch and a saved one. const ordered = DEFAULT_SLOTS.map( (fallback) => slots.find((slot) => slot.slotkey === fallback.slotkey) ?? fallback, ); return (
{intro ? {intro} : null}
{ordered.map((slot) => ( update(slot.slotkey, patch)} /> ))}
{/* Mirrors the server's own checks so a mistake is caught while the shopkeeper's hands are still on it, rather than as a 409 after save. */} {problems.length > 0 ? ( ) : null}
); } function SlotRow({ slot, onChange, isDisabled, }: { slot: DeliverySlot; onChange: (patch: Partial) => void; isDisabled: boolean; }) { const isOff = slot.status !== 'active'; return (
onChange({ name: event.target.value })} placeholder={slot.slotkey} disabled={isDisabled} aria-label={`Name for the ${slot.slotkey} window`} style={{ ...inputStyle, flex: 1, minWidth: 0 }} /> onChange({ starttime: event.target.value })} disabled={isDisabled || isOff} aria-label={`Start of the ${slot.slotkey} window`} style={{ ...inputStyle, width: 104 }} /> to onChange({ endtime: event.target.value })} disabled={isDisabled || isOff} aria-label={`End of the ${slot.slotkey} window — also when it stops taking orders`} style={{ ...inputStyle, width: 104 }} /> {/* Label hidden: the row's name field says which window this is, and repeating "Offer the morning window" three times down a block of three is the same sentence three times. */} onChange({ status: on ? 'active' : 'inactive' })} isDisabled={isDisabled} size="sm" />
); } const inputStyle: React.CSSProperties = { padding: '7px 9px', fontSize: 13, border: '1px solid var(--color-line, #d7dce5)', borderRadius: 7, background: 'var(--color-surface, #fff)', color: 'var(--color-ink-1)', };