Files
The-transit-Project/admin_ui/src/components/StopsManager.jsx
T
Sven laptop 68490f8fd5 scheduling feature
Co-authored-by: Copilot <copilot@github.com>
2026-05-06 15:58:02 +02:00

144 lines
4.2 KiB
React

import React, { useEffect, useState } from 'react'
import { transitClient } from '../api'
export default function StopsManager({ token, user }) {
const client = transitClient(token)
const [stops, setStops] = useState([])
const [name, setName] = useState('')
const [lat, setLat] = useState('')
const [lon, setLon] = useState('')
const [message, setMessage] = useState(null)
const [isAdmin] = useState(Boolean(user?.userCode))
const [deleteConfirm, setDeleteConfirm] = useState(null)
const loadStops = async () => {
try {
const res = await client.get('/admin/stops')
setStops(res.data.stops || [])
} catch (err) {
setMessage('Error: ' + (err.response?.data?.error || err.message))
}
}
useEffect(() => {
loadStops()
}, [])
const createStop = async (e) => {
e.preventDefault()
setMessage(null)
try {
const res = await client.post('/admin/stops', {
name,
latitude: parseFloat(lat),
longitude: parseFloat(lon)
})
setMessage('Stop created: #' + res.data.stop.id)
setName(''); setLat(''); setLon('')
await loadStops()
} catch (err) {
const errMsg = err.response?.data?.error || err.message
if (err.response?.status === 401) {
setMessage('Unauthorized: invalid token')
} else if (err.response?.status === 403) {
setMessage('Forbidden: you are not an admin')
} else {
setMessage('Error: ' + errMsg)
}
}
}
const deleteStop = async (stopId, stopName) => {
setMessage(null)
try {
await client.delete(`/admin/stops/${stopId}`)
setMessage(`Halte "${stopName}" verwijderd`)
setDeleteConfirm(null)
await loadStops()
} catch (err) {
const errMsg = err.response?.data?.error || err.message
setMessage('Error: ' + errMsg)
}
}
return (
<section className="card">
<div className="section-header">
<div>
<h2>Stops</h2>
<p>Maak herbruikbare haltes aan.</p>
</div>
<button className="secondary" onClick={loadStops}>Refresh</button>
</div>
<div className="list-grid">
{stops.map(stop => (
<article className="mini-card" key={stop.id}>
<strong>#{stop.id} {stop.name}</strong>
<div>{stop.latitude}, {stop.longitude}</div>
{isAdmin && (
<button
className="danger"
onClick={() => setDeleteConfirm(stop)}
style={{ marginTop: '0.5rem', width: '100%' }}
>
Delete
</button>
)}
</article>
))}
</div>
{isAdmin ? (
<form onSubmit={createStop} className="stack-form">
<div className="form-row">
<input placeholder="Name" value={name} onChange={e=>setName(e.target.value)} />
<input placeholder="Latitude" value={lat} onChange={e=>setLat(e.target.value)} />
<input placeholder="Longitude" value={lon} onChange={e=>setLon(e.target.value)} />
<button type="submit">Create Stop</button>
</div>
</form>
) : (
<div>You must be admin to create stops.</div>
)}
{message && <div className="message">{message}</div>}
{deleteConfirm && (
<div style={overlay}>
<div style={modal}>
<h3>Delete Stop?</h3>
<p><strong>{deleteConfirm.name}</strong></p>
<p style={{ color: '#999', fontSize: '0.9rem' }}>This cannot be undone.</p>
<div className="form-row" style={{ marginTop: '1rem' }}>
<button className="secondary" onClick={() => setDeleteConfirm(null)}>Cancel</button>
<button className="danger" onClick={() => deleteStop(deleteConfirm.id, deleteConfirm.name)}>
Delete
</button>
</div>
</div>
</div>
)}
</section>
)
}
const overlay = {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000,
}
const modal = {
backgroundColor: '#fff',
padding: '2rem',
borderRadius: '8px',
maxWidth: '400px',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
}