import { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { CheckCircle2, ChevronDown, ChevronUp, Folder, RefreshCw } from "lucide-react"; import { dashboardTaskKey, devLogin, getDashboardToday, getDevRoles } from "../lib/api"; import type { DashboardTask, Project } from "../lib/api"; import { Card, CardSkeleton, Chip, EmptyState, ErrorState, PageHeader, PanelsMenu, ProgressBar } from "../components/ui"; import { formatDate, isOverdue } from "../lib/format"; import { roleLabels, scopeLabel } from "../lib/roles"; import { useAuthStore } from "../store/auth"; type SectionKey = "overdue" | "due_today" | "awaiting_qc" | "failed_qc" | "blocked" | "upcoming"; interface SectionMeta { key: SectionKey; title: string; tone: "red" | "amber" | "sky" | "slate"; } /** * بخش‌های داشبورد به تفکیک نقش: * مدیر پروژه نگاه مدیریتی دارد (تأخیرها و QC همه پروژه‌ها) و سرپرست پروژه نگاه * عملیاتی (کار امروز و کاری که باید همین حالا اجرا/بازرسی شود). */ const managerSections: SectionMeta[] = [ { key: "overdue", title: "عقب‌افتاده", tone: "red" }, { key: "due_today", title: "کارهای امروز", tone: "sky" }, { key: "awaiting_qc", title: "منتظر QC", tone: "amber" }, { key: "failed_qc", title: "QC مردود", tone: "red" }, { key: "blocked", title: "مسدود شده", tone: "slate" }, { key: "upcoming", title: "هفته آینده", tone: "sky" }, ]; const supervisorSections: SectionMeta[] = [ { key: "due_today", title: "کارهای امروز", tone: "sky" }, { key: "overdue", title: "عقب‌افتاده", tone: "red" }, { key: "awaiting_qc", title: "منتظر اجرای بازرسی", tone: "amber" }, { key: "failed_qc", title: "بازرسی مردود", tone: "red" }, { key: "blocked", title: "مسدود شده (پیش‌نیاز ناتمام)", tone: "slate" }, { key: "upcoming", title: "هفته آینده", tone: "sky" }, ]; function TaskRow({ task, showBlockedBy = false }: { task: DashboardTask; showBlockedBy?: boolean }) { const overdue = isOverdue(task.due_date); // شناسهٔ پایدار: UUID تسک بومی، وگرنه op_wp_id میراث const taskKey = dashboardTaskKey(task); return (

{task.subject}

{formatDate(task.due_date)}
{task.percentage_done}٪
{task.project_name} {showBlockedBy && task.blocked_by && ( مسدود توسط: {task.blocked_by} )}
); } function SummaryBox({ tone, label, value }: { tone: "red" | "amber" | "green"; label: string; value: number }) { const cls = { red: "border-red-100 bg-red-50 text-red-600", amber: "border-amber-100 bg-amber-50 text-amber-600", green: "border-emerald-100 bg-emerald-50 text-emerald-600", }[tone]; return (

{value}

{label}

); } /** کاشی مدیریتی یک پروژه — فقط در داشبورد مدیر پروژه */ function ProjectTile({ project }: { project: Project }) { return (

{project.name}

{project.stats.total} فعالیت {project.stats.overdue > 0 && {project.stats.overdue} عقب‌افتاده} {project.stats.awaiting_qc > 0 && {project.stats.awaiting_qc} منتظر QC} {project.stats.failed_qc > 0 && {project.stats.failed_qc} مردود}
); } /** انتخابگر نقش — فقط در فاز توسعه (سرور آن را با PM_DEV_ROLE_SWITCH کنترل می‌کند) */ function DevRoleSwitcher() { const user = useAuthStore((s) => s.user); const setAuth = useAuthStore((s) => s.setAuth); const navigate = useNavigate(); const queryClient = useQueryClient(); const isDevMode = !import.meta.env.PROD; const rolesQuery = useQuery({ queryKey: ["dev-roles"], queryFn: getDevRoles, staleTime: 60_000, retry: false, enabled: isDevMode, }); const switchMutation = useMutation({ mutationFn: (username: string) => devLogin(username), onSuccess: (data) => { queryClient.clear(); setAuth(data.token, data.user); navigate("/", { replace: true }); }, onError: () => { queryClient.invalidateQueries({ queryKey: ["dev-roles"] }); }, }); if (!isDevMode || rolesQuery.isError || !rolesQuery.data || rolesQuery.data.length === 0) { return null; } const roles = rolesQuery.data; return ( ); } export default function TodayPage() { const queryClient = useQueryClient(); const user = useAuthStore((s) => s.user); const [showUpcoming, setShowUpcoming] = useState(false); const todayQuery = useQuery({ queryKey: ["today"], queryFn: getDashboardToday }); const data = todayQuery.data; const refresh = () => { void queryClient.invalidateQueries({ queryKey: ["today"] }); }; const isManager = user?.role === "project_manager"; const role = data?.role ?? user?.role ?? "project_supervisor"; const sections = isManager ? managerSections : supervisorSections; const projects = data?.projects ?? []; const totalItems = data ? Object.values(data.sections).reduce((sum, list) => sum + list.length, 0) : 0; return ( <> } right={ } />
{/* هویت نقش و محدوده دسترسی */}
{roleLabels[role]} {scopeLabel(user)} {isManager ? "نمای مدیریتی همه پروژه‌ها" : "نمای عملیاتی پروژه‌های اختصاص‌یافته"}
{todayQuery.isPending ? (
) : todayQuery.isError ? ( ) : data ? ( <>
{/* نمای مدیریتی پروژه‌ها — فقط مدیر پروژه */} {isManager && projects.length > 0 && (

وضعیت پروژه‌ها

همه پروژه‌ها
{projects.map((p) => ( ))}
)} {/* ناظر بدون پروژه اختصاص‌یافته */} {!isManager && (data?.scope?.project_count ?? user?.scope?.project_count ?? 0) === 0 && totalItems === 0 && ( )} {totalItems === 0 && (isManager || (data?.scope?.project_count ?? user?.scope?.project_count ?? 0) > 0) ? ( ) : (
{sections.map(({ key, title, tone }) => { const items = data.sections[key]; if (!items || items.length === 0) return null; const isUpcoming = key === "upcoming"; if (isUpcoming && !showUpcoming) { return ( ); } return (

{title}

{items.length} مورد {isUpcoming && ( )}
{items.map((t) => ( ))}
); })}
)} ) : null}
); }