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>
120 lines
4.2 KiB
TypeScript
120 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,
|
||
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 DocumentsPage() {
|
||
const { tenantId } = useTenantId();
|
||
const qc = useQueryClient();
|
||
const [open, setOpen] = useState(false);
|
||
const [entityType, setEntityType] = useState("lead");
|
||
const [entityId, setEntityId] = useState("");
|
||
|
||
const q = useQuery({
|
||
queryKey: ["crm", tenantId, "attachments", entityType, entityId],
|
||
queryFn: () => crmApi.attachments.list(tenantId!, entityType, entityId),
|
||
enabled: !!tenantId && !!entityId,
|
||
});
|
||
|
||
const form = useForm({
|
||
defaultValues: { file_storage_id: "", title: "", kind: "document" },
|
||
});
|
||
|
||
const createM = useMutation({
|
||
mutationFn: (d: Record<string, string>) =>
|
||
crmApi.attachments.create(tenantId!, {
|
||
entity_type: entityType,
|
||
entity_id: entityId,
|
||
file_storage_id: d.file_storage_id,
|
||
title: d.title,
|
||
kind: d.kind,
|
||
}),
|
||
onSuccess: async () => {
|
||
toast.success("پیوست ثبت شد");
|
||
setOpen(false);
|
||
form.reset();
|
||
await qc.invalidateQueries({ queryKey: ["crm", tenantId, "attachments"] });
|
||
},
|
||
onError: (e: Error) => toast.error(e.message),
|
||
});
|
||
|
||
if (!tenantId) return <CrmPageLoader />;
|
||
|
||
return (
|
||
<div>
|
||
<CrmBreadcrumbs items={[{ label: "اسناد" }]} />
|
||
<PageHeader
|
||
title="اسناد و پیوستها"
|
||
description="ارجاع به File Storage — CRM فقط metadata نگه میدارد."
|
||
actions={
|
||
<Button onClick={() => setOpen(true)} disabled={!entityId}>
|
||
ثبت پیوست
|
||
</Button>
|
||
}
|
||
/>
|
||
<div className="mb-4 flex flex-wrap gap-3">
|
||
<Select value={entityType} onChange={(e) => setEntityType(e.target.value)}>
|
||
<option value="lead">سرنخ</option>
|
||
<option value="contact">مخاطب</option>
|
||
<option value="organization">سازمان</option>
|
||
</Select>
|
||
<Input
|
||
placeholder="UUID موجودیت"
|
||
value={entityId}
|
||
onChange={(e) => setEntityId(e.target.value)}
|
||
className="max-w-md"
|
||
/>
|
||
</div>
|
||
{entityId && q.isLoading ? <CrmPageLoader /> : null}
|
||
{q.error ? <CrmPageError error={q.error} onRetry={() => q.refetch()} /> : null}
|
||
{entityId && q.data ? (
|
||
<div className="space-y-2">
|
||
{q.data.map((a) => (
|
||
<div
|
||
key={a.id}
|
||
className="rounded-xl border border-[var(--border)] bg-[var(--surface)] px-4 py-3"
|
||
>
|
||
<p className="font-medium">{a.title ?? a.kind}</p>
|
||
<p className="text-xs text-[var(--muted)]">{a.file_storage_id}</p>
|
||
</div>
|
||
))}
|
||
{q.data.length === 0 ? <EmptyState title="پیوستی نیست" /> : null}
|
||
</div>
|
||
) : (
|
||
!entityId && <EmptyState title="شناسه موجودیت را وارد کنید" />
|
||
)}
|
||
<Dialog open={open} onClose={() => setOpen(false)} title="ثبت پیوست">
|
||
<form className="space-y-3" onSubmit={form.handleSubmit((d) => createM.mutate(d))}>
|
||
<FormField label="File Storage ID">
|
||
<Input {...form.register("file_storage_id")} />
|
||
</FormField>
|
||
<FormField label="عنوان">
|
||
<Input {...form.register("title")} />
|
||
</FormField>
|
||
<FormField label="نوع">
|
||
<Input {...form.register("kind")} />
|
||
</FormField>
|
||
<Button type="submit" disabled={createM.isPending}>
|
||
ذخیره
|
||
</Button>
|
||
</form>
|
||
</Dialog>
|
||
</div>
|
||
);
|
||
}
|