/** * Timeline duty card: front = duty info + flip button; back = name + contacts + back button. * Flip card only when duty has phone or username. Ported from webapp/js/dutyList.js dutyTimelineCardHtml. */ "use client"; import { useState, useMemo, useRef } from "react"; import { useTranslation } from "@/i18n/use-translation"; import { localDateString, dateKeyToDDMM, formatHHMM, } from "@/lib/date-utils"; import { cn } from "@/lib/utils"; import { ContactLinks } from "@/components/contact/ContactLinks"; import { Button } from "@/components/ui/button"; import type { DutyWithUser } from "@/types"; import { Phone, ArrowLeft } from "lucide-react"; export interface DutyTimelineCardProps { duty: DutyWithUser; isCurrent: boolean; /** When provided, card is controlled: only one card can be flipped at a time (managed by parent). */ isFlipped?: boolean; /** Called when user flips to contacts (true) or back (false). Used with isFlipped for controlled mode. */ onFlipChange?: (flipped: boolean) => void; } function buildTimeStr(duty: DutyWithUser): string { const startLocal = localDateString(new Date(duty.start_at)); const endLocal = localDateString(new Date(duty.end_at)); const startDDMM = dateKeyToDDMM(startLocal); const endDDMM = dateKeyToDDMM(endLocal); const startTime = formatHHMM(duty.start_at); const endTime = formatHHMM(duty.end_at); if (startLocal === endLocal) { return `${startDDMM}, ${startTime} – ${endTime}`; } return `${startDDMM} ${startTime} – ${endDDMM} ${endTime}`; } const cardBase = "grid grid-cols-1 gap-y-0.5 items-baseline rounded-lg bg-surface px-2.5 py-2 border-l-[3px] shadow-sm min-h-0 pr-12 relative"; const borderByType = { duty: "border-l-duty", unavailable: "border-l-unavailable", vacation: "border-l-vacation", } as const; /** * Renders a single duty card. If duty has phone or username, wraps in a flip card * (front: type, name, time + "Contacts" button; back: name, ContactLinks, "Back" button). */ export function DutyTimelineCard({ duty, isCurrent, isFlipped, onFlipChange, }: DutyTimelineCardProps) { const { t } = useTranslation(); const [localFlipped, setLocalFlipped] = useState(false); const frontBtnRef = useRef(null); const backBtnRef = useRef(null); const hasContacts = Boolean( (duty.phone && String(duty.phone).trim()) || (duty.username && String(duty.username).trim()) ); const typeLabel = isCurrent ? t("duty.now_on_duty") : t(`event_type.${duty.event_type || "duty"}`); const timeStr = useMemo(() => buildTimeStr(duty), [duty]); const eventType = (duty.event_type || "duty") as keyof typeof borderByType; const borderClass = isCurrent ? "border-l-today" : borderByType[eventType] ?? "border-l-duty"; const isControlled = onFlipChange != null; const flipped = isControlled ? (isFlipped ?? false) : localFlipped; const handleFlipToBack = () => { if (isControlled) { onFlipChange?.(true); } else { setLocalFlipped(true); } setTimeout(() => backBtnRef.current?.focus(), 310); }; const handleFlipToFront = () => { if (isControlled) { onFlipChange?.(false); } else { setLocalFlipped(false); } setTimeout(() => frontBtnRef.current?.focus(), 310); }; if (!hasContacts) { return (
{typeLabel} {duty.full_name} {timeStr}
); } return (
{/* Front */}
{typeLabel} {duty.full_name} {timeStr}
{/* Back */}
{duty.full_name}
); }