diff --git a/application/src/components/dashboard/Header.tsx b/application/src/components/dashboard/Header.tsx index f282fcd..83c0b67 100644 --- a/application/src/components/dashboard/Header.tsx +++ b/application/src/components/dashboard/Header.tsx @@ -1,71 +1,59 @@ import { Button } from "@/components/ui/button"; import { AuthUser } from "@/services/authService"; import { useTheme } from "@/contexts/ThemeContext"; -import { Moon, PanelLeft, PanelLeftClose, Sun, Globe, FileText, Github, Twitter, MessageSquare, Bell, User, Settings, LogOut } from "lucide-react"; +import { + Moon, PanelLeft, PanelLeftClose, Sun, Globe, FileText, + Github, Twitter, MessageSquare, Bell, User, Settings, + LogOut, Menu, X +} from "lucide-react"; import { useLanguage } from "@/contexts/LanguageContext"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator } from "@/components/ui/dropdown-menu"; +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuTrigger, DropdownMenuSeparator +} from "@/components/ui/dropdown-menu"; import { useEffect, useState } from "react"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { useNavigate } from "react-router-dom"; import { useSystemSettings } from "@/hooks/useSystemSettings"; +import { useSidebar } from "@/contexts/SidebarContext"; +import { cn } from "@/lib/utils"; interface HeaderProps { currentUser: AuthUser | null; onLogout: () => void; - sidebarCollapsed: boolean; - toggleSidebar: () => void; } export const Header = ({ currentUser, onLogout, - sidebarCollapsed, - toggleSidebar }: HeaderProps) => { const { theme, toggleTheme } = useTheme(); const { language, setLanguage, t } = useLanguage(); + const { sidebarCollapsed, toggleSidebar, isMobileOpen, toggleMobileMenu } = useSidebar(); const [greeting, setGreeting] = useState(""); const { systemName } = useSystemSettings(); const navigate = useNavigate(); - // Set greeting based on time of day useEffect(() => { const updateGreeting = () => { const hour = new Date().getHours(); - if (hour >= 5 && hour < 12) { - setGreeting(t("goodMorning")); - } else if (hour >= 12 && hour < 18) { - setGreeting(t("goodAfternoon")); - } else { - setGreeting(t("goodEvening")); - } + if (hour >= 5 && hour < 12) setGreeting(t("goodMorning")); + else if (hour >= 12 && hour < 18) setGreeting(t("goodAfternoon")); + else setGreeting(t("goodEvening")); }; updateGreeting(); - }, [language, t]); - // Log avatar data for debugging - useEffect(() => { - if (currentUser) { + const avatarUrl = currentUser?.avatar || ''; - } - }, [currentUser]); - - let avatarUrl = ''; - if (currentUser?.avatar) { - - if (currentUser.avatar.startsWith('/upload/profile/')) { - avatarUrl = currentUser.avatar; - } else { - avatarUrl = currentUser.avatar; - } - console.log("Final avatar URL:", avatarUrl); - } + const openExternalLink = (url: string) => { + window.open(url, '_blank'); + }; return ( -
- {/* Grid Pattern Overlay - Similar to StatusCards */} -
+
+ {/* Grid Pattern Overlay */} +
- {/* Header Content */} -
- + + {/* Desktop Toggle */} + -
-

{greeting}, {currentUser?.name || currentUser?.email?.split('@')[0] || 'User'} 👋 ✨

+
+

+ {greeting}, {currentUser?.name || currentUser?.email?.split('@')[0] || 'User'} 👋 ✨ +

-
- + + + + +
+ + @@ -102,7 +145,7 @@ export const Header = ({ - + setLanguage("en")} className={language === "en" ? "bg-accent" : ""}> {t("english")} @@ -110,7 +153,7 @@ export const Header = ({ {t("khmer")} setLanguage("de")} className={language === "de" ? "bg-accent" : ""}> - {t("Deutsch")} + {t("german")} setLanguage("ko")} className={language === "ko" ? "bg-accent" : ""}> {t("korean")} @@ -124,74 +167,21 @@ export const Header = ({ - {/* Documentation */} - - - {/* GitHub */} - - - {/* X (Twitter) */} - - - {/* Discord */} - - - {/* Notifications */} - - - {/* User Profile Dropdown */} +
+ - - {avatarUrl ? : - {currentUser?.name?.[0] || currentUser?.email?.[0] || 'U'} - } + + {avatarUrl ? : {currentUser?.name?.[0] || 'U'}}
- {avatarUrl ? : - {currentUser?.name?.[0] || currentUser?.email?.[0] || 'U'} - } + {avatarUrl ? : {currentUser?.name?.[0] || 'U'}} -
- {currentUser?.name || 'User'} +
+ {currentUser?.name || 'User'} {currentUser?.email}
@@ -214,4 +204,4 @@ export const Header = ({
); -}; +}; \ No newline at end of file diff --git a/application/src/components/dashboard/Sidebar.tsx b/application/src/components/dashboard/Sidebar.tsx index 21ba733..4feec70 100644 --- a/application/src/components/dashboard/Sidebar.tsx +++ b/application/src/components/dashboard/Sidebar.tsx @@ -1,28 +1,44 @@ - import React from "react"; import { useTheme } from "@/contexts/ThemeContext"; import { SidebarHeader } from "./sidebar/SidebarHeader"; import { MainNavigation } from "./sidebar/MainNavigation"; import { SettingsPanel } from "./sidebar/SettingsPanel"; +import { useSidebar } from "@/contexts/SidebarContext"; +import { cn } from "@/lib/utils"; -interface SidebarProps { - collapsed: boolean; -} - -export const Sidebar = ({ collapsed }: SidebarProps) => { +export const Sidebar = () => { const { theme } = useTheme(); + const { sidebarCollapsed, isMobileOpen, toggleMobileMenu } = useSidebar(); return ( -
- - - -
+ <> + {/* Mobile Overlay */} + {isMobileOpen && ( +
+ )} + + {/* Sidebar Container */} + + ); }; \ No newline at end of file diff --git a/application/src/components/dashboard/sidebar/MenuItem.tsx b/application/src/components/dashboard/sidebar/MenuItem.tsx index 9d47d88..2bbf474 100644 --- a/application/src/components/dashboard/sidebar/MenuItem.tsx +++ b/application/src/components/dashboard/sidebar/MenuItem.tsx @@ -3,6 +3,8 @@ import { useLocation, useNavigate } from "react-router-dom"; import { useTheme } from "@/contexts/ThemeContext"; import { useLanguage } from "@/contexts/LanguageContext"; import { LucideIcon } from "lucide-react"; +import { cn } from "@/lib/utils"; + interface MenuItemProps { id: string; path: string | null; @@ -12,8 +14,8 @@ interface MenuItemProps { hasNavigation: boolean; collapsed: boolean; } + export const MenuItem: React.FC = ({ - id, path, icon: Icon, translationKey, @@ -21,36 +23,44 @@ export const MenuItem: React.FC = ({ hasNavigation, collapsed }) => { - const { - theme - } = useTheme(); - const { - t - } = useLanguage(); + const { theme } = useTheme(); + const { t } = useLanguage(); const location = useLocation(); const navigate = useNavigate(); + + const isActive = path && location.pathname === path; + const handleClick = (e: React.MouseEvent) => { e.preventDefault(); - e.stopPropagation(); if (hasNavigation && path) { - navigate(path, { - replace: false - }); + navigate(path); } }; - const isActive = path && location.pathname === path; - const mainIconSize = "h-5 w-5"; - return
- - {!collapsed && + + return ( +
+ + + {!collapsed && ( + {t(translationKey)} - } -
; +
+ )} + + {/* Small Indicator for active state */} + {isActive && !collapsed && ( +
+ )} +
+ ); }; \ No newline at end of file diff --git a/application/src/components/services/ServiceDetailContainer/ServiceDetailWrapper.tsx b/application/src/components/services/ServiceDetailContainer/ServiceDetailWrapper.tsx index ed1fac3..cc2153d 100644 --- a/application/src/components/services/ServiceDetailContainer/ServiceDetailWrapper.tsx +++ b/application/src/components/services/ServiceDetailContainer/ServiceDetailWrapper.tsx @@ -1,4 +1,3 @@ - import React from "react"; import { Sidebar } from "@/components/dashboard/Sidebar"; import { Header } from "@/components/dashboard/Header"; @@ -7,11 +6,9 @@ import { ServiceNotFound } from "@/components/services/ServiceNotFound"; import { Service } from "@/types/service.types"; interface ServiceDetailWrapperProps { - children: React.ReactNode; + children?: React.ReactNode; isLoading: boolean; service: Service | null; - sidebarCollapsed: boolean; - toggleSidebar: () => void; currentUser: any; handleLogout: () => void; } @@ -20,20 +17,16 @@ export const ServiceDetailWrapper = ({ children, isLoading, service, - sidebarCollapsed, - toggleSidebar, currentUser, handleLogout }: ServiceDetailWrapperProps) => { return (
- +
{isLoading ? ( @@ -48,4 +41,4 @@ export const ServiceDetailWrapper = ({
); -}; +}; \ No newline at end of file diff --git a/application/src/components/services/ServiceDetailContainer/index.tsx b/application/src/components/services/ServiceDetailContainer/index.tsx index fed6a1d..3b2ab8f 100644 --- a/application/src/components/services/ServiceDetailContainer/index.tsx +++ b/application/src/components/services/ServiceDetailContainer/index.tsx @@ -1,78 +1,34 @@ -import { useState, useEffect, useCallback } from "react"; +import React, { useState, useCallback } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { DateRangeOption } from "../DateRangeFilter"; import { authService } from "@/services/authService"; import { ServiceDetailContent } from "../ServiceDetailContent"; import { ServiceDetailWrapper } from "./ServiceDetailWrapper"; import { useServiceData, useRealTimeUpdates } from "./hooks"; -import { toast } from "@/components/ui/use-toast"; export const ServiceDetailContainer = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); - // Set default to 24h const [startDate, setStartDate] = useState(() => { const date = new Date(); - date.setHours(date.getHours() - 24); // Go back 24 hours + date.setHours(date.getHours() - 24); return date; }); const [endDate, setEndDate] = useState(() => { const date = new Date(); - date.setMinutes(date.getMinutes() + 5); // Add 5 minutes buffer to future + date.setMinutes(date.getMinutes() + 5); return date; }); const [selectedRange, setSelectedRange] = useState('24h'); - // State for sidebar collapse functionality (shared with Dashboard) - const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { - // Check if there's a saved preference in localStorage - const saved = localStorage.getItem("sidebarCollapsed"); - return saved ? JSON.parse(saved) : window.innerWidth < 768; - }); - - // Toggle sidebar and save preference - const toggleSidebar = useCallback(() => { - setSidebarCollapsed(prev => { - const newState = !prev; - localStorage.setItem("sidebarCollapsed", JSON.stringify(newState)); - return newState; - }); - }, []); - - // Get current user for header const currentUser = authService.getCurrentUser(); - useEffect(() => { - // Verify user is authenticated - if (!authService.isAuthenticated()) { - toast({ - variant: "destructive", - title: "Authentication required", - description: "Please log in to view service details", - }); - navigate("/login"); - } - - // Auto-collapse sidebar on small screens - const handleResize = () => { - if (window.innerWidth < 768 && !sidebarCollapsed) { - setSidebarCollapsed(true); - localStorage.setItem("sidebarCollapsed", JSON.stringify(true)); - } - }; - - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, [navigate, sidebarCollapsed]); - - // Handler for logout (same as Dashboard) const handleLogout = () => { authService.logout(); navigate("/login"); }; - // Use our custom hooks const { service, uptimeData, @@ -85,7 +41,6 @@ export const ServiceDetailContainer = () => { handleRegionalAgentChange } = useServiceData(id, startDate, endDate); - // Set up real-time updates useRealTimeUpdates({ serviceId: id, startDate, @@ -94,18 +49,12 @@ export const ServiceDetailContainer = () => { setUptimeData }); - // Handle date range filter changes const handleDateRangeChange = useCallback((start: Date, end: Date, option: DateRangeOption) => { - console.log(`ServiceDetailContainer: Date range changed: ${start.toISOString()} to ${end.toISOString()}, option: ${option}`); - - // Update state which will trigger the useEffect in useServiceData setStartDate(start); setEndDate(end); setSelectedRange(option); - // Also explicitly fetch data with the new range to ensure immediate update if (id) { - console.log(`ServiceDetailContainer: Explicitly fetching data for service ${id} with new range`); fetchUptimeData(id, start, end, option, selectedRegionalAgent); } }, [id, fetchUptimeData, selectedRegionalAgent]); @@ -114,12 +63,10 @@ export const ServiceDetailContainer = () => { - {service && ( + {service ? ( { selectedRegionalAgent={selectedRegionalAgent} onRegionalAgentChange={handleRegionalAgentChange} /> - )} + ) : null} ); }; \ No newline at end of file diff --git a/application/src/contexts/SidebarContext.tsx b/application/src/contexts/SidebarContext.tsx index cfab360..2e695b1 100644 --- a/application/src/contexts/SidebarContext.tsx +++ b/application/src/contexts/SidebarContext.tsx @@ -1,25 +1,64 @@ - -import React, { createContext, useContext, useState } from 'react'; +import React, { createContext, useContext, useState, useEffect } from 'react'; interface SidebarContextType { sidebarCollapsed: boolean; setSidebarCollapsed: (collapsed: boolean) => void; toggleSidebar: () => void; + isMobileOpen: boolean; + setIsMobileOpen: (open: boolean) => void; + toggleMobileMenu: () => void; } const SidebarContext = createContext(undefined); -export const SidebarProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [sidebarCollapsed, setSidebarCollapsed] = useState(false); +export const SidebarProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { + // Initialize sidebar collapsed state from localStorage with window check for safety + const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { + if (typeof window !== 'undefined') { + const saved = localStorage.getItem("sidebarCollapsed"); + return saved ? JSON.parse(saved) : false; + } + return false; + }); + + // Fix: added isMobileOpen state for responsive mobile menu + const [isMobileOpen, setIsMobileOpen] = useState(false); const toggleSidebar = () => { - setSidebarCollapsed(prev => !prev); + setSidebarCollapsed(prev => { + const newState = !prev; + if (typeof window !== 'undefined') { + localStorage.setItem("sidebarCollapsed", JSON.stringify(newState)); + } + return newState; + }); }; + // Fix: added toggleMobileMenu function to manage mobile drawer state + const toggleMobileMenu = () => { + setIsMobileOpen(prev => !prev); + }; + + // Fix: added resize listener to close mobile menu when switching to desktop view + useEffect(() => { + if (typeof window === 'undefined') return; + + const handleResize = () => { + if (window.innerWidth >= 1024) { + setIsMobileOpen(false); + } + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + const value = { sidebarCollapsed, setSidebarCollapsed, - toggleSidebar + toggleSidebar, + isMobileOpen, + setIsMobileOpen, + toggleMobileMenu }; return ( @@ -35,4 +74,4 @@ export const useSidebar = () => { throw new Error('useSidebar must be used within a SidebarProvider'); } return context; -}; \ No newline at end of file +}; diff --git a/application/src/pages/Dashboard.tsx b/application/src/pages/Dashboard.tsx index 2ab2c52..65ab85f 100644 --- a/application/src/pages/Dashboard.tsx +++ b/application/src/pages/Dashboard.tsx @@ -1,4 +1,3 @@ - import React, { useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import { Header } from "@/components/dashboard/Header"; @@ -11,72 +10,39 @@ import { LoadingState } from "@/components/services/LoadingState"; import { useSidebar } from "@/contexts/SidebarContext"; const Dashboard = () => { - // Use shared sidebar state - const { sidebarCollapsed, toggleSidebar } = useSidebar(); - - // Get current user const currentUser = authService.getCurrentUser(); const navigate = useNavigate(); - // For debugging user data - useEffect(() => { - // console.log("Current user data:", currentUser); - }, [currentUser]); - - // Handle logout const handleLogout = () => { authService.logout(); navigate("/login"); }; - // Fetch all services with 1-minute polling for real-time updates const { data: services = [], isLoading, error } = useQuery({ queryKey: ['services'], queryFn: serviceService.getServices, - refetchInterval: 60000, // 1 minute as requested - staleTime: 30000, // Data is fresh for 30 seconds - gcTime: 120000, // Keep in cache for 2 minutes - refetchOnWindowFocus: true, // Refetch when window gains focus - refetchOnMount: true, // Refetch on mount - refetchOnReconnect: true, // Refetch on reconnect - retry: 2, - retryDelay: 3000, + refetchInterval: 60000, }); - // Start monitoring all active services when the dashboard loads - only once useEffect(() => { - let hasStarted = false; - const startActiveServices = async () => { - if (hasStarted) return; - hasStarted = true; - await serviceService.startAllActiveServices(); - // console.log("Active services monitoring started"); }; - - // Only start once and add a delay to prevent immediate execution const timeoutId = setTimeout(startActiveServices, 2000); - - return () => { - clearTimeout(timeoutId); - }; - }, []); // Remove services dependency to prevent re-runs + return () => clearTimeout(timeoutId); + }, []); - // Show the loading state while fetching data if (isLoading) { return ; } return (
- -
+ +
{ - const { theme } = useTheme(); const { t } = useLanguage(); - const { sidebarCollapsed, toggleSidebar } = useSidebar(); const navigate = useNavigate(); const [stats, setStats] = useState({ @@ -29,13 +25,13 @@ const InstanceMonitoring = () => { warning: 0 }); - const [currentUser, setCurrentUser] = useState(authService.getCurrentUser()); + const [currentUser] = useState(authService.getCurrentUser()); const [addDialogOpen, setAddDialogOpen] = useState(false); - const { data: servers = [], isLoading, error, refetch } = useQuery({ + const { data: servers = [], isLoading, error, refetch } = useQuery({ queryKey: ['servers'], queryFn: serverService.getServers, - refetchInterval: 30000 // Refetch every 30 seconds + refetchInterval: 30000 }); useEffect(() => { @@ -44,88 +40,38 @@ const InstanceMonitoring = () => { } }, [servers]); - const handleRefresh = () => { - refetch(); - }; - const handleLogout = () => { authService.logout(); navigate('/login'); }; - const handleAgentAdded = () => { - refetch(); - }; - - if (error) { - return ( -
- -
-
-
-
-

{t('errorLoadingServers')}

-

- {t('unableToFetchServerData')} -

- -
-
-
-
- ); - } - return (
- +
-
- {/* Header Section */} -
-
-
-

- {t('instanceMonitoring')} -

-

- {t('describeMonitorInstance')} -

-
- +
+
+
+

+ {t('instanceMonitoring')} +

+

+ {t('describeMonitorInstance')} +

+
- {/* Stats Cards Section */} -
- -
- - {/* Server Table Section */} -
- -
+ +
@@ -133,7 +79,7 @@ const InstanceMonitoring = () => {
);