fix: mobile UX overhaul — collapsible filters, settings nav, responsive layout
Deploy HiHala Dashboard / deploy (push) Successful in 8s

- Add Settings link to desktop nav bar for admin users
- Rewrite Settings page from table layout to responsive card list (fixes unusable mobile state)
- Filter bar (Dashboard + Comparison): collapsible panel on mobile via display:contents trick; stacked full-width dropdowns replace horizontal scroll
- Active filter count badge shown in collapsed filter header
- AltMultiSelect dropdowns go full-width on mobile to prevent viewport overflow
- Chart control separators hidden on mobile to avoid crowding
- Metric grid: 2-col at ≤700px, 1-col at ≤480px
- Comparison period cards: smaller font and tighter padding at ≤680px
- Page shell padding reduced on mobile (48px→20px top, 24px→16px sides)
- Settings page gets correct 80px bottom padding for mobile nav

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-04-28 12:22:07 +03:00
parent 30cdb5064a
commit c9cfb58896
5 changed files with 447 additions and 218 deletions
+23 -11
View File
@@ -184,6 +184,8 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
const metricOpts = [{ value:'revenue', label:L.revenue }, { value:'visitors', label:L.visitors }, { value:'tickets', label:L.tickets }];
const granOpts = [{ value:'day', label:L.daily }, { value:'week', label:L.weekly }, { value:'month', label:L.monthly }];
const hasFilters = selDistricts.length>0 || selChannels.length>0 || selMuseums.length>0;
const activeFilterCount = selDistricts.length + selChannels.length + selMuseums.length;
const [filtersOpen, setFiltersOpen] = useState(false);
return (
<div
@@ -200,17 +202,27 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
<PeriodHero start={start} end={end} onChange={(s,e) => { setStart(s); setEnd(e); }} availableYears={availableYears} L={L} />
<div className="alt-filter-bar">
<span className="alt-filter-label">{L.filter}</span>
<div className="alt-filter-sep" />
<AltMultiSelect value={selDistricts} options={allDistricts} onChange={setSelDistricts} allLabel={L.allDistricts} countLabel={L.countDistricts} clearLabel={L.clearSel} />
<AltMultiSelect value={selChannels} options={allChannels} onChange={setSelChannels} allLabel={L.allChannels} countLabel={L.countChannels} clearLabel={L.clearSel} />
<AltMultiSelect value={selMuseums} options={allMuseums} onChange={setSelMuseums} allLabel={L.allMuseums} countLabel={L.countMuseums} clearLabel={L.clearSel} />
{hasFilters && <button type="button" className="alt-filter-reset" onClick={() => { setSelDistricts([]); setSelChannels([]); setSelMuseums([]); }}>{L.reset}</button>}
<div className="alt-filter-spacer" />
<div className="alt-vat-toggle">
<button type="button" className={`alt-vat-opt${!includeVAT?' alt-vat-opt--on':''}`} onClick={() => setIncludeVAT(false)}>{L.exclVAT}</button>
<button type="button" className={`alt-vat-opt${includeVAT ?' alt-vat-opt--on':''}`} onClick={() => setIncludeVAT(true)}>{L.inclVAT}</button>
<div className={`alt-filter-bar${filtersOpen ? ' alt-filter-bar--open' : ''}`}>
<div className="alt-filter-head">
<span className="alt-filter-label">{L.filter}</span>
{activeFilterCount > 0 && <span className="alt-filter-badge">{activeFilterCount}</span>}
<button type="button" className="alt-filter-toggle" onClick={() => setFiltersOpen(v => !v)} aria-expanded={filtersOpen} aria-label="Toggle filters">
<svg className={`alt-filter-chevron${filtersOpen ? ' alt-filter-chevron--open' : ''}`} width="14" height="14" viewBox="0 0 10 10" fill="none">
<path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</div>
<div className="alt-filter-body">
<div className="alt-filter-sep" />
<AltMultiSelect value={selDistricts} options={allDistricts} onChange={setSelDistricts} allLabel={L.allDistricts} countLabel={L.countDistricts} clearLabel={L.clearSel} />
<AltMultiSelect value={selChannels} options={allChannels} onChange={setSelChannels} allLabel={L.allChannels} countLabel={L.countChannels} clearLabel={L.clearSel} />
<AltMultiSelect value={selMuseums} options={allMuseums} onChange={setSelMuseums} allLabel={L.allMuseums} countLabel={L.countMuseums} clearLabel={L.clearSel} />
{hasFilters && <button type="button" className="alt-filter-reset" onClick={() => { setSelDistricts([]); setSelChannels([]); setSelMuseums([]); }}>{L.reset}</button>}
<div className="alt-filter-spacer" />
<div className="alt-vat-toggle">
<button type="button" className={`alt-vat-opt${!includeVAT?' alt-vat-opt--on':''}`} onClick={() => setIncludeVAT(false)}>{L.exclVAT}</button>
<button type="button" className={`alt-vat-opt${includeVAT ?' alt-vat-opt--on':''}`} onClick={() => setIncludeVAT(true)}>{L.inclVAT}</button>
</div>
</div>
</div>