From 10427342789712115196fecf0b9cf344d8907ffa Mon Sep 17 00:00:00 2001 From: Desktop Date: Wed, 12 Nov 2025 22:44:49 +0500 Subject: [PATCH] asd --- .../common/SiteAndSectorSelector.tsx | 278 ++++++++++++++++++ frontend/src/pages/Planner/Dashboard.tsx | 4 +- frontend/src/pages/Writer/Dashboard.tsx | 4 +- 3 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/common/SiteAndSectorSelector.tsx diff --git a/frontend/src/components/common/SiteAndSectorSelector.tsx b/frontend/src/components/common/SiteAndSectorSelector.tsx new file mode 100644 index 00000000..3a245ff8 --- /dev/null +++ b/frontend/src/components/common/SiteAndSectorSelector.tsx @@ -0,0 +1,278 @@ +/** + * Combined Site and Sector Selector Component + * Displays both site switcher and sector selector side by side with accent colors + */ +import { useState, useEffect, useRef } from 'react'; +import { Dropdown } from '../ui/dropdown/Dropdown'; +import { DropdownItem } from '../ui/dropdown/DropdownItem'; +import { fetchSites, Site, setActiveSite as apiSetActiveSite } from '../../services/api'; +import { useToast } from '../ui/toast/ToastContainer'; +import { useSiteStore } from '../../store/siteStore'; +import { useSectorStore } from '../../store/sectorStore'; +import { useAuthStore } from '../../store/authStore'; + +export default function SiteAndSectorSelector() { + const toast = useToast(); + const { activeSite, setActiveSite, loadActiveSite } = useSiteStore(); + const { activeSector, sectors, setActiveSector, loading: sectorsLoading } = useSectorStore(); + const { user, refreshUser, isAuthenticated } = useAuthStore(); + + // Site switcher state + const [sitesOpen, setSitesOpen] = useState(false); + const [sites, setSites] = useState([]); + const [sitesLoading, setSitesLoading] = useState(true); + const siteButtonRef = useRef(null); + + // Sector selector state + const [sectorsOpen, setSectorsOpen] = useState(false); + const sectorButtonRef = useRef(null); + + // Load sites + useEffect(() => { + if (isAuthenticated && user) { + refreshUser().catch((error) => { + console.debug('SiteAndSectorSelector: Failed to refresh user (non-critical):', error); + }); + } + }, [isAuthenticated]); + + useEffect(() => { + loadSites(); + if (!activeSite) { + loadActiveSite(); + } + }, [user?.account?.id]); + + const loadSites = async () => { + try { + setSitesLoading(true); + const response = await fetchSites(); + const activeSites = (response.results || []).filter(site => site.is_active); + setSites(activeSites); + } catch (error: any) { + console.error('Failed to load sites:', error); + toast.error(`Failed to load sites: ${error.message}`); + } finally { + setSitesLoading(false); + } + }; + + const handleSiteSelect = async (siteId: number) => { + try { + await apiSetActiveSite(siteId); + const selectedSite = sites.find(s => s.id === siteId); + if (selectedSite) { + setActiveSite(selectedSite); + toast.success(`Switched to "${selectedSite.name}"`); + } + setSitesOpen(false); + } catch (error: any) { + toast.error(`Failed to switch site: ${error.message}`); + } + }; + + const handleSectorSelect = (sectorId: number | null) => { + if (sectorId === null) { + setActiveSector(null); + setSectorsOpen(false); + } else { + const sector = sectors.find(s => s.id === sectorId); + if (sector) { + setActiveSector(sector); + setSectorsOpen(false); + } + } + }; + + // Don't render if no active site + if (!activeSite) { + return null; + } + + return ( +
+ {/* Site Switcher */} +
+ + + setSitesOpen(false)} + anchorRef={siteButtonRef} + placement="bottom-left" + className="w-64 p-2" + > + {sites.map((site) => ( + handleSiteSelect(site.id)} + className={`flex items-center gap-3 px-3 py-2 font-medium rounded-lg text-sm text-left ${ + activeSite?.id === site.id + ? "bg-brand-50 text-brand-700 dark:bg-brand-500/20 dark:text-brand-300" + : "text-gray-700 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300" + }`} + > + {site.name} + {activeSite?.id === site.id && ( + + + + )} + + ))} + +
+ + {/* Sector Selector */} + {!sectorsLoading && sectors.length > 0 && ( +
+ + + setSectorsOpen(false)} + anchorRef={sectorButtonRef} + placement="bottom-right" + className="w-64 p-2 overflow-y-auto max-h-[300px]" + > + {/* "All Sectors" option */} + handleSectorSelect(null)} + className={`flex items-center gap-3 px-3 py-2 font-medium rounded-lg text-sm text-left ${ + !activeSector + ? "bg-brand-50 text-brand-700 dark:bg-brand-500/20 dark:text-brand-300" + : "text-gray-700 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300" + }`} + > + All Sectors + {!activeSector && ( + + + + )} + + {sectors.map((sector) => ( + handleSectorSelect(sector.id)} + className={`flex items-center gap-3 px-3 py-2 font-medium rounded-lg text-sm text-left ${ + activeSector?.id === sector.id + ? "bg-brand-50 text-brand-700 dark:bg-brand-500/20 dark:text-brand-300" + : "text-gray-700 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300" + }`} + > + {sector.name} + {activeSector?.id === sector.id && ( + + + + )} + + ))} + +
+ )} +
+ ); +} + diff --git a/frontend/src/pages/Planner/Dashboard.tsx b/frontend/src/pages/Planner/Dashboard.tsx index b8e6851e..95aa30fb 100644 --- a/frontend/src/pages/Planner/Dashboard.tsx +++ b/frontend/src/pages/Planner/Dashboard.tsx @@ -27,7 +27,7 @@ import { } from "../../services/api"; import { useSiteStore } from "../../store/siteStore"; import { useSectorStore } from "../../store/sectorStore"; -import SectorSelector from "../../components/common/SectorSelector"; +import SiteAndSectorSelector from "../../components/common/SiteAndSectorSelector"; interface DashboardStats { keywords: { @@ -502,7 +502,7 @@ export default function PlannerDashboard() {
- +
- +