feat: redesigned dashboard UI with editorial aesthetic and RTL support
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:
fahed
2026-04-19 17:58:33 +03:00
parent 0f6881309c
commit c8c3465233
13 changed files with 2819 additions and 178 deletions
+16 -11
View File
@@ -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>
);