feat: add CitySidebar component with categorized, sortable city lists and persistent expansion state
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { startTransition, useEffect, useMemo, useState } from "react";
|
import { startTransition, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Clock } from "lucide-react";
|
import { Clock } from "lucide-react";
|
||||||
import { useDashboardStore } from "@/hooks/useDashboardStore";
|
import { useDashboardStore } from "@/hooks/useDashboardStore";
|
||||||
@@ -61,6 +61,7 @@ export function CitySidebar() {
|
|||||||
const [expandedGroups, setExpandedGroups] = useState<
|
const [expandedGroups, setExpandedGroups] = useState<
|
||||||
Record<RiskGroupKey, boolean>
|
Record<RiskGroupKey, boolean>
|
||||||
>(DEFAULT_EXPANDED_GROUPS);
|
>(DEFAULT_EXPANDED_GROUPS);
|
||||||
|
const cityItemRefs = useRef<Record<string, HTMLButtonElement | null>>({});
|
||||||
|
|
||||||
const sortedCities = useMemo(
|
const sortedCities = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -104,6 +105,23 @@ export function CitySidebar() {
|
|||||||
);
|
);
|
||||||
}, [selectedCity, store.cities]);
|
}, [selectedCity, store.cities]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedCity) return;
|
||||||
|
const selected = store.cities.find((city) => city.name === selectedCity);
|
||||||
|
if (!selected) return;
|
||||||
|
const groupKey = toPerformanceGroup(selected);
|
||||||
|
if (!expandedGroups[groupKey]) return;
|
||||||
|
|
||||||
|
const frameId = window.requestAnimationFrame(() => {
|
||||||
|
cityItemRefs.current[selectedCity]?.scrollIntoView({
|
||||||
|
block: "center",
|
||||||
|
inline: "nearest",
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return () => window.cancelAnimationFrame(frameId);
|
||||||
|
}, [expandedGroups, selectedCity, store.cities]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
const raw = window.localStorage.getItem(GROUP_STATE_STORAGE_KEY);
|
const raw = window.localStorage.getItem(GROUP_STATE_STORAGE_KEY);
|
||||||
@@ -226,6 +244,9 @@ export function CitySidebar() {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={city.name}
|
key={city.name}
|
||||||
|
ref={(node) => {
|
||||||
|
cityItemRefs.current[city.name] = node;
|
||||||
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
className={clsx("city-item", isActive && "active")}
|
className={clsx("city-item", isActive && "active")}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user