some custom stuff

This commit is contained in:
2025-11-15 00:23:48 +01:00
parent 4dbbd30d4d
commit c189e3f59a
19 changed files with 1116 additions and 831 deletions

View File

@@ -9,7 +9,8 @@
"version": "0.0.0",
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0"
"react-dom": "^19.2.0",
"swr": "^2.3.6"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
@@ -1675,6 +1676,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.252",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.252.tgz",
@@ -2653,6 +2663,19 @@
"node": ">=8"
}
},
"node_modules/swr": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz",
"integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -2724,6 +2747,15 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/vite": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",

View File

@@ -11,7 +11,8 @@
},
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0"
"react-dom": "^19.2.0",
"swr": "^2.3.6"
},
"devDependencies": {
"@eslint/js": "^9.39.1",

View File

@@ -1,30 +1,27 @@
import { useState, useEffect } from "react";
import { useState, useMemo } from "react";
import useSWR from "swr";
import "./App.css";
function StopSelector({ stops, onChange, placeholder, inputId }) {
const [inputValue, setInputValue] = useState("");
const [filteredStops, setFilteredStops] = useState([]);
const [showList, setShowList] = useState(false);
useEffect(() => {
const filteredStops = useMemo(() => {
if (inputValue.trim()) {
const filtered = stops
return stops
.filter((stop) =>
stop.stop_name.toLowerCase().includes(inputValue.toLowerCase()),
)
.slice(0, 10);
setFilteredStops(filtered);
setShowList(true);
} else {
setFilteredStops([]);
setShowList(false);
return [];
}
}, [inputValue, stops]);
const showList = useMemo(() => filteredStops.length > 0, [filteredStops]);
const handleSelect = (stop) => {
setInputValue(stop.stop_name);
onChange({ id: stop.stop_id, name: stop.stop_name });
setShowList(false);
};
return (
@@ -50,8 +47,10 @@ function StopSelector({ stops, onChange, placeholder, inputId }) {
);
}
const fetcher = (url) => fetch(url).then(res => res.ok ? res.json() : Promise.reject(res.statusText));
function App() {
const [stops, setStops] = useState([]);
const { data: stops = [], error: stopsError } = useSWR('/api/stops', fetcher);
const [from, setFrom] = useState({
id: "740000030",
name: "Falun Centralstation",
@@ -60,40 +59,21 @@ function App() {
id: "740000001",
name: "Stockholm Centralstation",
});
const [time, setTime] = useState(new Date().toISOString().slice(0, 16));
const [time, setTime] = useState(new Date().toISOString());
const [num, setNum] = useState(3);
const [routes, setRoutes] = useState([]);
const [routeKey, setRouteKey] = useState(null);
const { data: routes = [], error: routesError, isLoading } = useSWR(routeKey, fetcher);
const [selectedRouteIndex, setSelectedRouteIndex] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
fetch("/api/stops")
.then((res) => res.json())
.then(setStops)
.catch((err) => setError("Failed to load stops: " + err.message));
}, []);
const findRoute = () => {
if (!from.id || !to.id) {
setError("Please select both from and to stops");
return;
}
setLoading(true);
setError("");
fetch(
`/api/route?from=${from.id}&to=${to.id}&when=${encodeURIComponent(time)}&num=${num}`,
)
.then((res) => (res.ok ? res.json() : Promise.reject(res.statusText)))
.then((data) => {
setRoutes(data);
setSelectedRouteIndex(0);
setLoading(false);
})
.catch((err) => {
setError("Failed to find route: " + err);
setLoading(false);
});
setRouteKey(`/api/route?from=${from.id}&to=${to.id}&when=${encodeURIComponent(time)}&num=${num}`);
setSelectedRouteIndex(0);
};
return (
@@ -142,12 +122,12 @@ function App() {
<option value={10}>10</option>
</select>
</label>
<button onClick={findRoute} disabled={loading} type="button">
{loading ? "Finding route..." : "Find Route"}
<button onClick={findRoute} disabled={isLoading} type="button">
{isLoading ? "Finding route..." : "Find Route"}
</button>
</div>
{error && <p className="error">{error}</p>}
{(error || stopsError || routesError) && <p className="error">{error || (stopsError ? "Failed to load stops: " + stopsError.message : "Failed to find route: " + routesError)}</p>}
{routes.length > 0 && (
<div className="routes">
<h2 className="route-title">

View File

@@ -8,8 +8,6 @@ export default defineConfig({
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
secure: false,
},
},
},