diff options
Diffstat (limited to 'src/frontend/app/AppContext.tsx')
| -rw-r--r-- | src/frontend/app/AppContext.tsx | 318 |
1 files changed, 24 insertions, 294 deletions
diff --git a/src/frontend/app/AppContext.tsx b/src/frontend/app/AppContext.tsx index 9c2521f..59f2724 100644 --- a/src/frontend/app/AppContext.tsx +++ b/src/frontend/app/AppContext.tsx @@ -1,304 +1,34 @@ /* eslint-disable react-refresh/only-export-components */ +import { type ReactNode } from "react"; +import { type RegionId } from "./config/RegionConfig"; +import { MapProvider, useMap } from "./contexts/MapContext"; import { - createContext, - useContext, - useEffect, - useState, - type ReactNode, -} from "react"; -import { type LngLatLike } from "maplibre-gl"; -import { - type RegionId, - DEFAULT_REGION, - getRegionConfig, - isValidRegion, - REGIONS, -} from "./data/RegionConfig"; - -export type Theme = "light" | "dark" | "system"; -type TableStyle = "regular" | "grouped" | "experimental_consolidated"; -type MapPositionMode = "gps" | "last"; - -interface MapState { - center: LngLatLike; - zoom: number; - userLocation: LngLatLike | null; - hasLocationPermission: boolean; -} - -interface AppContextProps { - theme: Theme; - setTheme: React.Dispatch<React.SetStateAction<Theme>>; - toggleTheme: () => void; - - tableStyle: TableStyle; - setTableStyle: React.Dispatch<React.SetStateAction<TableStyle>>; - toggleTableStyle: () => void; - - mapState: MapState; - setMapCenter: (center: LngLatLike) => void; - setMapZoom: (zoom: number) => void; - setUserLocation: (location: LngLatLike | null) => void; - setLocationPermission: (hasPermission: boolean) => void; - updateMapState: (center: LngLatLike, zoom: number) => void; - - mapPositionMode: MapPositionMode; - setMapPositionMode: (mode: MapPositionMode) => void; - - region: RegionId; - setRegion: (region: RegionId) => void; -} - -const AppContext = createContext<AppContextProps | undefined>(undefined); - -export const AppProvider = ({ children }: { children: ReactNode }) => { - //#region Theme - const getPreferredScheme = () => { - if (typeof window === "undefined" || !window.matchMedia) { - return "light" as const; - } - return window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; - }; - - const [systemTheme, setSystemTheme] = useState<"light" | "dark">( - getPreferredScheme - ); - - const [theme, setTheme] = useState<Theme>(() => { - const savedTheme = localStorage.getItem("theme"); - if ( - savedTheme === "light" || - savedTheme === "dark" || - savedTheme === "system" - ) { - return savedTheme; - } - return "system"; - }); - - useEffect(() => { - if (typeof window === "undefined" || !window.matchMedia) { - return; - } - - const media = window.matchMedia("(prefers-color-scheme: dark)"); - const handleChange = (event: MediaQueryListEvent) => { - setSystemTheme(event.matches ? "dark" : "light"); - }; - - // Sync immediately in case theme changed before subscription - setSystemTheme(media.matches ? "dark" : "light"); - - if (media.addEventListener) { - media.addEventListener("change", handleChange); - } else { - media.addListener(handleChange); - } - - return () => { - if (media.removeEventListener) { - media.removeEventListener("change", handleChange); - } else { - media.removeListener(handleChange); - } - }; - }, []); + SettingsProvider, + useSettings, + type MapPositionMode, + type TableStyle, + type Theme, +} from "./contexts/SettingsContext"; - const resolvedTheme = theme === "system" ? systemTheme : theme; +// Re-export types for compatibility +export type { MapPositionMode, RegionId, TableStyle, Theme }; - const toggleTheme = () => { - setTheme((prevTheme) => { - if (prevTheme === "light") { - return "dark"; - } - if (prevTheme === "dark") { - return "system"; - } - return "light"; - }); - }; - - useEffect(() => { - document.documentElement.setAttribute("data-theme", resolvedTheme); - document.documentElement.style.colorScheme = resolvedTheme; - }, [resolvedTheme]); - - useEffect(() => { - localStorage.setItem("theme", theme); - }, [theme]); - //#endregion - - //#region Table Style - const [tableStyle, setTableStyle] = useState<TableStyle>(() => { - const savedTableStyle = localStorage.getItem("tableStyle"); - if (savedTableStyle) { - return savedTableStyle as TableStyle; - } - return "regular"; - }); - - const toggleTableStyle = () => { - setTableStyle((prevTableStyle) => - prevTableStyle === "regular" ? "grouped" : "regular" - ); - }; - - useEffect(() => { - localStorage.setItem("tableStyle", tableStyle); - }, [tableStyle]); - //#endregion - - //#region Map Position Mode - const [mapPositionMode, setMapPositionMode] = useState<MapPositionMode>( - () => { - const saved = localStorage.getItem("mapPositionMode"); - return saved === "last" ? "last" : "gps"; - } - ); - - useEffect(() => { - localStorage.setItem("mapPositionMode", mapPositionMode); - }, [mapPositionMode]); - //#endregion - - //#region Region - const [region, setRegionState] = useState<RegionId>(() => { - const savedRegion = localStorage.getItem("region"); - if (savedRegion && isValidRegion(savedRegion)) { - return savedRegion; - } - return DEFAULT_REGION; - }); - - const setRegion = (newRegion: RegionId) => { - setRegionState(newRegion); - localStorage.setItem("region", newRegion); - - // Update map to region's default center and zoom - const regionConfig = getRegionConfig(newRegion); - updateMapState(regionConfig.defaultCenter, regionConfig.defaultZoom); - }; - - useEffect(() => { - localStorage.setItem("region", region); - }, [region]); - //#endregion - - //#region Map State - const [mapState, setMapState] = useState<MapState>(() => { - const savedMapState = localStorage.getItem("mapState"); - if (savedMapState) { - try { - const parsed = JSON.parse(savedMapState); - return { - center: parsed.center || REGIONS[region].defaultCenter, - zoom: parsed.zoom || REGIONS[region].defaultZoom, - userLocation: parsed.userLocation || null, - hasLocationPermission: parsed.hasLocationPermission || false, - }; - } catch (e) { - console.error("Error parsing saved map state", e); - } - } - return { - center: REGIONS[region].defaultCenter, - zoom: REGIONS[region].defaultZoom, - userLocation: null, - hasLocationPermission: false, - }; - }); - - const setMapCenter = (center: LngLatLike) => { - setMapState((prev) => { - const newState = { ...prev, center }; - localStorage.setItem("mapState", JSON.stringify(newState)); - return newState; - }); - }; - - const setMapZoom = (zoom: number) => { - setMapState((prev) => { - const newState = { ...prev, zoom }; - localStorage.setItem("mapState", JSON.stringify(newState)); - return newState; - }); - }; - - const setUserLocation = (userLocation: LngLatLike | null) => { - setMapState((prev) => { - const newState = { ...prev, userLocation }; - localStorage.setItem("mapState", JSON.stringify(newState)); - return newState; - }); - }; - - const setLocationPermission = (hasLocationPermission: boolean) => { - setMapState((prev) => { - const newState = { ...prev, hasLocationPermission }; - localStorage.setItem("mapState", JSON.stringify(newState)); - return newState; - }); - }; +// Combined hook for backward compatibility +export const useApp = () => { + const settings = useSettings(); + const map = useMap(); - const updateMapState = (center: LngLatLike, zoom: number) => { - setMapState((prev) => { - const newState = { ...prev, center, zoom }; - localStorage.setItem("mapState", JSON.stringify(newState)); - return newState; - }); + return { + ...settings, + ...map, }; - //#endregion - - // Tratar de obtener la ubicación del usuario cuando se carga la aplicación si ya se había concedido permiso antes - useEffect(() => { - if (mapState.hasLocationPermission && !mapState.userLocation) { - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition( - (position) => { - const { latitude, longitude } = position.coords; - setUserLocation([latitude, longitude]); - }, - (error) => { - console.error("Error getting location:", error); - setLocationPermission(false); - } - ); - } - } - }, [mapState.hasLocationPermission, mapState.userLocation]); +}; +// Wrapper provider +export const AppProvider = ({ children }: { children: ReactNode }) => { return ( - <AppContext.Provider - value={{ - theme, - setTheme, - toggleTheme, - tableStyle, - setTableStyle, - toggleTableStyle, - mapState, - setMapCenter, - setMapZoom, - setUserLocation, - setLocationPermission, - updateMapState, - mapPositionMode, - setMapPositionMode, - region, - setRegion, - }} - > - {children} - </AppContext.Provider> + <SettingsProvider> + <MapProvider>{children}</MapProvider> + </SettingsProvider> ); }; - -export const useApp = () => { - const context = useContext(AppContext); - if (!context) { - throw new Error("useApp must be used within a AppProvider"); - } - return context; -}; |
