Migrates Tailwind v3 -> v4 (CSS-first, theme tokens for brand, workflow states and charts) and adds 23 shadcn/ui components on Radix. - lib/workflow.ts is now the single source of truth for state name, colour, icon and available actions; "Financial Review" is the canonical served name, with "Finance Review" kept as an alias so those rows stop falling through to the unstyled fallback and dropping out of every count - record view: TanStack Table with server-side sort/paginate, company column with initials avatar, page totals, density and a URL-synced query state - dashboard: one cached analytics fetch shared by tiles and charts, donut + aging + vendor spend, click-to-filter - detail view: rebuilt against the live detailview payload — restores the PO number and payment terms that the old key heuristics silently dropped, parses match_summary into a 3-way-match checklist, and separates the department's and reviewer's own words from AI-generated content - form modal on Radix Dialog (focus trap, scroll lock, escape handling)
51 lines
1.4 KiB
TypeScript
51 lines
1.4 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
import { resolveStatus } from "@/lib/workflow";
|
|
|
|
interface Props {
|
|
value: string | null | undefined;
|
|
/** `sm` for table cells, `md` for the detail header. */
|
|
size?: "sm" | "md";
|
|
/** Show the state icon alongside the dot. Off in dense tables. */
|
|
withIcon?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* The one status pill. Colour, label and icon all come from lib/workflow, so a
|
|
* state looks identical in a table cell, a detail header and a chart legend.
|
|
*/
|
|
export default function StatusBadge({ value, size = "sm", withIcon = false, className }: Props) {
|
|
const status = resolveStatus(value);
|
|
if (!status) return null;
|
|
|
|
const { label, tone, pulse, icon: Icon } = status;
|
|
|
|
return (
|
|
<span
|
|
className={cn(
|
|
"inline-flex shrink-0 items-center gap-1.5 rounded-full border font-semibold",
|
|
tone.bg,
|
|
tone.border,
|
|
tone.text,
|
|
size === "sm" ? "px-2 py-0.5 text-xs" : "px-2.5 py-1 text-sm",
|
|
className,
|
|
)}
|
|
>
|
|
{withIcon ? (
|
|
<Icon className={size === "sm" ? "size-3" : "size-3.5"} aria-hidden />
|
|
) : (
|
|
<span
|
|
className={cn(
|
|
"size-1.5 shrink-0 rounded-full",
|
|
tone.solid,
|
|
// Only the in-progress state pulses, so motion means "working".
|
|
pulse && "animate-pulse",
|
|
)}
|
|
aria-hidden
|
|
/>
|
|
)}
|
|
{label}
|
|
</span>
|
|
);
|
|
}
|