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 (
-
-
-
+
);
}
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;
+}