TorbatYar/frontend/components/ds/NavChrome.tsx
Mortezakoohjani 12c8615615 Ship enterprise Accounting FE/API with CRUD parity and production wiring.
Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 15:26:43 +03:30

144 lines
3.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { cn } from "@/lib/utils";
export function Tabs({
items,
value,
onChange,
className,
}: {
items: { id: string; label: string }[];
value: string;
onChange: (id: string) => void;
className?: string;
}) {
return (
<div
role="tablist"
className={cn(
"flex gap-1 overflow-x-auto rounded-xl border border-[var(--border)] bg-[var(--surface-muted)] p-1",
className
)}
>
{items.map((item) => {
const active = item.id === value;
return (
<button
key={item.id}
type="button"
role="tab"
aria-selected={active}
onClick={() => onChange(item.id)}
className={cn(
"whitespace-nowrap rounded-lg px-3 py-1.5 text-sm font-medium transition-colors",
active
? "bg-[var(--surface)] text-secondary shadow-sm"
: "text-[var(--muted)] hover:text-secondary"
)}
>
{item.label}
</button>
);
})}
</div>
);
}
export function TableToolbar({
search,
onSearchChange,
searchPlaceholder = "جستجو…",
actions,
className,
}: {
search?: string;
onSearchChange?: (v: string) => void;
searchPlaceholder?: string;
actions?: React.ReactNode;
className?: string;
}) {
return (
<div className={cn("mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", className)}>
{onSearchChange ? (
<input
value={search ?? ""}
onChange={(e) => onSearchChange(e.target.value)}
placeholder={searchPlaceholder}
className="h-10 w-full max-w-sm rounded-xl border border-[var(--border)] bg-[var(--surface)] px-3 text-sm"
/>
) : (
<div />
)}
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
</div>
);
}
export function Breadcrumbs({
items,
}: {
items: { label: string; href?: string }[];
}) {
return (
<nav aria-label="مسیر" className="mb-3 flex flex-wrap items-center gap-1 text-xs text-[var(--muted)]">
{items.map((item, i) => (
<span key={`${item.label}-${i}`} className="flex items-center gap-1">
{i > 0 ? <span>/</span> : null}
{item.href ? (
<a href={item.href} className="hover:text-secondary">
{item.label}
</a>
) : (
<span className="text-secondary">{item.label}</span>
)}
</span>
))}
</nav>
);
}
export function Skeleton({ className }: { className?: string }) {
return <div className={cn("animate-pulse rounded-xl bg-[var(--surface-muted)]", className)} />;
}
export function Pagination({
page,
pageSize,
total,
onPageChange,
}: {
page: number;
pageSize: number;
total: number;
onPageChange: (page: number) => void;
}) {
const pages = Math.max(1, Math.ceil(total / pageSize));
if (pages <= 1) return null;
return (
<div className="mt-4 flex items-center justify-between text-sm">
<span className="text-[var(--muted)]">
صفحه {page} از {pages}
</span>
<div className="flex gap-2">
<button
type="button"
disabled={page <= 1}
className="rounded-lg border border-[var(--border)] px-3 py-1.5 disabled:opacity-40"
onClick={() => onPageChange(page - 1)}
>
قبلی
</button>
<button
type="button"
disabled={page >= pages}
className="rounded-lg border border-[var(--border)] px-3 py-1.5 disabled:opacity-40"
onClick={() => onPageChange(page + 1)}
>
بعدی
</button>
</div>
</div>
);
}