import React, { useState, useRef, useEffect } from "react"; import { Bell, ChevronDown, Menu, Search, X, Download, LogIn, LogOut, Loader2 } from "lucide-react"; import { useNavigate, useLocation } from "react-router-dom"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import Logo from "@/components/ui/logo"; import { useIsMobile } from "@/hooks/use-mobile"; import { useAuth } from "@/lib/auth-context"; import { toast } from "@/lib/custom-toast"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { useAssetRequestNotifications } from "@/hooks/useAssetRequestNotifications"; import { attendanceApi } from "@/services/attendanceApi"; import { getConfettiOriginFromElement, triggerAttendanceConfetti } from "@/lib/confetti"; import CheckOutReminderDialog from "@/components/CheckOutReminderDialog"; interface HeaderProps { toggleSidebar: () => void; isSidebarOpen: boolean; } const Header = ({ toggleSidebar, isSidebarOpen }: HeaderProps) => { const isMobile = useIsMobile(); const [isSearchOpen, setIsSearchOpen] = useState(false); const [userMenuOpen, setUserMenuOpen] = useState(false); const [downloadMenuOpen, setDownloadMenuOpen] = useState(false); const [now, setNow] = useState(() => new Date()); const [showCheckOutReminder, setShowCheckOutReminder] = useState(false); const [reminderDismissed, setReminderDismissed] = useState(false); const quickAttendanceBtnRef = useRef(null); const userMenuRef = useRef(null); const downloadMenuRef = useRef(null); const { logout, userData, isAuthenticated } = useAuth(); const queryClient = useQueryClient(); const navigate = useNavigate(); const location = useLocation(); const { notificationCount, hasNotifications } = useAssetRequestNotifications(); // Check if we're on the download app page const isDownloadPage = location.pathname === "/download-app"; const employeeId = userData?.employeeId; const isClient = userData?.roleName?.toLowerCase() === "client"; const { data: todayAttendance, isLoading: todayAttendanceLoading } = useQuery({ queryKey: ["attendance-today", employeeId ?? 0], queryFn: () => { if (!employeeId) return Promise.resolve(null); return attendanceApi.getToday(employeeId); }, enabled: isAuthenticated && !isDownloadPage && !isClient && !!employeeId, refetchInterval: 60_000, }); const invalidateAttendance = async () => { if (employeeId) { await queryClient.invalidateQueries({ queryKey: ["attendance-today", employeeId] }); } await queryClient.invalidateQueries({ queryKey: ["attendance"] }); }; const checkInMutation = useMutation({ mutationFn: async () => { if (!employeeId) throw new Error("Employee ID not found"); return attendanceApi.checkIn(employeeId); }, onSuccess: async () => { toast.success("Checked in successfully"); triggerAttendanceConfetti("checkIn", getConfettiOriginFromElement(quickAttendanceBtnRef.current)); await invalidateAttendance(); }, onError: (error: any) => { toast.error(error?.response?.data || error?.message || "Failed to check in"); }, }); const checkOutMutation = useMutation({ mutationFn: async () => { if (!employeeId) throw new Error("Employee ID not found"); return attendanceApi.checkOut(employeeId); }, onSuccess: async () => { toast.success("Checked out successfully"); triggerAttendanceConfetti("checkOut", getConfettiOriginFromElement(quickAttendanceBtnRef.current)); await invalidateAttendance(); }, onError: (error: any) => { toast.error(error?.response?.data || error?.message || "Failed to check out"); }, }); const isCheckedIn = !!todayAttendance?.inTime; const isCheckedOut = !!todayAttendance?.outTime; const canCheckIn = !todayAttendanceLoading && !checkInMutation.isPending && !isCheckedIn; const canCheckOut = !todayAttendanceLoading && !checkOutMutation.isPending && isCheckedIn && !isCheckedOut; const showQuickAttendance = isAuthenticated && !isDownloadPage && !isClient && !!employeeId; useEffect(() => { if (!isAuthenticated || isDownloadPage || !isCheckedIn || isCheckedOut) { setNow(new Date()); return; } const intervalId = window.setInterval(() => setNow(new Date()), 1000); return () => window.clearInterval(intervalId); }, [isAuthenticated, isDownloadPage, isCheckedIn, isCheckedOut]); const parseTimeToToday = (time: string) => { const parts = time.split(":").map(p => parseInt(p, 10)); const [hh, mm, ss] = [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0]; const dt = new Date(now); dt.setHours(hh, mm, ss, 0); return dt; }; const officeDurationText = (() => { if (!todayAttendance?.inTime) return null; const start = parseTimeToToday(todayAttendance.inTime); const end = todayAttendance.outTime ? parseTimeToToday(todayAttendance.outTime) : now; const diffMs = Math.max(0, end.getTime() - start.getTime()); const totalSeconds = Math.floor(diffMs / 1000); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; const pad2 = (n: number) => n.toString().padStart(2, "0"); return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`; })(); // Handle navigation to home/dashboard const goToHomePage = () => { if (isAuthenticated) { // For client role, redirect to production-bugs page instead of dashboard if (userData?.roleName?.toLowerCase() === "client") { navigate('/production-bugs'); } else { // For other roles, redirect to dashboard navigate('/'); } } else { // For non-authenticated users, redirect to login navigate('/login'); } }; // Check if it's past 6:30 PM and show checkout reminder useEffect(() => { if (!isAuthenticated || isDownloadPage || isClient || !isCheckedIn || isCheckedOut || reminderDismissed) { return; } const currentHour = now.getHours(); const currentMinute = now.getMinutes(); // Check if it's 6:30 PM or later (18:30 in 24-hour format) const isPast630PM = currentHour > 18 || (currentHour === 18 && currentMinute >= 30); if (isPast630PM && !showCheckOutReminder) { setShowCheckOutReminder(true); } }, [now, isAuthenticated, isDownloadPage, isClient, isCheckedIn, isCheckedOut, reminderDismissed, showCheckOutReminder]); // Handle outside clicks to close the menus useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) { setUserMenuOpen(false); } if (downloadMenuRef.current && !downloadMenuRef.current.contains(event.target as Node)) { setDownloadMenuOpen(false); } }; if (userMenuOpen || downloadMenuOpen) { document.addEventListener('mousedown', handleClickOutside); document.addEventListener('touchstart', handleClickOutside as any); } return () => { document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('touchstart', handleClickOutside as any); }; }, [userMenuOpen, downloadMenuOpen]); const handleLogout = () => { logout(); toast.success("Logged out successfully"); navigate('/login'); }; const handleReminderClose = () => { setShowCheckOutReminder(false); setReminderDismissed(true); }; const handleReminderCheckOut = async () => { await checkOutMutation.mutateAsync(); setShowCheckOutReminder(false); }; const toggleUserMenu = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setUserMenuOpen(prev => !prev); }; const toggleDownloadMenu = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setDownloadMenuOpen(prev => !prev); }; // Get initials for avatar fallback const getInitials = () => { if (!userData) return "U"; const first = userData.firstName?.charAt(0) || ''; const last = userData.lastName?.charAt(0) || ''; return (first + last).toUpperCase(); }; // Get display name const getDisplayName = () => { if (!userData) return "User"; return `${userData.firstName} ${userData.lastName}`; }; // Generate avatar URL const getAvatarUrl = () => { if (!userData) return ''; return `https://ui-avatars.com/api/?name=${userData.firstName}+${userData.lastName}&background=random`; }; // Download handlers const handleDownloadAPK = () => { window.open('/pm-tool.apk', '_blank'); setDownloadMenuOpen(false); }; const handleVisitDownloadPage = () => { navigate('/download-app'); setDownloadMenuOpen(false); }; return (
{isMobile && isAuthenticated && ( )} {(!isMobile || !isSearchOpen) && (
)}
{!isDownloadPage && isAuthenticated && (isSearchOpen || !isMobile) && (
{isMobile && ( )}
)} {isMobile && !isSearchOpen && !isDownloadPage && isAuthenticated && ( )}
{/* Custom Download Menu Implementation */}
{downloadMenuOpen && (

Download Options

)}
{isAuthenticated ? ( <> {!isDownloadPage && !!officeDurationText && (
In Office {officeDurationText}
)} {showQuickAttendance && (

{canCheckIn ? "Quick Check In" : canCheckOut ? "Quick Check Out" : "Attendance completed for today"}

)}

{hasNotifications ? `${notificationCount} asset request ${notificationCount === 1 ? 'notification' : 'notifications'}` : 'No new notifications'}

{/* Custom User Menu Implementation */}
{userMenuOpen && (

My Account

)}
) : (
)}
{/* Check-Out Reminder Dialog */}
); }; export default Header;