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>
198 lines
7.6 KiB
TypeScript
198 lines
7.6 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import { ChevronLeft, ExternalLink, Palette, Shield, Users } from "lucide-react";
|
||
import { accountingApi } from "@/lib/accounting-api";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import { useColorMode } from "@/components/providers/ColorModeProvider";
|
||
import {
|
||
PageHeader,
|
||
LoadingState,
|
||
ErrorState,
|
||
Card,
|
||
CardContent,
|
||
CardHeader,
|
||
CardTitle,
|
||
Badge,
|
||
Button,
|
||
} from "@/components/ds";
|
||
|
||
const SETUP_STEPS: { key: keyof Awaited<ReturnType<typeof accountingApi.setup.status>>; label: string }[] = [
|
||
{ key: "has_chart", label: "دفتر حساب (Chart)" },
|
||
{ key: "has_accounts", label: "حسابها" },
|
||
{ key: "has_currency", label: "ارز" },
|
||
{ key: "has_base_currency", label: "ارز پایه" },
|
||
{ key: "has_fiscal_year", label: "سال مالی" },
|
||
{ key: "has_fiscal_period", label: "دوره مالی" },
|
||
{ key: "has_cash_box", label: "صندوق نقد" },
|
||
{ key: "has_customer", label: "مشتری" },
|
||
{ key: "has_voucher", label: "سند حسابداری" },
|
||
];
|
||
|
||
const SUPERAPP_LINKS = [
|
||
{
|
||
href: "/dashboard/settings",
|
||
title: "کاربران و نقشها",
|
||
description: "مدیریت دسترسی اعضای workspace از SuperApp",
|
||
icon: Users,
|
||
},
|
||
{
|
||
href: "/dashboard/settings",
|
||
title: "نقشها و مجوزها",
|
||
description: "تخصیص نقش IAM — از تنظیمات workspace",
|
||
icon: Shield,
|
||
},
|
||
{
|
||
href: "/dashboard/settings",
|
||
title: "برندسازی (White-label)",
|
||
description: "لوگو، رنگ و هویت بصری tenant",
|
||
icon: Palette,
|
||
},
|
||
];
|
||
|
||
export default function AccountingSettingsPage() {
|
||
const { tenantId } = useTenantId();
|
||
const { mode, toggle } = useColorMode();
|
||
|
||
const healthQ = useQuery({
|
||
queryKey: ["accounting", "health"],
|
||
queryFn: () => accountingApi.health(),
|
||
});
|
||
|
||
const setupQ = useQuery({
|
||
queryKey: ["accounting", tenantId, "setup-status"],
|
||
queryFn: () => accountingApi.setup.status(tenantId!),
|
||
enabled: !!tenantId,
|
||
});
|
||
|
||
if (!tenantId || setupQ.isLoading) return <LoadingState />;
|
||
if (setupQ.error) {
|
||
return <ErrorState message={setupQ.error.message} onRetry={() => setupQ.refetch()} />;
|
||
}
|
||
|
||
const setup = setupQ.data!;
|
||
|
||
return (
|
||
<div>
|
||
<PageHeader
|
||
title="تنظیمات حسابداری"
|
||
description="وضعیت راهاندازی، سرویس و پیوندهای مدیریت workspace."
|
||
/>
|
||
|
||
<div className="mb-6 rounded-2xl border border-[var(--border)] bg-[var(--surface)] p-5">
|
||
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||
<div>
|
||
<h2 className="font-semibold text-secondary">پیشرفت راهاندازی</h2>
|
||
<p className="mt-1 text-sm text-[var(--muted)]">
|
||
{setup.completed_steps} از {setup.total_steps} مرحله تکمیل شده
|
||
</p>
|
||
</div>
|
||
<Badge tone={setup.percent >= 100 ? "success" : setup.percent >= 50 ? "primary" : "warning"}>
|
||
{setup.percent}%
|
||
</Badge>
|
||
</div>
|
||
<div className="mb-4 h-2 overflow-hidden rounded-full bg-[var(--surface-muted)]">
|
||
<div
|
||
className="h-full rounded-full bg-primary transition-all"
|
||
style={{ width: `${setup.percent}%` }}
|
||
/>
|
||
</div>
|
||
<ul className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||
{SETUP_STEPS.map(({ key, label }) => (
|
||
<li key={key} className="flex items-center justify-between rounded-xl border border-[var(--border)] px-3 py-2 text-sm">
|
||
<span className="text-secondary">{label}</span>
|
||
<Badge tone={setup[key] ? "success" : "default"}>{setup[key] ? "✓" : "—"}</Badge>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
|
||
<div className="grid gap-4 lg:grid-cols-2">
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>سرویس حسابداری</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3 text-sm">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-[var(--muted)]">وضعیت</span>
|
||
<Badge tone={healthQ.data?.status === "ok" ? "success" : "warning"}>
|
||
{healthQ.data?.status ?? "…"}
|
||
</Badge>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-[var(--muted)]">نسخه</span>
|
||
<span className="font-medium">{healthQ.data?.version ?? "—"}</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-[var(--muted)]">Tenant</span>
|
||
<span className="font-mono text-xs" dir="ltr">
|
||
{tenantId}
|
||
</span>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>ظاهر ماژول</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium">تم رابط</p>
|
||
<p className="text-xs text-[var(--muted)]">روشن / تاریک — RTL حفظ میشود</p>
|
||
</div>
|
||
<Button type="button" variant="outline" onClick={toggle}>
|
||
{mode === "dark" ? "تم روشن" : "تم تاریک"}
|
||
</Button>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
<h2 className="mb-3 mt-8 text-sm font-semibold text-secondary">مدیریت workspace (SuperApp)</h2>
|
||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||
{SUPERAPP_LINKS.map((link) => {
|
||
const Icon = link.icon;
|
||
return (
|
||
<Link
|
||
key={link.title}
|
||
href={link.href}
|
||
className="group flex flex-col rounded-2xl border border-[var(--border)] bg-[var(--surface)] p-4 transition-colors hover:border-primary/40 hover:bg-[var(--surface-muted)]"
|
||
>
|
||
<div className="mb-3 flex items-center justify-between">
|
||
<div className="rounded-xl bg-[var(--surface-muted)] p-2">
|
||
<Icon className="h-4 w-4 text-primary" />
|
||
</div>
|
||
<ExternalLink className="h-3.5 w-3.5 text-[var(--muted)] opacity-0 transition-opacity group-hover:opacity-100" />
|
||
</div>
|
||
<p className="font-medium text-secondary">{link.title}</p>
|
||
<p className="mt-1 text-xs text-[var(--muted)]">{link.description}</p>
|
||
<span className="mt-3 inline-flex items-center gap-1 text-xs text-primary">
|
||
<ChevronLeft className="h-3 w-3" />
|
||
رفتن به تنظیمات
|
||
</span>
|
||
</Link>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className="mt-6 grid gap-2 sm:grid-cols-2">
|
||
{[
|
||
["/accounting/chart-of-accounts", "دفتر حسابها"],
|
||
["/accounting/fiscal", "سال و دوره مالی"],
|
||
["/accounting/currencies", "ارزها"],
|
||
["/accounting/treasury", "خزانه"],
|
||
].map(([href, label]) => (
|
||
<Link
|
||
key={href}
|
||
href={href}
|
||
className="rounded-xl border border-[var(--border)] px-4 py-3 text-sm hover:bg-[var(--surface-muted)]"
|
||
>
|
||
{label}
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|