···
252
252
const [data, setData] = useState<StatisticsPayload | null>(null);
253
253
const [loading, setLoading] = useState(true);
254
254
const [error, setError] = useState<string | null>(null);
255
255
-
const [selectedYear, setSelectedYear] = useState<number | null>(null);
255
255
+
const [selectedYears, setSelectedYears] = useState<number[]>([]);
256
256
257
257
useEffect(() => {
258
258
const ac = new AbortController();
···
298
298
return (data?.years || []).slice().sort((a, b) => (b?.year ?? 0) - (a?.year ?? 0));
299
299
}, [data?.years]);
300
300
301
301
-
// Default selection to latest year when data arrives
301
301
+
// Default selection to latest year when data arrives.
302
302
useEffect(() => {
303
303
-
if (!selectedYear && yearsSorted.length > 0) {
304
304
-
setSelectedYear(yearsSorted[0].year);
303
303
+
if (selectedYears.length === 0 && yearsSorted.length > 0) {
304
304
+
setSelectedYears([yearsSorted[0].year]);
305
305
}
306
306
-
}, [yearsSorted, selectedYear]);
306
306
+
}, [yearsSorted, selectedYears.length]);
307
307
308
308
-
const currentYearStats = useMemo(() => {
309
309
-
if (yearsSorted.length === 0) return null;
310
310
-
if (selectedYear == null) return yearsSorted[0];
311
311
-
return yearsSorted.find((y) => y.year === selectedYear) || yearsSorted[0];
312
312
-
}, [yearsSorted, selectedYear]);
308
308
+
const selectedYearStats = useMemo(() => {
309
309
+
const selected = new Set(selectedYears);
310
310
+
return yearsSorted.filter((year) => selected.has(year.year));
311
311
+
}, [yearsSorted, selectedYears]);
312
312
+
313
313
+
const toggleYear = (year: number) => {
314
314
+
setSelectedYears((current) =>
315
315
+
current.includes(year)
316
316
+
? current.filter((selected) => selected !== year)
317
317
+
: [...current, year]
318
318
+
);
319
319
+
};
313
320
314
321
return (
315
322
<div>
316
323
<div className="mb-3 flex items-center justify-between">
317
324
<h2 className="text-lg font-semibold">{t("tabs.statistics", "Statistics")}</h2>
318
325
<div className="flex items-center gap-3">
319
319
-
<label className="text-xs text-gray-500">
320
320
-
{t("dashboard.year")}:
321
321
-
<select
322
322
-
className="text-xs bg-white dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 rounded px-2 py-1"
323
323
-
value={currentYearStats?.year ?? ""}
324
324
-
onChange={(e) => setSelectedYear(Number(e.target.value))}
325
325
-
>
326
326
-
{yearsSorted.map((y) => (
327
327
-
<option key={y.year} value={y.year}>{y.year}</option>
328
328
-
))}
329
329
-
</select>
330
330
-
</label>
326
326
+
<fieldset className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-gray-500">
327
327
+
<legend className="sr-only">{t("dashboard.year")}</legend>
328
328
+
<span aria-hidden>{t("dashboard.year")}:</span>
329
329
+
{yearsSorted.map((y) => (
330
330
+
<label key={y.year} className="inline-flex items-center gap-1 cursor-pointer">
331
331
+
<input
332
332
+
type="checkbox"
333
333
+
checked={selectedYears.includes(y.year)}
334
334
+
onChange={() => toggleYear(y.year)}
335
335
+
/>
336
336
+
<span>{y.year}</span>
337
337
+
</label>
338
338
+
))}
339
339
+
</fieldset>
331
340
<div className="text-xs text-gray-500">
332
341
{t("statuses.lastUpdate")} {data?.updatedAt ? new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(data.updatedAt)) : "–"}
333
342
</div>
334
343
</div>
335
344
</div>
336
336
-
{currentYearStats && (
337
337
-
<div>
338
338
-
<StatisticsKpis y={currentYearStats} />
345
345
+
{selectedYearStats.map((yearStats) => (
346
346
+
<section key={yearStats.year} className="mb-8" aria-labelledby={`statistics-year-${yearStats.year}`}>
347
347
+
<h3 id={`statistics-year-${yearStats.year}`} className="text-lg font-semibold mb-3">
348
348
+
{t("dashboard.year")}: {yearStats.year}
349
349
+
</h3>
350
350
+
<StatisticsKpis y={yearStats} />
339
351
<StatisticsLegend />
340
340
-
<CalendarHeatmap year={currentYearStats.year} />
341
341
-
<CalendarHeatmap
342
342
-
year={currentYearStats.year}
343
343
-
metric="tmin"
344
344
-
/>
345
345
-
<TopExtremes year={currentYearStats.year} />
346
346
-
</div>
347
347
-
)}
352
352
+
<CalendarHeatmap year={yearStats.year} />
353
353
+
<CalendarHeatmap year={yearStats.year} metric="tmin" />
354
354
+
<TopExtremes year={yearStats.year} />
355
355
+
</section>
356
356
+
))}
348
357
{loading && <div className="text-sm text-gray-600 dark:text-gray-300">{t("statuses.loading")}</div>}
349
358
{error && <div className="text-sm text-red-600">{t("statuses.error")}: {error}</div>}
350
359
{!loading && !error && data && data.years.length === 0 && (
351
360
<div className="text-sm text-gray-600 dark:text-gray-300">{t("statuses.noData")}</div>
352
361
)}
353
353
-
{!loading && !error && yearsSorted.length > 0 && (
362
362
+
{!loading && !error && selectedYearStats.length > 0 && (
354
363
<div>
355
355
-
{yearsSorted.map((y) => (
364
364
+
{selectedYearStats.map((y) => (
356
365
<YearSection key={y.year} y={y} />
357
366
))}
358
367
</div>