{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "accounting-core-gl",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "All entries, with the gapless-numbering check.",
  "files": [
    {
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useScrollEdges } from \"@/hooks/ledger/use-scroll-edges\";\nimport type { EntrySource, JournalEntryRecord } from \"@/lib/ledger/accounting.types\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport { formatDateShort } from \"@/lib/ledger/fiscal-period\";\nimport type { MoneyValue } from \"@/lib/ledger/money.types\";\nimport { ExclamationTriangleIcon } from \"@/components/ledger/icons\";\nimport { Money } from \"@/components/ledger/money\";\nimport { Reference } from \"@/components/ledger/reference\";\n\nexport interface JournalEntryTableProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  entries: (JournalEntryRecord & { total: MoneyValue })[];\n  /** Caps the scroller so the header and the entry column can freeze. */\n  maxHeight?: number;\n  /** Most tax regimes require sequence; a gap is an audit finding. */\n  numberingGaps?: string[];\n  onOpen?: (entry: JournalEntryRecord) => void;\n}\n\nconst SOURCES: EntrySource[] = [\n  \"manual\",\n  \"adjusting\",\n  \"recurring\",\n  \"subledger\",\n  \"schedule\",\n  \"elimination\",\n  \"translation\",\n  \"import\",\n];\n\nexport function JournalEntryTable({\n  className,\n  entries,\n  numberingGaps = [],\n  onOpen,\n  maxHeight = 520,\n  ...props\n}: JournalEntryTableProps) {\n  const scroller = useScrollEdges<HTMLDivElement>();\n  const [source, setSource] = React.useState<EntrySource | \"all\">(\"all\");\n  const filtered = source === \"all\" ? entries : entries.filter((e) => e.source === source);\n\n  return (\n    <div className={cn(\"space-y-3\", className)} {...props}>\n      {numberingGaps.length > 0 ? (\n        <p\n          className=\"flex items-center gap-2 rounded-sm bg-negative-subtle px-3 py-2 text-body text-negative\"\n          role=\"alert\"\n        >\n          <ExclamationTriangleIcon aria-hidden=\"true\" className=\"size-4 shrink-0\" />\n          Entry numbering has {numberingGaps.length} gap\n          {numberingGaps.length === 1 ? \"\" : \"s\"} ({numberingGaps.join(\", \")}). Sequence is required, and a\n          gap is an audit finding.\n        </p>\n      ) : null}\n\n      <label className=\"inline-flex items-center gap-2\">\n        <span className=\"text-label font-medium text-text-secondary\">Source</span>\n        <select\n          className=\"h-8 rounded-sm border border-border bg-surface px-2 text-label outline-none focus-visible:border-accent focus-visible:ring-2 focus-visible:ring-accent-subtle\"\n          onChange={(e) => setSource(e.target.value as EntrySource | \"all\")}\n          value={source}\n        >\n          <option value=\"all\">All</option>\n          {SOURCES.map((s) => (\n            <option className=\"capitalize\" key={s} value={s}>\n              {s}\n            </option>\n          ))}\n        </select>\n      </label>\n\n      <div\n        className=\"ledger-frozen scrollbar-thin rounded-xl border border-border bg-surface\"\n        ref={scroller}\n        style={{ maxHeight }}\n      >\n        <table className=\"w-full min-w-[720px]\">\n          <caption className=\"sr-only\">Journal entries</caption>\n          <thead>\n            <tr className=\"border-b border-border-subtle\">\n              {[\"Entry\", \"Date\", \"Description\", \"Source\", \"Status\", \"Amount\"].map((h, i) => (\n                <th\n                  className={cn(\n                    \"px-3 py-3 text-label font-medium text-text-secondary\",\n                    i === 5 ? \"text-right\" : \"text-left\",\n                    i === 0 && \"pl-6\",\n                    i === 5 && \"pr-6\",\n                  )}\n                  data-frozen={i === 0 ? \"left\" : undefined}\n                  key={h}\n                  scope=\"col\"\n                >\n                  {h}\n                </th>\n              ))}\n            </tr>\n          </thead>\n          <tbody>\n            {filtered.map((e) => (\n              <tr\n                className=\"border-b border-border-subtle transition-colors duration-1 ease-standard hover:bg-surface-hover\"\n                key={e.id}\n              >\n                <td className=\"py-3 pl-6 pr-3\" data-frozen=\"left\">\n                  <button className=\"text-left\" onClick={() => onOpen?.(e)} type=\"button\">\n                    <Reference copyable={false} truncate={false} value={e.entryNumber} />\n                  </button>\n                </td>\n                <td className=\"numeric px-3 py-3 text-figure-sm text-text-secondary\">\n                  {formatDateShort(e.date)}\n                </td>\n                <th\n                  className=\"max-w-0 truncate px-3 py-3 text-left text-body font-normal\"\n                  data-frozen=\"none\"\n                  scope=\"row\"\n                >\n                  {e.description}\n                </th>\n                <td className=\"px-3 py-3 text-caption capitalize text-text-secondary\">{e.source}</td>\n                <td className=\"px-3 py-3 text-caption capitalize text-text-secondary\">{e.status}</td>\n                <td className=\"py-3 pl-3 pr-6 text-right\">\n                  <Money accounting value={e.total} />\n                </td>\n              </tr>\n            ))}\n          </tbody>\n        </table>\n      </div>\n    </div>\n  );\n}\n",
      "path": "components/ledger/journal-entry-table.tsx",
      "target": "components/ledger/journal-entry-table.tsx",
      "type": "registry:component"
    }
  ],
  "name": "journal-entry-table",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/accounting.types",
    "@lopifi/cn",
    "@lopifi/fiscal-period",
    "@lopifi/icons",
    "@lopifi/money",
    "@lopifi/money.types",
    "@lopifi/reference",
    "@lopifi/use-scroll-edges"
  ],
  "title": "JournalEntryTable",
  "type": "registry:component"
}
