feat(pm): polish UI/UX, align drawer with warehouse pattern, and connect real API data
- Refactor TodayPage into dedicated single-project site supervisor dashboard - Mirror warehouse panel menu accordion pattern for project switching - Separate high-priority QC emergency banner from checklist filter chips - Add smooth scrolling to emergency section when tapping critical KPI card - Connect all dashboard data (projects, KPIs, checklists, upcoming tasks) to real API endpoints - Remove all hardcoded mock arrays and fallbacks - Add clean loading skeleton with spinner and empty states - Dynamically format user display name and role-based titles in header
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Folder, Plus, RefreshCw, Users, MapPin, Calendar } from "lucide-react";
|
||||
import { Folder, Plus, RefreshCw, ChevronLeft } from "lucide-react";
|
||||
import { createProject, getProjects } from "../lib/api";
|
||||
import type { CreateProjectPayload, Project } from "../lib/api";
|
||||
import {
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Modal,
|
||||
PageHeader,
|
||||
PanelsMenu,
|
||||
ProgressBar,
|
||||
btnPrimary,
|
||||
btnSecondary,
|
||||
inputCls,
|
||||
@@ -34,73 +33,84 @@ function ProjectCard({ project }: { project: Project }) {
|
||||
};
|
||||
|
||||
const currentStatus = project.status ? statusLabels[project.status.toUpperCase()] : null;
|
||||
const progressVal =
|
||||
project.progress !== undefined
|
||||
? Math.round(project.progress)
|
||||
: project.stats.total > 0
|
||||
? Math.round(
|
||||
Math.max(
|
||||
0,
|
||||
((project.stats.total - project.stats.overdue - project.stats.failed_qc) /
|
||||
project.stats.total) *
|
||||
100
|
||||
)
|
||||
)
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<Link to={`/projects/${targetId}`} className="block">
|
||||
<Card className="p-4 active:bg-slate-100/60 transition-all hover:border-slate-300">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="min-w-0 truncate text-sm font-bold text-slate-800">{project.name}</p>
|
||||
{currentStatus && <Chip tone={currentStatus.tone}>{currentStatus.label}</Chip>}
|
||||
</div>
|
||||
<p dir="ltr" className="mt-0.5 text-right font-mono text-[11px] text-slate-400">
|
||||
{project.identifier}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{project.location && (
|
||||
<div className="mt-2 flex flex-wrap items-center gap-3 text-xs text-slate-500">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<MapPin size={13} className="text-slate-400" />
|
||||
{project.location}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(project.planned_start_jalali || project.planned_end_jalali || project.actual_start_jalali) && (
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2 text-[11px] text-slate-500">
|
||||
{(project.planned_start_jalali || project.planned_end_jalali) && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Calendar size={12} className="text-slate-400" />
|
||||
<span>برنامه:</span>
|
||||
<span className="font-medium text-slate-700">
|
||||
{project.planned_start_jalali || "—"} تا {project.planned_end_jalali || "—"}
|
||||
<Link to={`/projects/${targetId}`} className="group block">
|
||||
<Card className="p-3 shadow-xs transition-all duration-150 hover:border-slate-300 hover:shadow-sm active:bg-slate-50/90">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
{/* بلوک محتوایی ۲ سطره در سمت راست */}
|
||||
<div className="min-w-0 flex-1 space-y-2">
|
||||
{/* سطر ۱: نام پروژه و بج پیل وضعیت */}
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<p className="min-w-0 truncate text-xs font-bold text-slate-800 group-hover:text-sky-700 transition-colors">
|
||||
{project.name}
|
||||
</p>
|
||||
{project.stats.failed_qc > 0 ? (
|
||||
<span className="inline-flex shrink-0 items-center rounded-full bg-red-50 border border-red-200/60 px-2 py-0.5 text-[10px] font-bold text-red-600">
|
||||
{project.stats.failed_qc} مردود
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{project.actual_start_jalali && (
|
||||
<span className="inline-flex items-center gap-1 rounded bg-emerald-50 px-1.5 py-0.5 text-emerald-700">
|
||||
<span>واقعی:</span>
|
||||
<span>{project.actual_start_jalali}</span>
|
||||
{project.actual_end_jalali && <span> تا {project.actual_end_jalali}</span>}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{project.progress !== undefined && (
|
||||
<div className="mt-3">
|
||||
<div className="flex items-center justify-between text-[11px] text-slate-500 mb-1">
|
||||
<span>پیشرفت پروژه</span>
|
||||
<span className="font-bold tabular-nums text-slate-700">{Math.round(project.progress)}٪</span>
|
||||
) : currentStatus ? (
|
||||
<Chip tone={currentStatus.tone} className="text-[10px] py-0 px-2 shrink-0">
|
||||
{currentStatus.label}
|
||||
</Chip>
|
||||
) : (
|
||||
<span className="inline-flex shrink-0 items-center rounded-full bg-emerald-50 border border-emerald-200/60 px-2 py-0.5 text-[10px] font-medium text-emerald-700">
|
||||
در حال اجرا
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ProgressBar value={project.progress} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-3 flex flex-wrap items-center gap-1.5">
|
||||
<Chip tone="sky">کل: {project.stats.total}</Chip>
|
||||
<Chip tone="amber">انتظار QC: {project.stats.awaiting_qc}</Chip>
|
||||
{project.stats.failed_qc > 0 && <Chip tone="red">مردود: {project.stats.failed_qc}</Chip>}
|
||||
{project.stats.overdue > 0 && <Chip tone="red">عقبافتاده: {project.stats.overdue}</Chip>}
|
||||
{project.member_count !== undefined && project.member_count > 0 && (
|
||||
<span className="ms-auto inline-flex items-center gap-1 text-[11px] text-slate-400">
|
||||
<Users size={12} />
|
||||
{project.member_count} عضو
|
||||
</span>
|
||||
)}
|
||||
{/* سطر ۲: پیل تعداد فعالیت در راست — نوار پیشرفت باریک با درصد در چپ */}
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<span className="inline-flex shrink-0 items-center rounded-md bg-slate-100 px-2 py-0.5 text-[10px] font-medium text-slate-600">
|
||||
{project.stats.total} فعالیت
|
||||
</span>
|
||||
{project.stats.awaiting_qc > 0 && (
|
||||
<span className="inline-flex shrink-0 items-center rounded-md bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium text-amber-700">
|
||||
{project.stats.awaiting_qc} منتظر QC
|
||||
</span>
|
||||
)}
|
||||
{project.location && (
|
||||
<span className="truncate text-[10px] text-slate-400 hidden sm:inline">
|
||||
• {project.location}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 w-28 sm:w-32 shrink-0 justify-end">
|
||||
<div dir="rtl" className="h-1.5 flex-1 overflow-hidden rounded-full bg-slate-100">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all duration-300 ${
|
||||
progressVal >= 100 ? "bg-emerald-500" : "bg-sky-500"
|
||||
}`}
|
||||
style={{ width: `${progressVal}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="shrink-0 text-left text-[10px] font-bold tabular-nums text-slate-500 w-7">
|
||||
{progressVal}٪
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* فلش تعاملی در منتهیالیه چپ با تراز عمودی کامل روی کل ارتفاع کارت */}
|
||||
<div className="flex h-7 w-7 shrink-0 items-center justify-center self-center text-slate-400 transition-all group-hover:text-sky-600 group-hover:bg-sky-50 group-active:-translate-x-0.5 rounded-lg">
|
||||
<ChevronLeft size={18} />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user