gendesign/frontend/src/components/site-finder/IsochronesPanel.tsx

185 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState } from "react";
import type { FeatureCollection } from "geojson";
import { useIsochrones } from "@/hooks/useIsochrones";
type IsoMode = "foot-walking" | "cycling-regular" | "driving-car";
interface Props {
cadNum: string;
onResult: (fc: FeatureCollection) => void;
}
const MODE_LABELS: Record<IsoMode, string> = {
"foot-walking": "Пешком",
"cycling-regular": "Велосипед",
"driving-car": "Машина",
};
const ALL_TIMES = [10, 15, 30] as const;
export function IsochronesPanel({ cadNum, onResult }: Props) {
const [mode, setMode] = useState<IsoMode>("foot-walking");
const [times, setTimes] = useState<Set<number>>(new Set([10, 15]));
const { mutate, isPending, isError, error } = useIsochrones();
function toggleTime(t: number) {
setTimes((prev) => {
const next = new Set(prev);
if (next.has(t)) {
next.delete(t);
} else {
next.add(t);
}
return next;
});
}
function handleShow() {
const timesMin = ALL_TIMES.filter((t) => times.has(t));
if (timesMin.length === 0) return;
mutate(
{ cadNum, mode, timesMin },
{
onSuccess: (res) => {
onResult(res.geojson);
},
},
);
}
return (
<div
style={{
padding: "12px 24px 16px",
borderTop: "1px solid #e5e7eb",
}}
>
<div
style={{
fontSize: 12,
fontWeight: 600,
color: "#6b7280",
textTransform: "uppercase",
letterSpacing: "0.05em",
marginBottom: 12,
}}
>
Изохроны доступности
</div>
{/* Mode radio */}
<div
style={{
display: "flex",
gap: 6,
marginBottom: 10,
flexWrap: "wrap",
}}
>
{(Object.keys(MODE_LABELS) as IsoMode[]).map((m) => (
<label
key={m}
style={{
display: "flex",
alignItems: "center",
gap: 5,
fontSize: 13,
cursor: "pointer",
padding: "4px 10px",
borderRadius: 6,
border: `1px solid ${mode === m ? "#3b82f6" : "#e5e7eb"}`,
background: mode === m ? "#dbeafe" : "#fff",
color: mode === m ? "#1d4ed8" : "#374151",
userSelect: "none",
}}
>
<input
type="radio"
name="iso-mode"
value={m}
checked={mode === m}
onChange={() => setMode(m)}
style={{ display: "none" }}
/>
{MODE_LABELS[m]}
</label>
))}
</div>
{/* Time checkboxes */}
<div
style={{
display: "flex",
gap: 6,
marginBottom: 12,
flexWrap: "wrap",
}}
>
{ALL_TIMES.map((t) => {
const checked = times.has(t);
return (
<label
key={t}
style={{
display: "flex",
alignItems: "center",
gap: 5,
fontSize: 13,
cursor: "pointer",
padding: "4px 10px",
borderRadius: 6,
border: `1px solid ${checked ? "#3b82f6" : "#e5e7eb"}`,
background: checked ? "#dbeafe" : "#fff",
color: checked ? "#1d4ed8" : "#374151",
userSelect: "none",
}}
>
<input
type="checkbox"
checked={checked}
onChange={() => toggleTime(t)}
style={{ display: "none" }}
/>
{t} мин
</label>
);
})}
</div>
{/* Show button */}
<button
onClick={handleShow}
disabled={isPending || times.size === 0}
style={{
padding: "7px 16px",
borderRadius: 8,
border: "none",
background: isPending || times.size === 0 ? "#e5e7eb" : "#3b82f6",
color: isPending || times.size === 0 ? "#9ca3af" : "#fff",
fontSize: 13,
fontWeight: 600,
cursor: isPending || times.size === 0 ? "not-allowed" : "pointer",
}}
>
{isPending ? "Загрузка…" : "Показать на карте"}
</button>
{/* Error */}
{isError && (
<div
style={{
marginTop: 8,
fontSize: 12,
color: "#dc2626",
}}
>
{error instanceof Error ? error.message : "Ошибка запроса изохрон"}
</div>
)}
</div>
);
}