From 242b8bad3d54fa42be07dc65973ce7b1f997d281 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 1 Feb 2026 08:41:02 +0000 Subject: [PATCH] fix: dashboard updates when switching sites - Added SiteContext for global site selection state - Updated admin layout with SiteProvider - Updated SiteSwitcher to use shared context - Dashboard now refetches data when site changes Co-Authored-By: Claude Opus 4.5 --- apps/web/app/(admin)/dashboard/page.tsx | 54 +++++++++------- apps/web/app/(admin)/layout.tsx | 15 +++-- apps/web/components/layout/site-switcher.tsx | 29 +-------- apps/web/contexts/site-context.tsx | 66 ++++++++++++++++++++ 4 files changed, 107 insertions(+), 57 deletions(-) create mode 100644 apps/web/contexts/site-context.tsx diff --git a/apps/web/app/(admin)/dashboard/page.tsx b/apps/web/app/(admin)/dashboard/page.tsx index 83c2a6e..85a54d3 100644 --- a/apps/web/app/(admin)/dashboard/page.tsx +++ b/apps/web/app/(admin)/dashboard/page.tsx @@ -1,12 +1,13 @@ "use client"; import { useSession } from "next-auth/react"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; import { formatCurrency, formatDate } from "@/lib/utils"; import { StatCard, StatCardSkeleton } from "@/components/dashboard/stat-card"; import { OccupancyChart, OccupancyChartSkeleton } from "@/components/dashboard/occupancy-chart"; import { RecentBookings, RecentBookingsSkeleton } from "@/components/dashboard/recent-bookings"; import { QuickActions } from "@/components/dashboard/quick-actions"; +import { useSite } from "@/contexts/site-context"; interface DashboardStats { todayBookings: number; @@ -49,34 +50,39 @@ interface DashboardData { export default function DashboardPage() { const { data: session } = useSession(); + const { selectedSiteId, selectedSite } = useSite(); const [dashboardData, setDashboardData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - useEffect(() => { - async function fetchDashboardData() { - try { - setIsLoading(true); - setError(null); + const fetchDashboardData = useCallback(async () => { + try { + setIsLoading(true); + setError(null); - const response = await fetch("/api/dashboard/stats"); + const url = selectedSiteId + ? `/api/dashboard/stats?siteId=${selectedSiteId}` + : "/api/dashboard/stats"; - if (!response.ok) { - throw new Error("Error al cargar los datos del dashboard"); - } + const response = await fetch(url); - const data = await response.json(); - setDashboardData(data); - } catch (err) { - console.error("Dashboard fetch error:", err); - setError(err instanceof Error ? err.message : "Error desconocido"); - } finally { - setIsLoading(false); + if (!response.ok) { + throw new Error("Error al cargar los datos del dashboard"); } - } + const data = await response.json(); + setDashboardData(data); + } catch (err) { + console.error("Dashboard fetch error:", err); + setError(err instanceof Error ? err.message : "Error desconocido"); + } finally { + setIsLoading(false); + } + }, [selectedSiteId]); + + useEffect(() => { fetchDashboardData(); - }, []); + }, [fetchDashboardData]); const userName = session?.user?.name?.split(" ")[0] || "Usuario"; const today = new Date(); @@ -93,10 +99,10 @@ export default function DashboardPage() { {formatDate(today)} - Panel de administracion

- {session?.user?.siteName && ( -
+ {selectedSite && ( +
- - {session.user.siteName} + + Mostrando: {selectedSite.name}
)} diff --git a/apps/web/app/(admin)/layout.tsx b/apps/web/app/(admin)/layout.tsx index 7da2c19..07c2090 100644 --- a/apps/web/app/(admin)/layout.tsx +++ b/apps/web/app/(admin)/layout.tsx @@ -1,4 +1,5 @@ import { AuthProvider } from '@/components/providers/auth-provider'; +import { SiteProvider } from '@/contexts/site-context'; import { Sidebar } from '@/components/layout/sidebar'; import { Header } from '@/components/layout/header'; @@ -9,13 +10,15 @@ export default function AdminLayout({ }) { return ( -
- -
-
-
{children}
+ +
+ +
+
+
{children}
+
-
+ ); } diff --git a/apps/web/components/layout/site-switcher.tsx b/apps/web/components/layout/site-switcher.tsx index f18e3b7..9ebb90a 100644 --- a/apps/web/components/layout/site-switcher.tsx +++ b/apps/web/components/layout/site-switcher.tsx @@ -4,40 +4,16 @@ import { useState, useEffect, useRef } from 'react'; import { useSession } from 'next-auth/react'; import { MapPin, ChevronDown, Check } from 'lucide-react'; import { cn } from '@/lib/utils'; - -interface Site { - id: string; - name: string; -} +import { useSite } from '@/contexts/site-context'; export function SiteSwitcher() { const { data: session } = useSession(); - const [sites, setSites] = useState([]); - const [selectedSiteId, setSelectedSiteId] = useState(null); + const { sites, selectedSiteId, selectedSite, setSelectedSiteId, isLoading } = useSite(); const [isOpen, setIsOpen] = useState(false); - const [isLoading, setIsLoading] = useState(true); const dropdownRef = useRef(null); const isSuperAdmin = session?.user?.role === 'SUPER_ADMIN'; - useEffect(() => { - async function fetchSites() { - try { - const response = await fetch('/api/sites'); - if (response.ok) { - const data = await response.json(); - setSites(data.data || []); - } - } catch (error) { - console.error('Failed to fetch sites:', error); - } finally { - setIsLoading(false); - } - } - - fetchSites(); - }, []); - useEffect(() => { function handleClickOutside(event: MouseEvent) { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { @@ -49,7 +25,6 @@ export function SiteSwitcher() { return () => document.removeEventListener('mousedown', handleClickOutside); }, []); - const selectedSite = sites.find((site) => site.id === selectedSiteId); const displayName = selectedSiteId ? selectedSite?.name : 'Todas las sedes'; // For non-SUPER_ADMIN users, just show their assigned site diff --git a/apps/web/contexts/site-context.tsx b/apps/web/contexts/site-context.tsx new file mode 100644 index 0000000..80d70b6 --- /dev/null +++ b/apps/web/contexts/site-context.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { createContext, useContext, useState, useEffect, ReactNode } from "react"; + +interface Site { + id: string; + name: string; +} + +interface SiteContextType { + sites: Site[]; + selectedSiteId: string | null; + selectedSite: Site | null; + setSelectedSiteId: (siteId: string | null) => void; + isLoading: boolean; +} + +const SiteContext = createContext(undefined); + +export function SiteProvider({ children }: { children: ReactNode }) { + const [sites, setSites] = useState([]); + const [selectedSiteId, setSelectedSiteId] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + async function fetchSites() { + try { + const response = await fetch("/api/sites"); + if (response.ok) { + const data = await response.json(); + setSites(data.data || []); + } + } catch (error) { + console.error("Failed to fetch sites:", error); + } finally { + setIsLoading(false); + } + } + + fetchSites(); + }, []); + + const selectedSite = sites.find((site) => site.id === selectedSiteId) || null; + + return ( + + {children} + + ); +} + +export function useSite() { + const context = useContext(SiteContext); + if (context === undefined) { + throw new Error("useSite must be used within a SiteProvider"); + } + return context; +}