"use client"; /** * One document in the vault timeline (compact-density Card) plus the two * transient upload-state cards (in-flight with a live progress ring, failed * with the translated §3.2 reason). All three share the card footprint so an * optimistic entry morphs into the stored row without the grid jumping. * * Anatomy per the design standards: kind icon `text-foreground size-5`, * title in foreground (`text-sm font-medium`, truncated), one muted meta * line (date · size · filename), condition-tag pills, an attachment-class * badge for download-only formats, and a selection checkbox that appears on * hover / focus / while selected. The whole card is clickable through an * invisible overlay button; the checkbox floats above it. */ import { Download, X } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; import type { InboundDocumentDto } from "@/lib/validations/inbound-documents"; import { DOCUMENT_KIND_ICONS } from "./document-kind-meta"; import type { UploadQueueItem } from "./use-document-upload"; import { documentDateKey, formatBytes } from "./vault-utils"; export function DocumentCard({ document, selected, onToggleSelected, onOpen, highlighted, onPrefetch, }: { document: InboundDocumentDto; selected: boolean; onToggleSelected: (id: string) => void; onOpen: (id: string) => void; /** Brief ring after a duplicate upload resolved to this existing row. */ highlighted: boolean; /** Hover/focus intent — prefetches the detail metadata (never the blob). */ onPrefetch?: (id: string) => void; }) { const { t, locale } = useTranslations(); const format = useFormatters(); const title = document.title ?? document.filename ?? t("documents.card.untitled"); const Icon = DOCUMENT_KIND_ICONS[document.kind]; const date = format.date(`${documentDateKey(document)}T12:00:00.000Z`); const size = formatBytes(document.byteSize, locale); const showFilename = document.filename !== null && document.filename !== title; return (

{title}

onToggleSelected(document.id)} aria-label={t("documents.card.selectLabel", { title })} className={cn( "relative z-10 shrink-0 transition-opacity", "opacity-0 group-focus-within:opacity-100 group-hover:opacity-100", selected && "opacity-100", )} />

{date} · {size} {showFilename ? ` · ${document.filename}` : ""}

{document.conditionLinks.length > 0 || document.servingClass === "attachment" ? (
{document.conditionLinks.map((link) => ( {link.name} ))} {document.servingClass === "attachment" ? ( {t("documents.card.attachmentBadge")} ) : null}
) : null}
{/* Whole-card click target. Painted last so it sits above the content (the checkbox stays reachable via its own z-10). */} ) : null} {failureCopy ? (

{failureCopy}

) : null}
); }