TorbatYar/frontend/modules/workspace/features/hubDashboard.tsx
Mortezakoohjani a483b47369 feat(workspace): complete production workspace and content operations
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-12 11:54:17 +03:30

110 lines
5.3 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 { useQuery } from "@tanstack/react-query";
import { useParams } from "next/navigation";
import Link from "next/link";
import { Button, PageHeader, StatCard, Card, CardContent } from "@/components/ds";
import { useTenantId } from "@/hooks/useTenantId";
import { workspaceApi } from "@/modules/workspace/services/workspace-api";
import { CONTENT_STATUS, displayPerson, formatDay, labelOf } from "@/modules/workspace/labels";
import { StatusChip } from "@/modules/workspace/components/StatusChip";
import { WorkspaceEmptyState, WorkspacePageError, WorkspacePageLoader } from "@/modules/workspace/pages/shared";
export const WorkspaceHubDashboard = function WorkspaceHubDashboardPage() {
const params = useParams<{ id: string }>();
const { tenantId } = useTenantId();
const hubId = params.id;
const dash = useQuery({
queryKey: ["workspace", tenantId, "dashboard", hubId],
queryFn: () => workspaceApi.hubs.dashboard(tenantId!, hubId),
enabled: !!tenantId && !!hubId,
});
const hub = useQuery({
queryKey: ["workspace", tenantId, "hub", hubId],
queryFn: () => workspaceApi.hubs.get(tenantId!, hubId),
enabled: !!tenantId && !!hubId,
});
if (!tenantId || dash.isLoading || hub.isLoading) return <WorkspacePageLoader />;
if (dash.error) return <WorkspacePageError error={dash.error} onRetry={() => dash.refetch()} />;
const data = dash.data!;
const overview = data.overview;
return (
<div className="space-y-6">
<PageHeader
title={String(hub.data?.name || "داشبورد مرکز کار")}
description="نمای واقعی از پروژه‌ها، وظایف و وضعیت تولید محتوا."
actions={
<div className="flex flex-wrap gap-2">
<Link href="/workspace/teams"><Button variant="outline">تیم‌ها</Button></Link>
<Link href="/workspace/projects"><Button variant="outline">پروژه‌ها</Button></Link>
<Link href="/workspace/tasks"><Button variant="outline">وظایف</Button></Link>
<Link href="/workspace/content"><Button variant="outline">محتوا</Button></Link>
</div>
}
/>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<StatCard label="پروژه‌های فعال" value={overview.active_projects} />
<StatCard label="وظایف باز" value={overview.open_tasks} hint={`${overview.overdue_tasks} عقب‌افتاده`} />
<StatCard label="وضعیت تولید" value={overview.content_in_production} />
<StatCard label="در انتظار بررسی" value={overview.content_awaiting_review} />
<StatCard label="زمان‌بندی شده" value={overview.scheduled_content} />
<StatCard label="صف تأیید" value={overview.pending_approvals} />
<StatCard label="اعضای تیم" value={overview.team_members} />
<StatCard label="مهلت‌های نزدیک" value={overview.upcoming_deadlines} />
</div>
<div className="grid gap-4 lg:grid-cols-2">
<Card>
<CardContent className="space-y-3 p-5">
<h2 className="font-semibold text-secondary">قیف محتوا</h2>
{Object.entries(data.content).map(([status, count]) => (
<div key={status} className="flex items-center justify-between text-sm">
<span>{labelOf(CONTENT_STATUS, status)}</span>
<span className="font-medium">{count}</span>
</div>
))}
</CardContent>
</Card>
<Card>
<CardContent className="space-y-3 p-5">
<h2 className="font-semibold text-secondary">بار کاری تیم</h2>
{data.workload.length === 0 ? (
<WorkspaceEmptyState title="بار کاری ثبت نشده" description="با تخصیص وظیفه یا محتوا، این بخش پر می‌شود." />
) : (
data.workload.map((row) => (
<div key={row.user_id} className="flex items-center justify-between text-sm">
<span>{displayPerson(row.user_id)}</span>
<span>
{row.tasks} وظیفه · {row.content} محتوا · {row.overdue} عقب‌افتاده
</span>
</div>
))
)}
</CardContent>
</Card>
</div>
<Card>
<CardContent className="space-y-3 p-5">
<h2 className="font-semibold text-secondary">مهلت‌های پیش‌رو</h2>
{data.upcoming.length === 0 ? (
<p className="text-sm text-[var(--muted)]">مهلت نزدیکی ثبت نشده است.</p>
) : (
data.upcoming.map((task) => (
<div key={String(task.id)} className="flex items-center justify-between gap-3 text-sm">
<span>{String(task.title || "وظیفه")}</span>
<div className="flex items-center gap-2">
<StatusChip value={String(task.status || "")} kind="task" />
<span className="text-[var(--muted)]">{formatDay(String(task.due_date || ""))}</span>
</div>
</div>
))
)}
</CardContent>
</Card>
</div>
);
};
export default WorkspaceHubDashboard;