diff --git a/components/dashboard/dashboard-page.tsx b/components/dashboard/dashboard-page.tsx index 063fdc5..eddbbdc 100644 --- a/components/dashboard/dashboard-page.tsx +++ b/components/dashboard/dashboard-page.tsx @@ -26,6 +26,7 @@ export function DashboardPage() { const { data: positions = [] } = useMeteoStationPositions(); const selectedStationId = useWeatherStore((state) => state.selectedStationId); const selectedLocation = useWeatherStore((state) => state.selectedLocation); + const dashboardSections = useWeatherStore((state) => state.dashboardSections); const selectedStation = stations?.find((station) => station.id === selectedStationId) ?? stations?.find((station) => station.name === DEFAULT_STATION_NAME) ?? stations?.[0]; @@ -62,10 +63,10 @@ export function DashboardPage() { - + {dashboardSections.weatherBrief && } - + {dashboardSections.featuredStations && } ); } diff --git a/components/settings/settings-page.tsx b/components/settings/settings-page.tsx index 16333b4..cc288e4 100644 --- a/components/settings/settings-page.tsx +++ b/components/settings/settings-page.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import { Bell, BellRing, ChevronDown, Clock3, Languages, MapPinned, Palette, Settings, Smartphone, Thermometer, Wind } from "lucide-react"; +import { Bell, BellRing, ChevronDown, Clock3, Languages, LayoutDashboard, MapPinned, Palette, Settings, Smartphone, Thermometer, Wind } from "lucide-react"; import type { LucideIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; @@ -10,6 +10,7 @@ import { ThemeToggle } from "@/components/ui/theme-toggle"; import { useMeteoStationPositions } from "@/hooks/use-meteo-stations"; import { useWeatherStations } from "@/hooks/use-weather-stations"; import { DEFAULT_STATION_ID } from "@/lib/constants"; +import { DASHBOARD_SECTION_SETTINGS } from "@/lib/dashboard-sections"; import { useI18n } from "@/lib/i18n"; import { locateSynopStations } from "@/lib/location-utils"; import { decodeBase64UrlKey, deletePushSubscription, fetchVapidPublicKey, savePushSubscription, sendTestPushNotification } from "@/lib/notification-api"; @@ -131,6 +132,7 @@ export function SettingsPage() { const manualProvince = useWeatherStore((state) => state.warningNotificationProvince); const temperatureUnit = useWeatherStore((state) => state.temperatureUnit); const windSpeedUnit = useWeatherStore((state) => state.windSpeedUnit); + const dashboardSections = useWeatherStore((state) => state.dashboardSections); const setNotificationsEnabled = useWeatherStore((state) => state.setWarningNotificationsEnabled); const setMorningBriefEnabled = useWeatherStore((state) => state.setMorningBriefNotificationsEnabled); const setTomorrowBriefEnabled = useWeatherStore((state) => state.setTomorrowBriefNotificationsEnabled); @@ -138,6 +140,7 @@ export function SettingsPage() { const setManualProvince = useWeatherStore((state) => state.setWarningNotificationProvince); const setTemperatureUnit = useWeatherStore((state) => state.setTemperatureUnit); const setWindSpeedUnit = useWeatherStore((state) => state.setWindSpeedUnit); + const setDashboardSectionVisible = useWeatherStore((state) => state.setDashboardSectionVisible); const selectedStation = stations?.find((station) => station.id === selectedStationId) ?? stations?.find((station) => station.id === DEFAULT_STATION_ID) @@ -435,6 +438,31 @@ export function SettingsPage() { + +
+
+
+
+

{t("settings.dashboardSections.title")}

+

{t("settings.dashboardSections.description")}

+
+
+ +
+ {DASHBOARD_SECTION_SETTINGS.map((section) => ( + setDashboardSectionVisible(section.id, checked)} + /> + ))} +
+
+
diff --git a/docs/architecture.md b/docs/architecture.md index 884962a..84dc0d3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -22,7 +22,7 @@ Najważniejsze widoki: - `/` - dashboard pogody, wyszukiwarka lokalizacji, hero, prognoza, briefy i ostrzeżenia regionalne, - `/warnings` - pełny widok ostrzeżeń meteo i hydro, - `/hydro` - stacje hydrologiczne, -- `/settings` - język, motyw, lokalizacja powiadomień i Web Push, +- `/settings` - język, motyw, widoczność sekcji dashboardu, lokalizacja powiadomień i Web Push, - `/station/[id]` - szczegóły stacji, - `/offline` - fallback offline. @@ -42,7 +42,8 @@ Zustand w `lib/store.ts` przechowuje trwałe preferencje użytkownika w `localSt - ulubione stacje, - wybraną stację albo lokalizację, - ustawienia powiadomień, -- tryb wyboru województwa dla alertów. +- tryb wyboru województwa dla alertów, +- widoczność opcjonalnych sekcji dashboardu. Język interfejsu jest przechowywany osobno w `localStorage` pod kluczem `wtr:language`. diff --git a/lib/dashboard-sections.ts b/lib/dashboard-sections.ts new file mode 100644 index 0000000..4758d1e --- /dev/null +++ b/lib/dashboard-sections.ts @@ -0,0 +1,38 @@ +import type { TranslationKey } from "@/lib/i18n"; + +export const DASHBOARD_SECTION_SETTINGS = [ + { + id: "weatherBrief", + titleKey: "settings.dashboardSections.weatherBrief.title", + descriptionKey: "settings.dashboardSections.weatherBrief.description", + defaultVisible: true, + }, + { + id: "featuredStations", + titleKey: "settings.dashboardSections.featuredStations.title", + descriptionKey: "settings.dashboardSections.featuredStations.description", + defaultVisible: false, + }, +] as const satisfies ReadonlyArray<{ + id: string; + titleKey: TranslationKey; + descriptionKey: TranslationKey; + defaultVisible: boolean; +}>; + +export type DashboardSectionId = typeof DASHBOARD_SECTION_SETTINGS[number]["id"]; + +export type DashboardSectionVisibility = Record; + +export const DEFAULT_DASHBOARD_SECTION_VISIBILITY = DASHBOARD_SECTION_SETTINGS.reduce((visibility, section) => ({ + ...visibility, + [section.id]: section.defaultVisible, +}), {} as DashboardSectionVisibility); + +export function normalizeDashboardSectionVisibility(value: unknown): DashboardSectionVisibility { + const stored = value && typeof value === "object" ? value as Partial> : {}; + return DASHBOARD_SECTION_SETTINGS.reduce((visibility, section) => ({ + ...visibility, + [section.id]: typeof stored[section.id] === "boolean" ? stored[section.id] : section.defaultVisible, + }), {} as DashboardSectionVisibility); +} diff --git a/lib/i18n.tsx b/lib/i18n.tsx index 92b9006..6b2e2d9 100644 --- a/lib/i18n.tsx +++ b/lib/i18n.tsx @@ -26,6 +26,12 @@ const translations = { "settings.language.description": "Zmieniaj język interfejsu. Nazwy stacji i treści IMGW pozostają bez automatycznego tłumaczenia.", "settings.theme.title": "Motyw", "settings.theme.description": "Przełącz jasny lub ciemny wygląd aplikacji na tym urządzeniu.", + "settings.dashboardSections.title": "Strona główna", + "settings.dashboardSections.description": "Wybierz, które dodatkowe sekcje mają być widoczne na dashboardzie.", + "settings.dashboardSections.weatherBrief.title": "Brief dnia", + "settings.dashboardSections.weatherBrief.description": "Pokazuje deterministyczne podsumowanie dnia i krótką prognozę na jutro.", + "settings.dashboardSections.featuredStations.title": "Szybki wybór", + "settings.dashboardSections.featuredStations.description": "Pokazuje listę popularnych lokalizacji na dole strony głównej.", "settings.units.temperature.title": "Jednostka temperatury", "settings.units.temperature.description": "Stosowana w prognozie, briefach, powiadomieniach i bieżących pomiarach na tym urządzeniu.", "settings.units.temperature.label": "Wybierz jednostkę temperatury", @@ -283,6 +289,12 @@ const translations = { "settings.language.description": "Change the interface language. Station names and IMGW content are not translated automatically.", "settings.theme.title": "Theme", "settings.theme.description": "Switch the light or dark appearance on this device.", + "settings.dashboardSections.title": "Home screen", + "settings.dashboardSections.description": "Choose which additional sections are visible on the dashboard.", + "settings.dashboardSections.weatherBrief.title": "Daily brief", + "settings.dashboardSections.weatherBrief.description": "Shows the deterministic daily summary and a short forecast for tomorrow.", + "settings.dashboardSections.featuredStations.title": "Quick select", + "settings.dashboardSections.featuredStations.description": "Shows the popular locations list at the bottom of the home screen.", "settings.units.temperature.title": "Temperature unit", "settings.units.temperature.description": "Used in forecasts, briefs, notifications and current readings on this device.", "settings.units.temperature.label": "Choose temperature unit", diff --git a/lib/store.ts b/lib/store.ts index 9930ce2..b78dc13 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -5,6 +5,7 @@ import { persist } from "zustand/middleware"; import type { SelectedLocation } from "@/types/location"; import type { Province } from "@/types/province"; import type { TemperatureUnit, WindSpeedUnit } from "@/types/units"; +import { DEFAULT_DASHBOARD_SECTION_VISIBILITY, normalizeDashboardSectionVisibility, type DashboardSectionId, type DashboardSectionVisibility } from "@/lib/dashboard-sections"; import { DEFAULT_TEMPERATURE_UNIT, DEFAULT_WIND_SPEED_UNIT } from "@/lib/weather-utils"; import type { WeatherRegion } from "@/types/weather-region"; import { getWeatherRegionForCountryCode } from "@/types/weather-region"; @@ -22,6 +23,7 @@ interface WeatherStore { warningNotificationProvince: Province | null; temperatureUnit: TemperatureUnit; windSpeedUnit: WindSpeedUnit; + dashboardSections: DashboardSectionVisibility; toggleFavorite: (id: string) => void; selectStation: (id: string) => void; selectLocation: (location: SelectedLocation) => void; @@ -32,6 +34,7 @@ interface WeatherStore { setWarningNotificationProvince: (province: Province | null) => void; setTemperatureUnit: (unit: TemperatureUnit) => void; setWindSpeedUnit: (unit: WindSpeedUnit) => void; + setDashboardSectionVisible: (section: DashboardSectionId, visible: boolean) => void; } export const useWeatherStore = create()( @@ -47,6 +50,7 @@ export const useWeatherStore = create()( warningNotificationProvince: null, temperatureUnit: DEFAULT_TEMPERATURE_UNIT, windSpeedUnit: DEFAULT_WIND_SPEED_UNIT, + dashboardSections: DEFAULT_DASHBOARD_SECTION_VISIBILITY, toggleFavorite: (id) => set((state) => ({ favorites: state.favorites.includes(id) @@ -62,17 +66,23 @@ export const useWeatherStore = create()( setWarningNotificationProvince: (province) => set({ warningNotificationProvince: province }), setTemperatureUnit: (unit) => set({ temperatureUnit: unit }), setWindSpeedUnit: (unit) => set({ windSpeedUnit: unit }), + setDashboardSectionVisible: (section, visible) => set((state) => ({ + dashboardSections: { ...state.dashboardSections, [section]: visible }, + })), }), { name: "wtr:preferences", migrate: (persisted) => { const state = persisted as Partial | undefined; const location = state?.selectedLocation as (Partial & { region?: WeatherRegion }) | null | undefined; - if (!state || !location) return persisted; + if (!state) return persisted; + const dashboardSections = normalizeDashboardSectionVisibility(state.dashboardSections); + if (!location) return { ...state, dashboardSections }; const countryCode = location.countryCode ?? (location.province ? "PL" : null); const region = location.region ?? getWeatherRegionForCountryCode(countryCode); return { ...state, + dashboardSections, selectedLocation: { ...location, country: location.country ?? (countryCode === "PL" ? "Polska" : null),