Rewrite in TS

This commit is contained in:
Andras Schmelczer 2026-01-25 21:54:22 +00:00
parent 8c1f6a82e2
commit bfcf26e425
19 changed files with 3229 additions and 632 deletions

View file

@ -1,83 +0,0 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import Map from './components/Map';
import Filters from './components/Filters';
import { DEFAULT_FILTERS } from './lib/constants';
const DEBOUNCE_MS = 150;
export default function App() {
const [filters, setFilters] = useState(DEFAULT_FILTERS);
const [data, setData] = useState([]);
const [resolution, setResolution] = useState(8);
const [bounds, setBounds] = useState(null);
const [loading, setLoading] = useState(false);
const debounceRef = useRef(null);
const abortControllerRef = useRef(null);
// Debounced fetch when dependencies change
useEffect(() => {
if (!bounds) return;
// Clear previous debounce timer
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}
debounceRef.current = setTimeout(async () => {
// Cancel any in-flight request
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
setLoading(true);
try {
const boundsStr = `${bounds.south},${bounds.west},${bounds.north},${bounds.east}`;
const params = new URLSearchParams({
resolution: resolution.toString(),
min_year: filters.minYear.toString(),
max_year: filters.maxYear.toString(),
min_price: filters.minPrice.toString(),
max_price: filters.maxPrice.toString(),
bounds: boundsStr,
});
const res = await fetch(`/api/hexagons?${params}`, {
signal: abortControllerRef.current.signal,
});
const json = await res.json();
setData(json.features || []);
} catch (err) {
if (err.name !== 'AbortError') {
console.error('Failed to fetch data:', err);
}
} finally {
setLoading(false);
}
}, DEBOUNCE_MS);
return () => {
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}
};
}, [filters, resolution, bounds]);
const handleViewChange = useCallback(({ resolution: newRes, bounds: newBounds }) => {
setResolution(newRes);
setBounds(newBounds);
}, []);
return (
<div className="h-screen flex">
<Filters filters={filters} onChange={setFilters} />
<div className="flex-1 relative">
<Map data={data} onViewChange={handleViewChange} />
{loading && (
<div className="absolute top-4 right-4 bg-white px-3 py-1 rounded shadow">
Loading...
</div>
)}
</div>
</div>
);
}