diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx index 62f95353..584a9e5d 100644 --- a/client/src/pages/DashboardPage.tsx +++ b/client/src/pages/DashboardPage.tsx @@ -21,6 +21,7 @@ import { import { formatTime, splitReservationDateTime } from '../utils/formatters' import { convertDistance, getDistanceUnitLabel } from '../utils/units' import { useSettingsStore } from '../store/settingsStore' +import { normalizeAppearance } from '@trek/shared' import '../styles/dashboard.css' const GRADIENTS = [ @@ -92,6 +93,17 @@ export default function DashboardPage(): React.ReactElement { handleCreate, handleUpdate, confirmDelete, handleArchive, handleUnarchive, confirmCopy, } = useDashboard() + // Per-device dashboard widget visibility (from the appearance config). + const isMobile = useIsMobile() + const appearanceCfg = useSettingsStore(s => s.settings.appearance) + const dashCfg = normalizeAppearance(appearanceCfg).dashboard + const sideWidgets = isMobile ? dashCfg.mobile : dashCfg.desktop + const showCurrency = sideWidgets.currency + const showTimezones = sideWidgets.timezones + const showUpcoming = sideWidgets.upcomingReservations + // Desktop has a master toggle for the whole right column; off → centered layout. + const sidebarVisible = (isMobile || dashCfg.desktop.sidebar) && (showCurrency || showTimezones || showUpcoming) + return ( <> {/* Navbar lives outside .trek-dash so it keeps the app-wide font + button @@ -102,7 +114,7 @@ export default function DashboardPage(): React.ReactElement { {demoMode && }
-
+
{loadError && (
@@ -176,11 +188,13 @@ export default function DashboardPage(): React.ReactElement {
- + {sidebarVisible && ( + + )}
@@ -370,9 +384,28 @@ function formatCompactDistance(value: number): string { return String(rounded) } -function AtlasStats({ stats }: { stats: TravelStats | null }): React.ReactElement { +function AtlasStats({ stats }: { stats: TravelStats | null }): React.ReactElement | null { const { t } = useTranslation() const distanceUnit = useSettingsStore(s => s.settings.distance_unit) || 'metric' + const appearance = useSettingsStore(s => s.settings.appearance) + const isMobile = useIsMobile() + const dash = normalizeAppearance(appearance).dashboard + + // Per-device widget visibility. Atlas + distance are desktop-only tiles. + const showAtlas = !isMobile && dash.desktop.atlas + const showTrips = isMobile ? dash.mobile.tripsTotal : dash.desktop.tripsTotal + const showDays = isMobile ? dash.mobile.daysTraveled : dash.desktop.daysTraveled + const showDistance = !isMobile && dash.desktop.distanceFlown + if (!showAtlas && !showTrips && !showDays && !showDistance) return null + + // Reflow: the grid spreads the visible tiles to full width (the passport stays + // proportionally wider). Set as CSS vars so the responsive media queries still win. + const atlasTemplate = + [dash.desktop.atlas && '1.5fr', dash.desktop.tripsTotal && '1fr', dash.desktop.daysTraveled && '1fr', dash.desktop.distanceFlown && '1fr'] + .filter(Boolean).join(' ') || '1fr' + const atlasTemplateM = + [dash.mobile.tripsTotal && '1fr', dash.mobile.daysTraveled && '1fr'].filter(Boolean).join(' ') || '1fr' + const countries = stats?.countries || [] const distanceKm = stats?.totalDistanceKm || 0 const distance = convertDistance(distanceKm, distanceUnit) @@ -382,48 +415,56 @@ function AtlasStats({ stats }: { stats: TravelStats | null }): React.ReactElemen const distanceLabel = getDistanceUnitLabel(distanceUnit) return ( -
-
-
{t('dashboard.atlas.countriesVisited')}
-
{countries.length} {t('dashboard.atlas.ofTotal', { total: 195 })}
-
- {countries.slice(0, 5).map((c, i) => ( - - {c} - - ))} - {countries.length > 5 && +{countries.length - 5}} +
+ {showAtlas && ( +
+
{t('dashboard.atlas.countriesVisited')}
+
{countries.length} {t('dashboard.atlas.ofTotal', { total: 195 })}
+
+ {countries.slice(0, 5).map((c, i) => ( + + {c} + + ))} + {countries.length > 5 && +{countries.length - 5}} +
+
-
-
+ )} -
-
{t('dashboard.atlas.tripsTotal')}
-
{stats?.totalTrips ?? 0}
-
{t('dashboard.atlas.placesMapped', { count: stats?.totalPlaces ?? 0 })}
- - - -
+ {showTrips && ( +
+
{t('dashboard.atlas.tripsTotal')}
+
{stats?.totalTrips ?? 0}
+
{t('dashboard.atlas.placesMapped', { count: stats?.totalPlaces ?? 0 })}
+ + + +
+ )} -
-
{t('dashboard.atlas.daysTraveled')}
-
{stats?.totalDays ?? 0} {t('dashboard.atlas.daysUnit')}
-
{t('dashboard.atlas.acrossAllTrips')}
- - - -
+ {showDays && ( +
+
{t('dashboard.atlas.daysTraveled')}
+
{stats?.totalDays ?? 0} {t('dashboard.atlas.daysUnit')}
+
{t('dashboard.atlas.acrossAllTrips')}
+ + + +
+ )} -
-
{t('dashboard.atlas.distanceFlown')}
-
{distanceText} {distanceLabel}
-
{t('dashboard.atlas.aroundEquator', { count: equatorTimes })}
- - - - -
+ {showDistance && ( +
+
{t('dashboard.atlas.distanceFlown')}
+
{distanceText} {distanceLabel}
+
{t('dashboard.atlas.aroundEquator', { count: equatorTimes })}
+ + + + +
+ )}
) } diff --git a/client/src/styles/dashboard.css b/client/src/styles/dashboard.css index aaa45fb8..0337767a 100644 --- a/client/src/styles/dashboard.css +++ b/client/src/styles/dashboard.css @@ -103,6 +103,9 @@ align-items: start; } .trek-dash .page-main { min-width: 0; } +/* Right sidebar disabled → single centered column. */ +.trek-dash .page[data-no-sidebar="true"] { grid-template-columns: 1fr; } +.trek-dash .page[data-no-sidebar="true"] .page-main { max-width: 1080px; margin-inline: auto; width: 100%; } .trek-dash .page-sidebar { position: sticky; top: 24px; @@ -318,7 +321,7 @@ .trek-dash .pass-cell.countdown { gap: 6px; } /* ----------------- atlas / stats ----------------- */ -.trek-dash .atlas { display: grid; grid-template-columns: 1.5fr 1fr 1fr 1fr; gap: 16px; margin-bottom: 56px; } +.trek-dash .atlas { display: grid; grid-template-columns: var(--atlas-template, 1.5fr 1fr 1fr 1fr); gap: 16px; margin-bottom: 56px; } .trek-dash .atlas-card { background: var(--glass-bg); border-radius: var(--r-lg); padding: 24px 26px; border: 1px solid var(--glass-border); @@ -599,7 +602,7 @@ /* Atlas → single row of stat cards. Passport (countries) and distance are hidden on mobile; only Trips total + Days traveled remain. */ - .trek-dash .atlas { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 0 0 26px; } + .trek-dash .atlas { display: grid; grid-template-columns: var(--atlas-template-m, 1fr 1fr); gap: 10px; margin: 0 0 26px; } .trek-dash .atlas-card.passport, .trek-dash .atlas-card:last-child { display: none; } .trek-dash .atlas .spark { display: none; }