"use client"; import { useState } from "react"; import { useParams, useRouter } from "next/navigation"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { Button, Card, CardContent, ConfirmDialog, Dialog, Input, PageHeader, Select, Textarea } from "@/components/ds"; import { useTenantId } from "@/hooks/useTenantId"; import { workspaceApi } from "@/modules/workspace/services/workspace-api"; import { APPROVAL_TYPE, BRIEF_FIELDS, CONTENT_STATUS, contentActionLabel, displayPerson, formatWhen, labelOf, } from "@/modules/workspace/labels"; import { StatusChip } from "@/modules/workspace/components/StatusChip"; import { CommentThread } from "@/modules/workspace/components/CommentThread"; import { IdentityUserSelect } from "@/modules/workspace/components/IdentityUserSelect"; import { WorkspacePageError, WorkspacePageLoader } from "@/modules/workspace/pages/shared"; const HIDDEN_FROM_REVIEW = new Set(["approved"]); export const WorkspaceContentDetail = function WorkspaceContentDetailPage() { const params = useParams<{ id: string }>(); const router = useRouter(); const { tenantId } = useTenantId(); const queryClient = useQueryClient(); const [approver, setApprover] = useState(""); const [approvalType, setApprovalType] = useState("internal"); const [fileRef, setFileRef] = useState(""); const [assetTitle, setAssetTitle] = useState(""); const [scheduleOpen, setScheduleOpen] = useState(false); const [scheduledAt, setScheduledAt] = useState(""); const [archiveOpen, setArchiveOpen] = useState(false); const [briefDraft, setBriefDraft] = useState>({}); const item = useQuery({ queryKey: ["workspace", tenantId, "content-item", params.id], queryFn: () => workspaceApi.content.get(tenantId!, params.id), enabled: !!tenantId && !!params.id, }); const invalidate = () => { void queryClient.invalidateQueries({ queryKey: ["workspace", tenantId, "content-item", params.id] }); void queryClient.invalidateQueries({ queryKey: ["workspace", tenantId, "content"] }); }; const update = useMutation({ mutationFn: (body: Record) => workspaceApi.content.update(tenantId!, params.id, body), onSuccess: () => { toast.success("محتوا به‌روز شد"); setScheduleOpen(false); invalidate(); }, onError: (err) => toast.error(err instanceof Error ? err.message : "این اقدام مجاز نیست"), }); const requestApproval = useMutation({ mutationFn: () => workspaceApi.approvals.request(tenantId!, { content_id: params.id, approval_type: approvalType, approver_user_id: approver || item.data?.reviewer_user_id, }), onSuccess: () => { toast.success("درخواست تأیید ثبت شد"); invalidate(); }, onError: (err) => toast.error(err instanceof Error ? err.message : "درخواست تأیید ناموفق بود"), }); const respond = useMutation({ mutationFn: ({ id, status, comment }: { id: string; status: string; comment?: string }) => workspaceApi.approvals.respond(tenantId!, id, { status, comment }), onSuccess: () => { toast.success("پاسخ تأیید ذخیره شد"); invalidate(); }, onError: (err) => toast.error(err instanceof Error ? err.message : "ثبت پاسخ ناموفق بود"), }); const addAsset = useMutation({ mutationFn: () => workspaceApi.assets.create(tenantId!, { work_hub_id: item.data?.work_hub_id, content_id: params.id, file_ref: fileRef, title: assetTitle || fileRef, }), onSuccess: () => { toast.success("ارجاع فایل ثبت شد"); setFileRef(""); setAssetTitle(""); invalidate(); }, onError: (err) => toast.error(err instanceof Error ? err.message : "ثبت ارجاع ناموفق بود"), }); const duplicate = useMutation({ mutationFn: () => { const row = item.data!; return workspaceApi.content.create(tenantId!, { work_hub_id: row.work_hub_id, project_id: row.project_id, team_id: row.team_id, title: `${row.title || "محتوا"} (رونوشت)`, content_type: row.content_type, channel: row.channel, brief: row.brief, owner_user_id: row.owner_user_id, reviewer_user_id: row.reviewer_user_id, }); }, onSuccess: (created) => { toast.success("رونوشت ایجاد شد"); if (created.id) router.push(`/workspace/content/${created.id}`); }, }); if (!tenantId || item.isLoading) return ; if (item.error) return item.refetch()} />; const row = item.data!; const brief = { ...((row.brief || {}) as Record), ...briefDraft }; const current = String(row.status || "draft"); const nextActions = (row.next_actions || []).filter((status) => { if (["internal_review", "client_review"].includes(current) && HIDDEN_FROM_REVIEW.has(status)) return false; return true; }); function applyStatus(status: string) { if (status === "scheduled") { setScheduleOpen(true); return; } update.mutate({ status }); } return (
} />
مسئول: {displayPerson(String(row.owner_user_id || ""))} بازبین: {displayPerson(String(row.reviewer_user_id || ""))}

اقدام بعدی

{nextActions.map((status) => ( ))} {["internal_review", "client_review"].includes(current) ? ( ) : null} {current === "scheduled" ? ( ) : null}
update.mutate({ owner_user_id: id })} emptyLabel="سازنده / مسئول" /> update.mutate({ reviewer_user_id: id })} emptyLabel="بازبین" />

بریف محتوا

{BRIEF_FIELDS.map((field) => field.multiline ? (