TorbatYar/frontend/app/accounting/suppliers/page.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

124 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { Plus } from "lucide-react";
import { accountingApi } from "@/lib/accounting-api";
import { useTenantId } from "@/hooks/useTenantId";
import { formatMoney } from "@/lib/utils";
import {
PageHeader,
Button,
Dialog,
Input,
Label,
FieldError,
DataTable,
EmptyState,
LoadingState,
ErrorState,
Badge,
} from "@/components/ds";
const schema = z.object({
code: z.string().min(1, "کد الزامی است"),
name: z.string().min(1, "نام الزامی است"),
});
export default function SuppliersPage() {
const { tenantId } = useTenantId();
const qc = useQueryClient();
const [open, setOpen] = useState(false);
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: { code: "", name: "" },
});
const listQ = useQuery({
queryKey: ["accounting", tenantId, "suppliers"],
queryFn: () => accountingApi.suppliers.list(tenantId!),
enabled: !!tenantId,
});
const createM = useMutation({
mutationFn: (d: z.infer<typeof schema>) => accountingApi.suppliers.create(tenantId!, d),
onSuccess: async () => {
toast.success("تأمین‌کننده ایجاد شد");
setOpen(false);
form.reset();
await qc.invalidateQueries({ queryKey: ["accounting", tenantId, "suppliers"] });
},
onError: (e: Error) => toast.error(e.message),
});
if (!tenantId || listQ.isLoading) return <LoadingState />;
if (listQ.error) return <ErrorState message={listQ.error.message} onRetry={() => listQ.refetch()} />;
return (
<div>
<PageHeader
title="تأمین‌کنندگان"
description="مدیریت تأمین‌کنندگان و مانده بدهی — API واقعی AR/AP."
actions={
<Button onClick={() => setOpen(true)}>
<Plus className="h-4 w-4" />
تأمینکننده جدید
</Button>
}
/>
<DataTable
columns={[
{ key: "code", header: "کد" },
{ key: "name", header: "نام" },
{
key: "outstanding_balance",
header: "مانده بدهی",
render: (r) => formatMoney(String(r.outstanding_balance ?? "0")),
},
{
key: "is_active",
header: "وضعیت",
render: (r) => (
<Badge tone={r.is_active ? "success" : "default"}>
{r.is_active ? "فعال" : "غیرفعال"}
</Badge>
),
},
]}
rows={(listQ.data ?? []) as unknown as Record<string, unknown>[]}
empty={
<EmptyState title="تأمین‌کننده‌ای ثبت نشده" action={<Button onClick={() => setOpen(true)}>ایجاد</Button>} />
}
/>
<Dialog open={open} onClose={() => setOpen(false)} title="تأمین‌کننده جدید">
<form className="space-y-3" onSubmit={form.handleSubmit((d) => createM.mutate(d))}>
<div>
<Label>کد</Label>
<Input {...form.register("code")} />
<FieldError>{form.formState.errors.code?.message}</FieldError>
</div>
<div>
<Label>نام</Label>
<Input {...form.register("name")} />
<FieldError>{form.formState.errors.name?.message}</FieldError>
</div>
<div className="flex justify-end gap-2">
<Button type="button" variant="outline" onClick={() => setOpen(false)}>
انصراف
</Button>
<Button type="submit" disabled={createM.isPending}>
ذخیره
</Button>
</div>
</form>
</Dialog>
</div>
);
}