{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chain-of-custody",
  "title": "Chain Of Custody",
  "description": "A waste-transfer audit trail built on the Timeline primitive — typed custody events (produced through disputed) with per-type tones, verification states, transfer-note document chips, quantity deltas and licensed party cards, in compact and detailed variants.",
  "dependencies": [
    "@untitledui/icons@^0.0.22"
  ],
  "registryDependencies": [
    "@nx-ui/timeline",
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/blocks/domain/chain-of-custody/chain-of-custody.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  AlertTriangle,\n  Building07,\n  CheckVerified01,\n  ClipboardCheck,\n  Clock,\n  Cube01,\n  File02,\n  Inbox01,\n  PackageCheck,\n  RefreshCw01,\n  SwitchHorizontal01,\n  Tag01,\n  Truck01,\n  User01,\n} from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Timeline,\n  TimelineContent,\n  TimelineItem,\n  TimelineMeta,\n  TimelineNode,\n  TimelineTitle,\n} from \"@/components/ui/timeline\"\n\nimport type {\n  ChainOfCustodyProps,\n  CustodyDocument,\n  CustodyEvent,\n  CustodyEventType,\n  CustodyParty,\n  CustodyPartyRole,\n  CustodyVerification,\n} from \"./types\"\n\nconst EVENT_META: Record<\n  CustodyEventType,\n  {\n    Icon: React.ComponentType<{ className?: string }>\n    label: string\n    nodeClassName: string\n  }\n> = {\n  produced: {\n    Icon: Cube01,\n    label: \"Waste produced\",\n    nodeClassName: \"bg-bg-tertiary text-text-secondary\",\n  },\n  classified: {\n    Icon: Tag01,\n    label: \"Waste classified\",\n    nodeClassName: \"bg-utility-blue-50 text-utility-blue-600\",\n  },\n  consigned: {\n    Icon: ClipboardCheck,\n    label: \"Consignment raised\",\n    nodeClassName: \"bg-utility-purple-50 text-utility-purple-600\",\n  },\n  collected: {\n    Icon: Truck01,\n    label: \"Collected by carrier\",\n    nodeClassName: \"bg-utility-orange-50 text-utility-orange-600\",\n  },\n  transferred: {\n    Icon: SwitchHorizontal01,\n    label: \"Custody transferred\",\n    nodeClassName: \"bg-utility-blue-50 text-utility-blue-600\",\n  },\n  received: {\n    Icon: Inbox01,\n    label: \"Received at site\",\n    nodeClassName: \"bg-utility-green-50 text-utility-green-600\",\n  },\n  processed: {\n    Icon: RefreshCw01,\n    label: \"Processed for reuse\",\n    nodeClassName: \"bg-utility-green-50 text-utility-green-600\",\n  },\n  disputed: {\n    Icon: AlertTriangle,\n    label: \"Transfer disputed\",\n    nodeClassName: \"bg-utility-red-50 text-utility-red-600\",\n  },\n}\n\nconst PARTY_ROLE_META: Record<\n  CustodyPartyRole,\n  { Icon: React.ComponentType<{ className?: string }>; label: string }\n> = {\n  producer: { Icon: Building07, label: \"Producer\" },\n  carrier: { Icon: Truck01, label: \"Carrier\" },\n  receiver: { Icon: PackageCheck, label: \"Receiver\" },\n  broker: { Icon: User01, label: \"Broker\" },\n}\n\nconst VERIFICATION_META: Record<\n  CustodyVerification,\n  { Icon: React.ComponentType<{ className?: string }>; label: string; className: string }\n> = {\n  verified: {\n    Icon: CheckVerified01,\n    label: \"Verified\",\n    className: \"text-utility-green-600\",\n  },\n  pending: {\n    Icon: Clock,\n    label: \"Awaiting verification\",\n    className: \"text-utility-yellow-600\",\n  },\n  mismatch: {\n    Icon: AlertTriangle,\n    label: \"Quantity mismatch\",\n    className: \"text-utility-red-600\",\n  },\n}\n\nfunction VerificationChip({ verification }: { verification: CustodyVerification }) {\n  const meta = VERIFICATION_META[verification]\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-center gap-1 text-xs font-medium\",\n        meta.className\n      )}\n    >\n      <meta.Icon className=\"size-3.5\" aria-hidden=\"true\" />\n      {meta.label}\n    </span>\n  )\n}\n\nfunction DocumentChip({\n  document,\n  onClick,\n}: {\n  document: CustodyDocument\n  onClick?: () => void\n}) {\n  const content = (\n    <>\n      <File02 className=\"size-3.5 shrink-0 text-text-quaternary\" aria-hidden=\"true\" />\n      <span className=\"text-text-tertiary\">{document.label}</span>\n      <span className=\"font-medium text-text-secondary\">{document.reference}</span>\n    </>\n  )\n  const chipClassName =\n    \"inline-flex items-center gap-1.5 rounded-full border border-border bg-bg-primary px-2.5 py-0.5 text-xs\"\n\n  if (document.href) {\n    return (\n      <a\n        href={document.href}\n        className={cn(chipClassName, \"transition hover:border-border-primary hover:bg-bg-secondary\")}\n      >\n        {content}\n      </a>\n    )\n  }\n  if (onClick) {\n    return (\n      <button\n        type=\"button\"\n        onClick={onClick}\n        className={cn(chipClassName, \"transition hover:border-border-primary hover:bg-bg-secondary\")}\n      >\n        {content}\n      </button>\n    )\n  }\n  return <span className={chipClassName}>{content}</span>\n}\n\nfunction QuantityDelta({ value, unit }: { value: number; unit: string }) {\n  const sign = value > 0 ? \"+\" : value < 0 ? \"−\" : \"±\"\n  const magnitude = Math.abs(value)\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium tabular-nums\",\n        value > 0 && \"bg-utility-green-50 text-utility-green-700\",\n        value < 0 && \"bg-utility-orange-50 text-utility-orange-700\",\n        value === 0 && \"bg-bg-secondary text-text-tertiary\"\n      )}\n    >\n      {sign}\n      {magnitude} {unit}\n    </span>\n  )\n}\n\nfunction PartyCard({ party }: { party: CustodyParty }) {\n  const meta = PARTY_ROLE_META[party.role]\n  return (\n    <div className=\"flex min-w-0 items-start gap-2.5 rounded-lg border border-border bg-bg-primary px-3 py-2\">\n      <meta.Icon className=\"mt-0.5 size-4 shrink-0 text-text-quaternary\" aria-hidden=\"true\" />\n      <div className=\"min-w-0\">\n        <div className=\"text-xs font-medium tracking-wide text-text-quaternary uppercase\">\n          {meta.label}\n        </div>\n        <div className=\"truncate text-sm font-medium text-text-secondary\">{party.name}</div>\n        {party.licenceNumber ? (\n          <div className=\"truncate text-xs text-text-tertiary tabular-nums\">\n            {party.licenceNumber}\n          </div>\n        ) : null}\n        {party.location ? (\n          <div className=\"truncate text-xs text-text-tertiary\">{party.location}</div>\n        ) : null}\n      </div>\n    </div>\n  )\n}\n\n/**\n * ChainOfCustody — a waste-transfer audit trail built on the Timeline primitive.\n *\n * Renders typed custody events (produced, classified, consigned, collected,\n * transferred, received, processed, disputed) with per-type icons and tones,\n * verification states, document reference chips, quantity deltas and party\n * cards. The \"compact\" variant is a slim activity feed; \"detailed\" surfaces\n * the full evidence for each step.\n */\nexport function ChainOfCustody({\n  events,\n  variant = \"detailed\",\n  title,\n  onDocumentClick,\n  className,\n}: ChainOfCustodyProps) {\n  const detailed = variant === \"detailed\"\n\n  return (\n    <div className={cn(\"flex flex-col gap-4\", className)}>\n      {title ? (\n        <h3 className=\"text-sm font-semibold text-foreground\">{title}</h3>\n      ) : null}\n      <Timeline>\n        {events.map((event) => {\n          const meta = EVENT_META[event.type]\n          const { Icon } = meta\n          return (\n            <TimelineItem key={event.key} className={detailed ? \"pb-7\" : \"pb-5\"}>\n              <TimelineNode className={meta.nodeClassName}>\n                <Icon aria-hidden=\"true\" />\n              </TimelineNode>\n              <TimelineContent className=\"gap-1.5\">\n                <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n                  <TimelineTitle className=\"text-foreground\">\n                    {event.title ?? meta.label}\n                  </TimelineTitle>\n                  {event.verification ? (\n                    <VerificationChip verification={event.verification} />\n                  ) : null}\n                  {event.quantityDelta ? (\n                    <QuantityDelta\n                      value={event.quantityDelta.value}\n                      unit={event.quantityDelta.unit}\n                    />\n                  ) : null}\n                </div>\n                <TimelineMeta>{event.timestamp}</TimelineMeta>\n                {detailed && event.description ? (\n                  <p className=\"text-sm text-text-tertiary\">{event.description}</p>\n                ) : null}\n                {detailed && event.documents?.length ? (\n                  <div className=\"mt-1 flex flex-wrap gap-1.5\">\n                    {event.documents.map((doc) => (\n                      <DocumentChip\n                        key={`${doc.label}-${doc.reference}`}\n                        document={doc}\n                        onClick={\n                          onDocumentClick\n                            ? () => onDocumentClick(doc, event)\n                            : undefined\n                        }\n                      />\n                    ))}\n                  </div>\n                ) : null}\n                {detailed && event.parties?.length ? (\n                  <div className=\"mt-1 grid gap-2 sm:grid-cols-2\">\n                    {event.parties.map((party) => (\n                      <PartyCard key={`${party.role}-${party.name}`} party={party} />\n                    ))}\n                  </div>\n                ) : null}\n              </TimelineContent>\n            </TimelineItem>\n          )\n        })}\n      </Timeline>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/domain/chain-of-custody/chain-of-custody.tsx"
    },
    {
      "path": "components/blocks/domain/chain-of-custody/index.ts",
      "content": "export { ChainOfCustody } from \"./chain-of-custody\"\nexport type {\n  ChainOfCustodyProps,\n  CustodyDocument,\n  CustodyEvent,\n  CustodyEventType,\n  CustodyParty,\n  CustodyPartyRole,\n  CustodyQuantityDelta,\n  CustodyVerification,\n} from \"./types\"\n",
      "type": "registry:component",
      "target": "components/blocks/domain/chain-of-custody/index.ts"
    },
    {
      "path": "components/blocks/domain/chain-of-custody/types.ts",
      "content": "/** Shared types for the ChainOfCustody audit-trail block. */\n\nexport type CustodyEventType =\n  | \"produced\"\n  | \"classified\"\n  | \"consigned\"\n  | \"collected\"\n  | \"transferred\"\n  | \"received\"\n  | \"processed\"\n  | \"disputed\"\n\nexport type CustodyVerification = \"verified\" | \"pending\" | \"mismatch\"\n\nexport type CustodyPartyRole = \"producer\" | \"carrier\" | \"receiver\" | \"broker\"\n\nexport interface CustodyParty {\n  role: CustodyPartyRole\n  name: string\n  /** e.g. a waste carrier registration (CBDU…) or environmental permit number. */\n  licenceNumber?: string\n  location?: string\n}\n\nexport interface CustodyDocument {\n  /** Short document type label, e.g. \"WTN\" or \"Consignment note\". */\n  label: string\n  /** The document reference, e.g. \"WTN-2026-00412\". */\n  reference: string\n  href?: string\n}\n\nexport interface CustodyQuantityDelta {\n  /** Signed change in quantity; negative for material leaving custody. */\n  value: number\n  /** e.g. \"t\", \"kg\", \"m³\". */\n  unit: string\n}\n\nexport interface CustodyEvent {\n  /** Stable key. */\n  key: string\n  type: CustodyEventType\n  /** Override the default title for the event type. */\n  title?: string\n  /** Pre-formatted timestamp string, e.g. \"14 Mar 2026, 09:42\". */\n  timestamp: string\n  description?: string\n  /** Parties involved at this step (producer, carrier, receiver…). */\n  parties?: CustodyParty[]\n  /** Document chips (waste transfer note / consignment note references). */\n  documents?: CustodyDocument[]\n  quantityDelta?: CustodyQuantityDelta\n  verification?: CustodyVerification\n}\n\nexport interface ChainOfCustodyProps {\n  events: CustodyEvent[]\n  /** \"compact\" shows a slim feed; \"detailed\" adds party cards, documents and deltas. */\n  variant?: \"compact\" | \"detailed\"\n  title?: string\n  /** Called when a document chip is clicked (alternative to `href`). */\n  onDocumentClick?: (document: CustodyDocument, event: CustodyEvent) => void\n  className?: string\n}\n",
      "type": "registry:component",
      "target": "components/blocks/domain/chain-of-custody/types.ts"
    }
  ],
  "type": "registry:block"
}