import React, { useEffect, useState, FormEvent } from 'react'; import { api, ApiError, TimeOffRequest, ConflictingShift, Volunteer } from '../api'; import { useAuth } from '../auth'; export default function TimeOff() { const { role, volunteerID } = useAuth(); const [requests, setRequests] = useState([]); const [volunteers, setVolunteers] = useState([]); const [error, setError] = useState(''); const [showForm, setShowForm] = useState(false); const [form, setForm] = useState({ starts_at: '', ends_at: '', reason: '', volunteer_id: 0 }); const [editingId, setEditingId] = useState(null); const [conflicts, setConflicts] = useState(null); const [deletePreview, setDeletePreview] = useState<{ id: number; shifts: ConflictingShift[] } | null>(null); useEffect(() => { api.listTimeOff().then(setRequests).catch(() => setError('Could not load requests.')); if (role === 'admin') { api.listVolunteers().then(vols => setVolunteers(vols as Volunteer[])); } }, [role]); async function handleCreate(e: FormEvent) { e.preventDefault(); setError(''); try { const payload: any = { starts_at: form.starts_at, ends_at: form.ends_at, reason: form.reason }; if (role === 'admin' && form.volunteer_id > 0) { payload.volunteer_id = form.volunteer_id; } if (conflicts) { payload.confirm_conflicts = true; } const result = await api.createTimeOff(payload); setRequests(prev => [result as TimeOffRequest, ...prev]); resetForm(); } catch (err: any) { if (err instanceof ApiError && err.status === 409 && err.data?.conflicts) { setConflicts(err.data.conflicts); return; } setError(err.message); } } async function handleUpdate(e: FormEvent) { e.preventDefault(); if (!editingId) return; setError(''); try { const req = await api.updateTimeOff(editingId, { starts_at: form.starts_at, ends_at: form.ends_at, reason: form.reason, }); setRequests(prev => prev.map(r => r.id === editingId ? req : r)); resetForm(); } catch (err: any) { setError(err.message); } } async function handleDelete(id: number) { setError(''); try { const result = await api.deleteTimeOff(id); setRequests(prev => prev.filter(r => r.id !== id)); setDeletePreview(null); if (result.restored_shifts?.length > 0) { setError(`Restored volunteer to ${result.restored_shifts.length} shift(s).`); } } catch (err: any) { setError(err.message); } } async function handleDeleteClick(id: number) { if (role === 'admin') { try { const shifts = await api.getRemovedShifts(id); if (shifts.length > 0) { setDeletePreview({ id, shifts }); return; } } catch { // If we can't fetch preview, proceed with confirm } } if (window.confirm('Delete this time off request?')) { handleDelete(id); } } async function handleReview(id: number, status: 'approved' | 'rejected') { try { const req = await api.reviewTimeOff(id, status); setRequests(prev => prev.map(r => r.id === id ? req : r)); } catch (err: any) { setError(err.message); } } function startEdit(r: TimeOffRequest) { setEditingId(r.id); setForm({ starts_at: r.starts_at.split('T')[0], ends_at: r.ends_at.split('T')[0], reason: r.reason ?? '', volunteer_id: 0, }); setShowForm(true); setConflicts(null); } function resetForm() { setForm({ starts_at: '', ends_at: '', reason: '', volunteer_id: 0 }); setShowForm(false); setEditingId(null); setConflicts(null); } function canEditOrDelete(r: TimeOffRequest): boolean { if (role === 'admin') return true; if (r.volunteer_id !== volunteerID) return false; return new Date(r.starts_at) > new Date(); } const statusClass = (status: string) => { if (status === 'approved') return 'status-approved'; if (status === 'rejected') return 'status-rejected'; return 'status-pending'; }; const volunteerName = (vid: number) => { const v = volunteers.find(v => v.id === vid); return v ? v.name : `#${vid}`; }; return (

Time Off Requests

{error &&

{error}

} {showForm && (

{editingId ? 'Edit Request' : 'New Request'}

{role === 'admin' && !editingId && ( )}