108 lines
5.2 KiB
TypeScript
108 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import Link from "next/link";
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { toast } from "sonner";
|
|
import { Card, CardContent, PageHeader } from "@/components/ds";
|
|
import { useCurrentHub } from "@/modules/workspace/hooks/useCurrentHub";
|
|
import { workspaceApi } from "@/modules/workspace/services/workspace-api";
|
|
import { CHANNEL, CONTENT_BOARD_COLUMNS, CONTENT_TYPE, displayPerson, formatDay, labelOf } from "@/modules/workspace/labels";
|
|
import { StatusChip } from "@/modules/workspace/components/StatusChip";
|
|
import { WorkspaceEmptyState, WorkspacePageError, WorkspacePageLoader } from "@/modules/workspace/pages/shared";
|
|
import type { WorkspaceRecord } from "@/modules/workspace/types";
|
|
|
|
function columnStatus(columnId: string) {
|
|
return CONTENT_BOARD_COLUMNS.find((column) => column.id === columnId)?.statuses[0] || columnId;
|
|
}
|
|
|
|
export const WorkspaceContentBoard = function WorkspaceContentBoardPage() {
|
|
const queryClient = useQueryClient();
|
|
const { tenantId, hubId, loading } = useCurrentHub();
|
|
const items = useQuery({
|
|
queryKey: ["workspace", tenantId, "content", hubId],
|
|
queryFn: () => workspaceApi.content.list(tenantId!, { work_hub_id: hubId || undefined }),
|
|
enabled: !!tenantId && !!hubId,
|
|
});
|
|
const move = useMutation({
|
|
mutationFn: ({ id, status }: { id: string; status: string }) =>
|
|
workspaceApi.content.update(tenantId!, id, { status }),
|
|
onSuccess: () => {
|
|
toast.success("وضعیت محتوا ذخیره شد");
|
|
void queryClient.invalidateQueries({ queryKey: ["workspace", tenantId, "content"] });
|
|
},
|
|
onError: (err) => toast.error(err instanceof Error ? err.message : "این انتقال مجاز نیست"),
|
|
});
|
|
|
|
const grouped = useMemo(() => {
|
|
const map: Record<string, WorkspaceRecord[]> = {};
|
|
for (const column of CONTENT_BOARD_COLUMNS) map[column.id] = [];
|
|
for (const item of items.data ?? []) {
|
|
const column = CONTENT_BOARD_COLUMNS.find((entry) =>
|
|
(entry.statuses as readonly string[]).includes(String(item.status || "draft"))
|
|
);
|
|
const key = column?.id || "draft";
|
|
map[key] = [...(map[key] || []), item];
|
|
}
|
|
return map;
|
|
}, [items.data]);
|
|
|
|
if (!tenantId || loading || items.isLoading) return <WorkspacePageLoader />;
|
|
if (!hubId) return <WorkspaceEmptyState title="مرکز کار لازم است" description="ابتدا مرکز کار را انتخاب کنید." />;
|
|
if (items.error) return <WorkspacePageError error={items.error} onRetry={() => items.refetch()} />;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<PageHeader title="برد محتوا" description="گلوگاه تولید را ببینید. کشیدن کارت وضعیت را در سرور عوض میکند." />
|
|
<div className="grid gap-3 overflow-x-auto pb-4 lg:grid-cols-7">
|
|
{CONTENT_BOARD_COLUMNS.map((column) => (
|
|
<div
|
|
key={column.id}
|
|
className="min-w-[200px] rounded-2xl bg-[var(--surface-muted)] p-3"
|
|
onDragOver={(event) => event.preventDefault()}
|
|
onDrop={(event) => {
|
|
const id = event.dataTransfer.getData("text/content-id");
|
|
if (id) move.mutate({ id, status: columnStatus(column.id) });
|
|
}}
|
|
>
|
|
<h2 className="mb-3 text-sm font-semibold text-secondary">
|
|
{column.label} ({(grouped[column.id] ?? []).length})
|
|
</h2>
|
|
<div className="space-y-2">
|
|
{(grouped[column.id] ?? []).map((item) => (
|
|
<Card
|
|
key={String(item.id)}
|
|
draggable
|
|
onDragStart={(event) => event.dataTransfer.setData("text/content-id", String(item.id))}
|
|
className="cursor-grab"
|
|
>
|
|
<CardContent className="space-y-2 p-3">
|
|
<Link href={`/workspace/content/${item.id}`} className="text-sm font-medium text-secondary">
|
|
{String(item.title || "محتوا")}
|
|
</Link>
|
|
<p className="text-xs text-[var(--muted)]">
|
|
{labelOf(CONTENT_TYPE, String(item.content_type || ""))} · {labelOf(CHANNEL, String(item.channel || ""))}
|
|
</p>
|
|
<div className="flex flex-wrap items-center gap-1">
|
|
<StatusChip value={String(item.status || "")} />
|
|
{(item.approvals as unknown[] | undefined)?.length || item.status === "internal_review" || item.status === "client_review" ? (
|
|
<StatusChip value="pending" kind="approval" />
|
|
) : null}
|
|
</div>
|
|
<p className="text-xs text-[var(--muted)]">
|
|
{displayPerson(String(item.owner_user_id || ""))}
|
|
{item.scheduled_at ? ` · ${formatDay(String(item.scheduled_at))}` : ""}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default WorkspaceContentBoard;
|