inogen/app/components/dashboard/header.tsx
2025-10-13 18:29:29 +03:30

366 lines
12 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 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={nextFromYearHandler}
prevYearHandler={prevFromYearHandler}
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={nextUntilYearHandler}
prevYearHandler={prevUntilYearHandler}
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;