feat: redesigned dashboard UI with editorial aesthetic and RTL support
Deploy HiHala Dashboard / deploy (push) Successful in 9s
Deploy HiHala Dashboard / deploy (push) Successful in 9s
- Replace Dashboard/Comparison with DashboardDemo/PeriodSelectorDemo as primary pages at / and /comparison - New editorial design: DM Serif Display + Outfit fonts, inline period picker, multi-select filters for museum/channel/district - Full Arabic RTL support with IBM Plex Sans Arabic; EN/AR toggle synced to global LanguageContext - Bar/pie chart toggle + absolute/percent toggle for museum, channel, district charts - Refined top nav: transparent inactive links, accent active state, visual separator between nav links and utilities - DateRangePicker, MultiSelect, FilterControls shared components added - NavDemo: sidebar layout alternative (accessible at /nav-demo) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+16
-11
@@ -1,9 +1,9 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo, ReactNode, lazy, Suspense } from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom';
|
||||
|
||||
const Dashboard = lazy(() => import('./components/Dashboard'));
|
||||
const Comparison = lazy(() => import('./components/Comparison'));
|
||||
const Settings = lazy(() => import('./components/Settings'));
|
||||
const PeriodSelectorDemo = lazy(() => import('./components/PeriodSelectorDemo'));
|
||||
const DashboardDemo = lazy(() => import('./components/DashboardDemo'));
|
||||
import Login from './components/Login';
|
||||
import LoadingSkeleton from './components/shared/LoadingSkeleton';
|
||||
import { fetchData, getCacheStatus, refreshData, getUniqueMuseums, getUniqueChannels } from './services/dataService';
|
||||
@@ -30,6 +30,10 @@ function NavLink({ to, children, className }: NavLinkProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function AppNav({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
interface DataSource {
|
||||
id: string;
|
||||
labelKey: string;
|
||||
@@ -51,7 +55,6 @@ function App() {
|
||||
const [error, setError] = useState<{ message: string; type: DataErrorType } | null>(null);
|
||||
const [isOffline, setIsOffline] = useState<boolean>(false);
|
||||
const [cacheInfo, setCacheInfo] = useState<CacheStatus | null>(null);
|
||||
const [showDataLabels, setShowDataLabels] = useState<boolean>(false);
|
||||
const [includeVAT, setIncludeVAT] = useState<boolean>(true);
|
||||
const [dataSource, setDataSource] = useState<string>('museums');
|
||||
const [seasons, setSeasons] = useState<Season[]>([]);
|
||||
@@ -188,7 +191,7 @@ function App() {
|
||||
return (
|
||||
<Router>
|
||||
<div className="app" dir={dir}>
|
||||
<nav className="nav-bar" aria-label={t('nav.dashboard')}>
|
||||
<AppNav><nav className="nav-bar" aria-label={t('nav.dashboard')}>
|
||||
<div className="nav-content">
|
||||
<div className="nav-brand">
|
||||
<svg className="nav-brand-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
@@ -198,7 +201,8 @@ function App() {
|
||||
<rect x="14" y="11" width="7" height="10" rx="1"/>
|
||||
</svg>
|
||||
<span className="nav-brand-text">
|
||||
HiHala Data
|
||||
<span className="nav-brand-name">HiHala</span>
|
||||
<span className="nav-brand-tag">Data</span>
|
||||
<select
|
||||
className="data-source-select"
|
||||
value={dataSource}
|
||||
@@ -233,6 +237,7 @@ function App() {
|
||||
</svg>
|
||||
{t('nav.comparison')}
|
||||
</NavLink>
|
||||
<span className="nav-sep" aria-hidden="true" />
|
||||
{isOffline && (
|
||||
<span className="offline-badge" title={cacheInfo ? `Cached: ${new Date(cacheInfo.timestamp || '').toLocaleString()}` : ''}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
@@ -291,20 +296,20 @@ function App() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</nav></AppNav>
|
||||
|
||||
<main>
|
||||
<Suspense fallback={<LoadingSkeleton />}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard data={data} seasons={seasons} userRole={userRole} showDataLabels={showDataLabels} setShowDataLabels={setShowDataLabels} includeVAT={includeVAT} setIncludeVAT={setIncludeVAT} allowedMuseums={allowedMuseums} allowedChannels={allowedChannels} />} />
|
||||
<Route path="/comparison" element={<Comparison data={data} seasons={seasons} showDataLabels={showDataLabels} setShowDataLabels={setShowDataLabels} includeVAT={includeVAT} setIncludeVAT={setIncludeVAT} allowedMuseums={allowedMuseums} allowedChannels={allowedChannels} />} />
|
||||
<Route path="/" element={<DashboardDemo data={data} seasons={seasons} includeVAT={includeVAT} setIncludeVAT={setIncludeVAT} allowedMuseums={allowedMuseums} allowedChannels={allowedChannels} />} />
|
||||
<Route path="/comparison" element={<PeriodSelectorDemo data={data} seasons={seasons} includeVAT={includeVAT} allowedMuseums={allowedMuseums} allowedChannels={allowedChannels} />} />
|
||||
{userRole === 'admin' && <Route path="/settings" element={<Settings onSeasonsChange={loadSeasons} allMuseums={allMuseumsList} allChannels={allChannelsList} />} />}
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</main>
|
||||
|
||||
{/* Mobile Bottom Navigation */}
|
||||
<nav className="mobile-nav" aria-label="Mobile navigation">
|
||||
<AppNav><nav className="mobile-nav" aria-label="Mobile navigation">
|
||||
<NavLink to="/" className="mobile-nav-item">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||
<rect x="3" y="3" width="7" height="9" rx="1"/>
|
||||
@@ -342,7 +347,7 @@ function App() {
|
||||
</svg>
|
||||
<span>{t('language.switch')}</span>
|
||||
</button>
|
||||
</nav>
|
||||
</nav></AppNav>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user