// v0.6-4 TownAnchors table editor. import { useEffect, useState, useCallback } from 'react' import { Loader2, Plus, Trash2, Check, X, MapPin } from 'lucide-react' interface TownAnchor { anchor_id: number name: string lat: number lon: number state: string | null enabled: boolean updated_at: number } const EMPTY_DRAFT: TownAnchor = { anchor_id: 0, name: '', lat: 0, lon: 0, state: 'ID', enabled: true, updated_at: 0, } export default function TownAnchors() { const [rows, setRows] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [editing, setEditing] = useState(null) const [adding, setAdding] = useState(false) const [draft, setDraft] = useState(EMPTY_DRAFT) const refresh = useCallback(async () => { setLoading(true); setError(null) try { const res = await fetch('/api/town-anchors') if (!res.ok) throw new Error(`GET: ${res.status}`) setRows(await res.json()) } catch (e) { setError(String(e)) } finally { setLoading(false) } }, []) useEffect(() => { refresh() }, [refresh]) const beginEdit = (r: TownAnchor) => { setEditing(r.anchor_id); setDraft({ ...r }); setAdding(false) } const beginAdd = () => { setAdding(true); setEditing(null); setDraft({ ...EMPTY_DRAFT }) } const cancel = () => { setEditing(null); setAdding(false); setDraft(EMPTY_DRAFT) } const save = async () => { const url = adding ? '/api/town-anchors' : `/api/town-anchors/${editing}` const method = adding ? 'POST' : 'PUT' const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(draft), }) if (!res.ok) { const b = await res.json().catch(() => ({})) alert(`save failed: ${b.detail || res.statusText}`); return } cancel(); refresh() } const remove = async (id: number) => { if (!confirm(`Delete anchor ${id}?`)) return const res = await fetch(`/api/town-anchors/${id}`, { method: 'DELETE' }) if (!res.ok) { alert(`delete failed: ${res.status}`); return } refresh() } if (loading) return
Loading…
if (error) return
Load failed: {error}
return (

Town Anchors

{rows.length} towns

Lookup table for the "X mi <bearing> of <town>" anchor in wire-string rendering. Disabled rows fall through to the generic anchor chain.

{adding && }
{rows.map(r => editing === r.anchor_id ? ( ) : ( ))}
Name Lat Lon State On
{r.name} {r.lat.toFixed(4)} {r.lon.toFixed(4)} {r.state || '-'} {r.enabled ? : }
) } function RowEditor({ draft, setDraft, onSave, onCancel, adding }: { draft: TownAnchor, setDraft: (t: TownAnchor) => void, onSave: () => void, onCancel: () => void, adding?: boolean, }) { const upd = (k: keyof TownAnchor, v: unknown) => setDraft({ ...draft, [k]: v }) return (
) }