Files
walkies/web/src/pages/Schedules.tsx
James Griffin 4989ff1061 Scaffold full-stack volunteer scheduling application
Go backend with domain-based packages (volunteer, schedule, timeoff,
checkin, notification), SQLite storage, JWT auth, and chi router.
React TypeScript frontend with routing, auth context, and pages for
all core features. Multi-stage Dockerfile and docker-compose included.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 11:25:02 -04:00

107 lines
3.3 KiB
TypeScript

import React, { useEffect, useState, FormEvent } from 'react';
import { api, Schedule } from '../api';
import { useAuth } from '../auth';
export default function Schedules() {
const { role } = useAuth();
const [schedules, setSchedules] = useState<Schedule[]>([]);
const [error, setError] = useState('');
const [showForm, setShowForm] = useState(false);
const [form, setForm] = useState({ title: '', starts_at: '', ends_at: '', notes: '' });
useEffect(() => {
api.listSchedules().then(setSchedules).catch(() => setError('Could not load schedules.'));
}, []);
async function handleCreate(e: FormEvent) {
e.preventDefault();
setError('');
try {
const sc = await api.createSchedule(form);
setSchedules(prev => [...prev, sc]);
setForm({ title: '', starts_at: '', ends_at: '', notes: '' });
setShowForm(false);
} catch (err: any) {
setError(err.message);
}
}
async function handleDelete(id: number) {
if (!window.confirm('Delete this schedule?')) return;
try {
await api.deleteSchedule(id);
setSchedules(prev => prev.filter(s => s.id !== id));
} catch (err: any) {
setError(err.message);
}
}
return (
<div className="page">
<div className="page-header">
<h2>Schedules</h2>
{role === 'admin' && (
<button onClick={() => setShowForm(v => !v)}>
{showForm ? 'Cancel' : 'Add Shift'}
</button>
)}
</div>
{error && <p className="error">{error}</p>}
{showForm && (
<form className="card" onSubmit={handleCreate}>
<h3>New Shift</h3>
<label>
Title
<input value={form.title} onChange={e => setForm(f => ({ ...f, title: e.target.value }))} required />
</label>
<label>
Starts At
<input type="datetime-local" value={form.starts_at} onChange={e => setForm(f => ({ ...f, starts_at: e.target.value }))} required />
</label>
<label>
Ends At
<input type="datetime-local" value={form.ends_at} onChange={e => setForm(f => ({ ...f, ends_at: e.target.value }))} required />
</label>
<label>
Notes
<textarea value={form.notes} onChange={e => setForm(f => ({ ...f, notes: e.target.value }))} />
</label>
<button type="submit">Create</button>
</form>
)}
{schedules.length === 0 ? (
<p>No schedules found.</p>
) : (
<table>
<thead>
<tr>
<th>Title</th>
<th>Starts</th>
<th>Ends</th>
<th>Notes</th>
{role === 'admin' && <th>Actions</th>}
</tr>
</thead>
<tbody>
{schedules.map(s => (
<tr key={s.id}>
<td>{s.title}</td>
<td>{new Date(s.starts_at).toLocaleString()}</td>
<td>{new Date(s.ends_at).toLocaleString()}</td>
<td>{s.notes ?? '—'}</td>
{role === 'admin' && (
<td>
<button className="btn-danger btn-small" onClick={() => handleDelete(s.id)}>Delete</button>
</td>
)}
</tr>
))}
</tbody>
</table>
)}
</div>
);
}