Connect all CRM pages to the BFF and CRM service, add module docs, and mark CRM available in the SuperApp catalog. Co-authored-by: Cursor <cursoragent@cursor.com>
122 lines
4.2 KiB
TypeScript
122 lines
4.2 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { useForm } from "react-hook-form";
|
||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||
import { toast } from "sonner";
|
||
import {
|
||
Button,
|
||
Dialog,
|
||
FormField,
|
||
Input,
|
||
Select,
|
||
Checkbox,
|
||
PageHeader,
|
||
EmptyState,
|
||
} from "@/components/ds";
|
||
import { crmApi } from "@/modules/crm/services/crm-api";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import { CrmPageLoader, CrmPageError } from "@/modules/crm/pages/shared";
|
||
import { CrmBreadcrumbs } from "@/modules/crm/components/CrmBreadcrumbs";
|
||
|
||
export default function CustomFieldsPage() {
|
||
const { tenantId } = useTenantId();
|
||
const qc = useQueryClient();
|
||
const [entityType, setEntityType] = useState("lead");
|
||
const [open, setOpen] = useState(false);
|
||
|
||
const q = useQuery({
|
||
queryKey: ["crm", tenantId, "custom-fields", entityType],
|
||
queryFn: () => crmApi.customFields.list(tenantId!, entityType),
|
||
enabled: !!tenantId,
|
||
});
|
||
|
||
const form = useForm({
|
||
defaultValues: {
|
||
code: "",
|
||
label: "",
|
||
field_type: "text",
|
||
is_required: false,
|
||
},
|
||
});
|
||
|
||
const createM = useMutation({
|
||
mutationFn: (d: Record<string, unknown>) =>
|
||
crmApi.customFields.create(tenantId!, { ...d, entity_type: entityType }),
|
||
onSuccess: async () => {
|
||
toast.success("فیلد ایجاد شد");
|
||
setOpen(false);
|
||
form.reset();
|
||
await qc.invalidateQueries({ queryKey: ["crm", tenantId, "custom-fields"] });
|
||
},
|
||
onError: (e: Error) => toast.error(e.message),
|
||
});
|
||
|
||
if (!tenantId || q.isLoading) return <CrmPageLoader />;
|
||
if (q.error) return <CrmPageError error={q.error} onRetry={() => q.refetch()} />;
|
||
|
||
return (
|
||
<div>
|
||
<CrmBreadcrumbs items={[{ label: "فیلدهای سفارشی" }]} />
|
||
<PageHeader
|
||
title="فیلدهای سفارشی"
|
||
description="تعریف فیلد per entity_type و upsert مقادیر از API values."
|
||
actions={<Button onClick={() => setOpen(true)}>فیلد جدید</Button>}
|
||
/>
|
||
<Select
|
||
value={entityType}
|
||
onChange={(e) => setEntityType(e.target.value)}
|
||
className="mb-4 max-w-xs"
|
||
>
|
||
<option value="lead">سرنخ</option>
|
||
<option value="contact">مخاطب</option>
|
||
<option value="organization">سازمان</option>
|
||
</Select>
|
||
<div className="space-y-2">
|
||
{(q.data ?? []).map((f) => (
|
||
<div
|
||
key={f.id}
|
||
className="flex items-center justify-between rounded-xl border border-[var(--border)] bg-[var(--surface)] px-4 py-3"
|
||
>
|
||
<div>
|
||
<p className="font-medium">{f.label}</p>
|
||
<p className="text-xs text-[var(--muted)]">
|
||
{f.code} — {f.field_type}
|
||
</p>
|
||
</div>
|
||
{f.is_required ? (
|
||
<span className="text-xs text-[var(--crm-accent)]">الزامی</span>
|
||
) : null}
|
||
</div>
|
||
))}
|
||
{(q.data ?? []).length === 0 ? <EmptyState title="فیلدی تعریف نشده" /> : null}
|
||
</div>
|
||
<Dialog open={open} onClose={() => setOpen(false)} title="فیلد سفارشی">
|
||
<form className="space-y-3" onSubmit={form.handleSubmit((d) => createM.mutate(d))}>
|
||
<FormField label="کد">
|
||
<Input {...form.register("code")} />
|
||
</FormField>
|
||
<FormField label="برچسب">
|
||
<Input {...form.register("label")} />
|
||
</FormField>
|
||
<FormField label="نوع">
|
||
<Select {...form.register("field_type")}>
|
||
<option value="text">متن</option>
|
||
<option value="number">عدد</option>
|
||
<option value="date">تاریخ</option>
|
||
<option value="boolean">بله/خیر</option>
|
||
<option value="select">انتخابی</option>
|
||
</Select>
|
||
</FormField>
|
||
<FormField label="الزامی">
|
||
<Checkbox {...form.register("is_required")} />
|
||
</FormField>
|
||
<Button type="submit" disabled={createM.isPending}>
|
||
ذخیره
|
||
</Button>
|
||
</form>
|
||
</Dialog>
|
||
</div>
|
||
);
|
||
}
|