366 lines
12 KiB
TypeScript
366 lines
12 KiB
TypeScript
import jalaali from "jalaali-js";
|
||
import {
|
||
Calendar,
|
||
ChevronLeft,
|
||
Menu,
|
||
PanelLeft,
|
||
Server,
|
||
Settings,
|
||
User,
|
||
} from "lucide-react";
|
||
import React, { useEffect, useRef, useState } from "react";
|
||
import { Link } from "react-router";
|
||
import { Button } from "~/components/ui/button";
|
||
import { Calendar as CustomCalendar } from "~/components/ui/Calendar";
|
||
import { useAuth } from "~/contexts/auth-context";
|
||
import apiService from "~/lib/api";
|
||
import { cn, EventBus } from "~/lib/utils";
|
||
|
||
interface HeaderProps {
|
||
onToggleSidebar?: () => void;
|
||
className?: string;
|
||
title?: string;
|
||
titleIcon?: React.ComponentType<{ className?: string }> | null;
|
||
}
|
||
|
||
interface MonthItem {
|
||
id: string;
|
||
label: string;
|
||
start: string;
|
||
end: string;
|
||
}
|
||
|
||
interface CurrentDay {
|
||
start?: string;
|
||
end?: string;
|
||
sinceMonth?: string;
|
||
fromMonth?: string;
|
||
}
|
||
|
||
interface SelectedDate {
|
||
since?: number;
|
||
until?: number;
|
||
}
|
||
|
||
const monthList: Array<MonthItem> = [
|
||
{
|
||
id: "month-1",
|
||
label: "بهار",
|
||
start: "01/01",
|
||
end: "03/31",
|
||
},
|
||
{
|
||
id: "month-2",
|
||
label: "تابستان",
|
||
start: "04/01",
|
||
end: "06/31",
|
||
},
|
||
{
|
||
id: "month-3",
|
||
label: "پاییز",
|
||
start: "07/01",
|
||
end: "09/31",
|
||
},
|
||
{
|
||
id: "month-4",
|
||
label: "زمستان",
|
||
start: "10/01",
|
||
end: "12/29",
|
||
},
|
||
];
|
||
|
||
export function Header({
|
||
onToggleSidebar,
|
||
className,
|
||
title = "صفحه اول",
|
||
titleIcon,
|
||
}: HeaderProps) {
|
||
const { user } = useAuth();
|
||
const { jy } = jalaali.toJalaali(new Date());
|
||
|
||
const calendarRef = useRef<HTMLDivElement>(null);
|
||
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState<boolean>(false);
|
||
const [isNotificationOpen, setIsNotificationOpen] = useState<boolean>(false);
|
||
const [openCalendar, setOpenCalendar] = useState<boolean>(false);
|
||
const [currentYear, setCurrentYear] = useState<SelectedDate>({
|
||
since: jy,
|
||
until: jy,
|
||
});
|
||
|
||
const [selectedDate, setSelectedDate] = useState<CurrentDay>({
|
||
sinceMonth: "بهار",
|
||
fromMonth: "زمستان",
|
||
start: `${currentYear.since}/01/01`,
|
||
end: `${currentYear.until}/12/30`,
|
||
});
|
||
|
||
const redirectHandler = async () => {
|
||
try {
|
||
const getData = await apiService.post("/GenerateSsoCode");
|
||
const url = `https://inogen-bpms.pelekan.org/redirect/${getData.data}`;
|
||
window.open(url, "_blank");
|
||
} catch (error) {
|
||
console.log(error);
|
||
}
|
||
};
|
||
|
||
const changeSinceYear = (delta: number) => {
|
||
if (!currentYear) return;
|
||
|
||
const newSince = (currentYear.since ?? 0) + delta;
|
||
|
||
if (newSince > (currentYear.until ?? Infinity) || newSince < 0) return;
|
||
|
||
const updatedYear = { ...currentYear, since: newSince };
|
||
setCurrentYear(updatedYear);
|
||
|
||
const updatedDate = {
|
||
...selectedDate,
|
||
start: `${newSince}/${selectedDate.start?.split("/").slice(1).join("/")}`,
|
||
};
|
||
setSelectedDate(updatedDate);
|
||
EventBus.emit("dateSelected", updatedDate);
|
||
};
|
||
|
||
const nextFromYearHandler = () => changeSinceYear(1);
|
||
const prevFromYearHandler = () => changeSinceYear(-1);
|
||
|
||
const selectFromDateHandler = (val: MonthItem) => {
|
||
const data = {
|
||
...selectedDate,
|
||
start: `${currentYear.since}/${val.start}`,
|
||
sinceMonth: val.label,
|
||
};
|
||
setSelectedDate(data);
|
||
EventBus.emit("dateSelected", data);
|
||
};
|
||
|
||
const changeUntilYear = (delta: number) => {
|
||
if (!currentYear) return;
|
||
|
||
const newUntil = (currentYear.until ?? 0) + delta;
|
||
|
||
if (newUntil < (currentYear.since ?? 0)) return;
|
||
|
||
const updatedYear = { ...currentYear, until: newUntil };
|
||
setCurrentYear(updatedYear);
|
||
|
||
const updatedDate = {
|
||
...selectedDate,
|
||
end: `${newUntil}/${selectedDate.end?.split("/").slice(1).join("/")}`,
|
||
};
|
||
setSelectedDate(updatedDate);
|
||
EventBus.emit("dateSelected", updatedDate);
|
||
};
|
||
|
||
const nextUntilYearHandler = () => changeUntilYear(1);
|
||
const prevUntilYearHandler = () => changeUntilYear(-1);
|
||
|
||
const selectUntilDateHandler = (val: MonthItem) => {
|
||
const data = {
|
||
...selectedDate,
|
||
end: `${currentYear.until}/${val.end}`,
|
||
fromMonth: val.label,
|
||
};
|
||
setSelectedDate(data);
|
||
EventBus.emit("dateSelected", data);
|
||
toggleCalendar();
|
||
};
|
||
|
||
const toggleCalendar = () => {
|
||
setOpenCalendar(!openCalendar);
|
||
};
|
||
|
||
useEffect(() => {
|
||
const handleClickOutside = (event: MouseEvent) => {
|
||
if (
|
||
calendarRef.current &&
|
||
!calendarRef.current.contains(event.target as Node)
|
||
) {
|
||
setOpenCalendar(false);
|
||
}
|
||
};
|
||
document.addEventListener("mousedown", handleClickOutside);
|
||
return () => {
|
||
document.removeEventListener("mousedown", handleClickOutside);
|
||
};
|
||
}, []);
|
||
|
||
return (
|
||
<header
|
||
className={cn(
|
||
"backdrop-blur-sm border-b border-gray-400/30 h-16 flex items-center justify-between px-4 lg:px-6 shadow-sm relative z-30",
|
||
className
|
||
)}
|
||
>
|
||
{/* Left Section */}
|
||
<div className="flex items-center gap-4">
|
||
{/* Mobile Menu Toggle */}
|
||
{onToggleSidebar && (
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={onToggleSidebar}
|
||
className="lg:hidden"
|
||
>
|
||
<Menu className="h-5 w-5" />
|
||
</Button>
|
||
)}
|
||
|
||
{/* Page Title */}
|
||
<h1 className="text-xl flex items-center justify-center gap-4 font-bold text-white font-persian">
|
||
{/* Right-side icon for current page */}
|
||
{titleIcon ? (
|
||
<div className="flex items-center gap-2 mr-4">
|
||
{React.createElement(titleIcon, { className: "w-5 h-5 " })}
|
||
</div>
|
||
) : (
|
||
<PanelLeft />
|
||
)}
|
||
{title.includes("-") ? (
|
||
<div className="flex row items-center gap-4">
|
||
<div className="flex items-center gap-1">
|
||
{title.split("-")[0]}
|
||
<ChevronLeft className="inline-block w-4 h-4" />
|
||
{title.split("-")[1]}
|
||
</div>
|
||
</div>
|
||
) : (
|
||
title
|
||
)}
|
||
</h1>
|
||
|
||
<div ref={calendarRef} className="flex flex-col gap-3 relative">
|
||
<div
|
||
onClick={toggleCalendar}
|
||
className="flex flex-row w-full gap-2 items-center border border-pr-gray p-1.5 rounded-md px-2.5 min-w-64 cursor-pointer hover:bg-pr-gray/50 transition-all duration-300"
|
||
>
|
||
<Calendar size={20} />
|
||
{selectedDate ? (
|
||
<div className="flex flex-row justify-between w-full min-w-36 font-bold gap-1">
|
||
<div className="flex flex-row gap-1.5 w-max">
|
||
<span className="text-md">از</span>
|
||
<span className="text-md">{selectedDate?.sinceMonth}</span>
|
||
<span className="text-md">{currentYear.since}</span>
|
||
</div>
|
||
<div className="flex flex-row gap-1.5 w-max">
|
||
<span className="text-md">تا</span>
|
||
<span className="text-md">{selectedDate?.fromMonth}</span>
|
||
<span className="text-md">{currentYear.until}</span>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
"تاریخ مورد نظر خود را انتخاب نمایید"
|
||
)}
|
||
</div>
|
||
|
||
{openCalendar && (
|
||
<div className="flex flex-row gap-2.5 absolute top-14 right-[-40px] p-2.5 !pt-3.5 w-80 rounded-3xl overflow-hidden bg-pr-gray border-2 border-[#5F6284]">
|
||
<CustomCalendar
|
||
title="از"
|
||
nextYearHandler={prevFromYearHandler}
|
||
prevYearHandler={nextFromYearHandler}
|
||
currentYear={currentYear?.since}
|
||
monthList={monthList}
|
||
selectedDate={selectedDate?.sinceMonth}
|
||
selectDateHandler={selectFromDateHandler}
|
||
/>
|
||
<span className="w-0.5 h-[12.5rem] border border-[#5F6284] block "></span>
|
||
<CustomCalendar
|
||
title="تا"
|
||
nextYearHandler={prevUntilYearHandler}
|
||
prevYearHandler={nextUntilYearHandler}
|
||
currentYear={currentYear?.until}
|
||
monthList={monthList}
|
||
selectedDate={selectedDate?.fromMonth}
|
||
selectDateHandler={selectUntilDateHandler}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Right Section */}
|
||
<div className="flex items-center gap-2">
|
||
{/* User Menu */}
|
||
<div className="relative">
|
||
<div className="flex items-center gap-2">
|
||
{user?.id === 2041 && (
|
||
<button
|
||
className="flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
|
||
onClick={redirectHandler}
|
||
>
|
||
<Server className="h-4 w-4" />
|
||
ورود به میزکار مدیریت
|
||
</button>
|
||
)}
|
||
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={() => setIsProfileMenuOpen(!isProfileMenuOpen)}
|
||
className="flex items-center gap-2 text-gray-300"
|
||
>
|
||
<div className="hidden sm:block text-right">
|
||
<div className="text-sm font-medium font-persian">
|
||
{user?.name} {user?.family}
|
||
</div>
|
||
<div className="text-xs text-gray-400 font-persian">
|
||
{user?.username}
|
||
</div>
|
||
</div>
|
||
<div className="w-8 h-8 bg-gradient-to-r from-emerald-500/20 to-teal-500/20 text-emerald-400 rounded-lg flex items-center justify-center">
|
||
<User className="h-4 w-4" />
|
||
</div>
|
||
</Button>
|
||
</div>
|
||
{/* Profile Dropdown */}
|
||
{isProfileMenuOpen && (
|
||
<div className="absolute left-0 top-full mt-2 w-48 bg-gray-800 border border-emerald-500/30 rounded-lg shadow-lg z-50">
|
||
<div className="p-3 border-b border-emerald-500/30">
|
||
<div className="text-sm font-medium text-white font-persian">
|
||
{user?.name} {user?.family}
|
||
</div>
|
||
<div className="text-xs text-gray-400 font-persian">
|
||
{user?.email}
|
||
</div>
|
||
</div>
|
||
<div className="py-1">
|
||
<Link
|
||
to="/dashboard/profile"
|
||
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
|
||
onClick={() => setIsProfileMenuOpen(false)}
|
||
>
|
||
<User className="h-4 w-4" />
|
||
پروفایل کاربری
|
||
</Link>
|
||
<Link
|
||
to="/dashboard/settings"
|
||
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
|
||
onClick={() => setIsProfileMenuOpen(false)}
|
||
>
|
||
<Settings className="h-4 w-4" />
|
||
تنظیمات
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Click outside to close dropdowns */}
|
||
{(isProfileMenuOpen || isNotificationOpen) && (
|
||
<div
|
||
className="fixed inset-0 z-40"
|
||
onClick={() => {
|
||
setIsProfileMenuOpen(false);
|
||
setIsNotificationOpen(false);
|
||
}}
|
||
/>
|
||
)}
|
||
</header>
|
||
);
|
||
}
|
||
|
||
export default Header;
|