feat: add CitySidebar component with categorized, sortable city lists and persistent expansion state

This commit is contained in:
2569718930@qq.com
2026-04-20 20:02:42 +08:00
parent 02c7787985
commit 3901a9e967
+22 -1
View File
@@ -1,6 +1,6 @@
"use client";
import { startTransition, useEffect, useMemo, useState } from "react";
import { startTransition, useEffect, useMemo, useRef, useState } from "react";
import clsx from "clsx";
import { Clock } from "lucide-react";
import { useDashboardStore } from "@/hooks/useDashboardStore";
@@ -61,6 +61,7 @@ export function CitySidebar() {
const [expandedGroups, setExpandedGroups] = useState<
Record<RiskGroupKey, boolean>
>(DEFAULT_EXPANDED_GROUPS);
const cityItemRefs = useRef<Record<string, HTMLButtonElement | null>>({});
const sortedCities = useMemo(
() =>
@@ -104,6 +105,23 @@ export function CitySidebar() {
);
}, [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(() => {
if (typeof window === "undefined") return;
const raw = window.localStorage.getItem(GROUP_STATE_STORAGE_KEY);
@@ -226,6 +244,9 @@ export function CitySidebar() {
return (
<button
key={city.name}
ref={(node) => {
cityItemRefs.current[city.name] = node;
}}
type="button"
className={clsx("city-item", isActive && "active")}
onClick={() =>