[31b88f42] Finalize staff locations and travel time logic
This commit is contained in:
@@ -57,7 +57,13 @@ function App() {
|
||||
const [staffData, setStaffData] = useState<StaffData>({ sales: [], technicians: [] });
|
||||
const [showSales, setShowSales] = useState(false);
|
||||
const [showTechnicians, setShowTechnicians] = useState(false);
|
||||
const [travelRadius, setTravelRadius] = useState(50); // In km
|
||||
const [travelTime, setTravelTime] = useState(60); // In minutes
|
||||
const [averageSpeed, setAverageSpeed] = useState(80); // In km/h
|
||||
const [isochrones, setIsochrones] = useState<Record<string, any>>({});
|
||||
const [orsError, setOrsError] = useState<string | null>(null);
|
||||
|
||||
// Derived travel radius in km (fallback)
|
||||
const travelRadius = (travelTime / 60) * averageSpeed;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStaffLocations = async () => {
|
||||
@@ -71,6 +77,56 @@ function App() {
|
||||
fetchStaffLocations();
|
||||
}, []);
|
||||
|
||||
// Fetch isochrones when settings change
|
||||
useEffect(() => {
|
||||
if (!showSales && !showTechnicians) return;
|
||||
|
||||
const fetchAllIsochrones = async () => {
|
||||
const activeStaff = [
|
||||
...(showSales ? staffData.sales : []),
|
||||
...(showTechnicians ? staffData.technicians : [])
|
||||
];
|
||||
|
||||
if (activeStaff.length === 0) return;
|
||||
|
||||
const newIsochrones: Record<string, any> = { ...isochrones };
|
||||
let anyOrsError = false;
|
||||
|
||||
for (const person of activeStaff) {
|
||||
// Skip if already fetched for this person and time
|
||||
if (newIsochrones[person.name] && newIsochrones[person.name].minutes === travelTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/heatmap/api/isochrones', {
|
||||
params: { lat: person.lat, lon: person.lon, minutes: travelTime }
|
||||
});
|
||||
|
||||
if (response.data.simulated || response.data.error) {
|
||||
setOrsError("OpenRouteService API Key missing or invalid. Showing circular fallback.");
|
||||
anyOrsError = true;
|
||||
break;
|
||||
}
|
||||
|
||||
newIsochrones[person.name] = {
|
||||
data: response.data,
|
||||
minutes: travelTime
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(`Failed to fetch isochrone for ${person.name}:`, err);
|
||||
// Don't set anyOrsError here, just let this one person use the fallback
|
||||
}
|
||||
}
|
||||
|
||||
setIsochrones(newIsochrones);
|
||||
if (!anyOrsError) setOrsError(null);
|
||||
};
|
||||
|
||||
const timer = setTimeout(fetchAllIsochrones, 800); // Debounce
|
||||
return () => clearTimeout(timer);
|
||||
}, [travelTime, showSales, showTechnicians, staffData]);
|
||||
|
||||
const handleUploadSuccess = (response: any) => {
|
||||
setError(null);
|
||||
if (response.plz_column_needed) {
|
||||
@@ -194,18 +250,44 @@ function App() {
|
||||
</div>
|
||||
|
||||
{(showSales || showTechnicians) && (
|
||||
<div className="radius-control" style={{ marginBottom: '20px' }}>
|
||||
<label htmlFor="travel-radius">Travel Radius: {travelRadius} km</label>
|
||||
<input
|
||||
type="range"
|
||||
id="travel-radius"
|
||||
min="10"
|
||||
max="200"
|
||||
step="5"
|
||||
value={travelRadius}
|
||||
onChange={(e) => setTravelRadius(parseInt(e.target.value))}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<div className="radius-control" style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#444', borderRadius: '4px' }}>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label htmlFor="travel-time">Travel Time: {travelTime} min (max 60)</label>
|
||||
<input
|
||||
type="range"
|
||||
id="travel-time"
|
||||
min="15"
|
||||
max="60"
|
||||
step="5"
|
||||
value={travelTime}
|
||||
</div>
|
||||
{orsError ? (
|
||||
<>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label htmlFor="avg-speed">Avg. Speed: {averageSpeed} km/h</label>
|
||||
<input
|
||||
type="range"
|
||||
id="avg-speed"
|
||||
min="30"
|
||||
max="120"
|
||||
step="5"
|
||||
value={averageSpeed}
|
||||
onChange={(e) => setAverageSpeed(parseInt(e.target.value))}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8em', color: '#ffcc00', marginBottom: '5px' }}>
|
||||
{orsError}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.9em', color: '#ccc', textAlign: 'center', borderTop: '1px solid #666', paddingTop: '5px' }}>
|
||||
Resulting Radius: <strong>{travelRadius.toFixed(1)} km</strong>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ fontSize: '0.8em', color: '#4CAF50' }}>
|
||||
Using real road data (OpenRouteService)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -240,6 +322,7 @@ function App() {
|
||||
showSales={showSales}
|
||||
showTechnicians={showTechnicians}
|
||||
travelRadius={travelRadius}
|
||||
isochrones={isochrones}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user