Files
ERP/frontend/src/components/layout/Header.tsx
T

90 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
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.
import type { ReactNode } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { ChevronRight, ClipboardList } from "lucide-react";
import { cn } from "../../lib/utils";
import { useAuthStore, type AuthUser } from "../../store/auth";
const ROLE_LABELS: Record<string, string> = {
admin: "ادمین",
procurement_manager: "مدیر تدارکات",
pm: "مدیر پروژه",
supervisor: "سرپرست پروژه",
};
export function userSubtitle(user: AuthUser | null | undefined): string {
if (!user) return "";
const fullName = `${user.first_name || ""} ${user.last_name || ""}`.trim() || user.display_name || "کاربر";
const username = user.username || user.casdoor_id || "-";
const role = user.role_label || (user.role ? ROLE_LABELS[user.role] : "بدون نقش");
const proj = user.role === "supervisor" && user.project_name ? ` – پروژه: ${user.project_name}` : "";
return `${fullName} | نام کاربری: ${username} | نقش: ${role}${proj}`;
}
interface HeaderProps {
title: string;
sub?: string;
backTo?: string; // مسیر بازگشتِ جایگزین (وقتی سابقه‌ای در برنامه نیست)
backLabel?: string;
actions?: ReactNode; // دکمه‌های سمت چپ
brand?: boolean; // نمایش آیکون دفتر کنار عنوان
icon?: ReactNode; // آیکون سفارشی جعبه‌ی کنار منو (پیش‌فرض: ClipboardList)
menu?: ReactNode; // منوی همبرگری بالای راست (ناوبری بین پنل‌ها)
}
// نوار بالای هر صفحه: منو (همبرگری) + جعبه‌ی آیکون + عنوان + اکشن‌ها
export function Header({ title, sub, backTo, backLabel = "بازگشت", actions, brand, icon, menu }: HeaderProps) {
const navigate = useNavigate();
const location = useLocation();
const user = useAuthStore((s) => s.user);
const userSub = userSubtitle(user);
const displaySub = sub !== undefined ? sub : userSub;
// اگر کاربر از داخل برنامه به این صفحه آمده، یک قدم برمی‌گردیم؛
// اگر مستقیم باز شده (بدون سابقه‌ی داخلی)، به backTo می‌رویم.
const goBack = () => {
if (location.key !== "default") navigate(-1);
else if (backTo) navigate(backTo);
};
return (
<header className="flex flex-wrap items-center gap-3 border-b border-line bg-panel px-5 py-2.5 max-md:sticky max-md:top-0 max-md:z-40">
{menu && <div className="flex shrink-0 items-center">{menu}</div>}
{(icon || brand) && (
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-accent-bg text-accent">
{icon || <ClipboardList className="h-4.5 w-4.5" strokeWidth={2} />}
</span>
)}
{backTo && (
<button
type="button"
onClick={goBack}
className="hidden md:inline-flex cursor-pointer items-center gap-0.5 rounded-md px-2 py-1 text-[13px] text-muted transition-colors hover:bg-slate-bg hover:text-ink"
>
<ChevronRight className="h-4 w-4" />
{backLabel}
</button>
)}
<div className="flex min-w-0 items-center gap-2.5">
<div className="min-w-0">
<h1 className="text-[17px] font-extrabold leading-6">{title}</h1>
{displaySub && <div className="text-[11.5px] text-muted font-normal mt-0.5">{displaySub}</div>}
</div>
</div>
<div className="ms-auto flex flex-wrap items-center gap-2">{actions}</div>
</header>
);
}
// بدنه‌ی صفحه با عرض محدود و چینش مرکزی
export function PageLayout({
children,
className,
}: {
children: ReactNode;
className?: string;
}) {
return (
<div className={cn("mx-auto w-full max-w-[1200px] px-4 py-4 max-md:pb-24", className)}>{children}</div>
);
}