feat: multi-select filters for events and channels
- New MultiSelect component with checkbox dropdown - Event and channel filters now accept multiple selections - Empty array = all selected (no filter applied) - URL params store selections as comma-separated values - District and quarter remain single-select Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useMemo, useCallback, useRef } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Line, Bar } from 'react-chartjs-2';
|
||||
import { EmptyState, FilterControls } from './shared';
|
||||
import { EmptyState, FilterControls, MultiSelect } from './shared';
|
||||
import { ExportableChart } from './ChartExport';
|
||||
import { chartColors, createBaseOptions } from '../config/chartConfig';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
@@ -109,8 +109,8 @@ function Comparison({ data, showDataLabels, setShowDataLabels, includeVAT, setIn
|
||||
});
|
||||
const [filters, setFiltersState] = useState(() => ({
|
||||
district: searchParams.get('district') || 'all',
|
||||
channel: searchParams.get('channel') || 'all',
|
||||
museum: searchParams.get('museum') || 'all'
|
||||
channel: searchParams.get('channel')?.split(',').filter(Boolean) || [],
|
||||
museum: searchParams.get('museum')?.split(',').filter(Boolean) || []
|
||||
}));
|
||||
|
||||
const [chartMetric, setChartMetric] = useState('revenue');
|
||||
@@ -128,8 +128,8 @@ function Comparison({ data, showDataLabels, setShowDataLabels, includeVAT, setIn
|
||||
if (newTo) params.set('to', newTo);
|
||||
}
|
||||
if (newFilters?.district && newFilters.district !== 'all') params.set('district', newFilters.district);
|
||||
if (newFilters?.channel && newFilters.channel !== 'all') params.set('channel', newFilters.channel);
|
||||
if (newFilters?.museum && newFilters.museum !== 'all') params.set('museum', newFilters.museum);
|
||||
if (newFilters?.channel && newFilters.channel.length > 0) params.set('channel', newFilters.channel.join(','));
|
||||
if (newFilters?.museum && newFilters.museum.length > 0) params.set('museum', newFilters.museum.join(','));
|
||||
setSearchParams(params, { replace: true });
|
||||
}, [setSearchParams, latestYear]);
|
||||
|
||||
@@ -249,7 +249,7 @@ function Comparison({ data, showDataLabels, setShowDataLabels, includeVAT, setIn
|
||||
const currMetrics = useMemo(() => calculateMetrics(currData, includeVAT), [currData, includeVAT]);
|
||||
|
||||
const hasData = prevData.length > 0 || currData.length > 0;
|
||||
const resetFilters = () => setFilters({ district: 'all', channel: 'all', museum: 'all' });
|
||||
const resetFilters = () => setFilters({ district: 'all', channel: [], museum: [] });
|
||||
|
||||
const calcChange = (prev: number, curr: number) => prev === 0 ? (curr > 0 ? Infinity : 0) : ((curr - prev) / prev * 100);
|
||||
|
||||
@@ -581,22 +581,26 @@ function Comparison({ data, showDataLabels, setShowDataLabels, includeVAT, setIn
|
||||
</>
|
||||
)}
|
||||
<FilterControls.Group label={t('filters.district')}>
|
||||
<select value={filters.district} onChange={e => setFilters({...filters, district: e.target.value, museum: 'all'})}>
|
||||
<select value={filters.district} onChange={e => setFilters({...filters, district: e.target.value, museum: []})}>
|
||||
<option value="all">{t('filters.allDistricts')}</option>
|
||||
{districts.map(d => <option key={d} value={d}>{d}</option>)}
|
||||
</select>
|
||||
</FilterControls.Group>
|
||||
<FilterControls.Group label={t('filters.channel')}>
|
||||
<select value={filters.channel} onChange={e => setFilters({...filters, channel: e.target.value})}>
|
||||
<option value="all">{t('filters.allChannels')}</option>
|
||||
{channels.map(c => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
<MultiSelect
|
||||
options={channels}
|
||||
selected={filters.channel}
|
||||
onChange={selected => setFilters({...filters, channel: selected})}
|
||||
allLabel={t('filters.allChannels')}
|
||||
/>
|
||||
</FilterControls.Group>
|
||||
<FilterControls.Group label={t('filters.museum')}>
|
||||
<select value={filters.museum} onChange={e => setFilters({...filters, museum: e.target.value})}>
|
||||
<option value="all">{t('filters.allMuseums')}</option>
|
||||
{availableMuseums.map(m => <option key={m} value={m}>{m}</option>)}
|
||||
</select>
|
||||
<MultiSelect
|
||||
options={availableMuseums}
|
||||
selected={filters.museum}
|
||||
onChange={selected => setFilters({...filters, museum: selected})}
|
||||
allLabel={t('filters.allMuseums')}
|
||||
/>
|
||||
</FilterControls.Group>
|
||||
</FilterControls.Row>
|
||||
</FilterControls>
|
||||
|
||||
Reference in New Issue
Block a user