VirtualPR/frontend/src/views/virtualpr/program-sessions/index.jsx

122 lines
4.8 KiB
JavaScript

import { useEffect, useState } from 'react';
import { Button, Card, CardBody, Container, FormControl, FormLabel, Modal, ModalBody, ModalFooter, ModalHeader, Table } from 'react-bootstrap';
import PageBreadcrumb from '@/components/PageBreadcrumb';
import SessionVitalsModal, { rosterFromSession } from '@/components/SessionVitalsModal';
import { api } from '@/lib/api';
export default function ProgramSessionsPage() {
const [items, setItems] = useState([]);
const [error, setError] = useState('');
const [roster, setRoster] = useState(null);
const [cancel, setCancel] = useState({ id: '', reason: '' });
const [reminding, setReminding] = useState('');
const load = async () => {
const data = await api('/api/program-sessions?week=true');
setItems(data.items || []);
};
useEffect(() => {
load().catch((err) => setError(err.message));
}, []);
const openRoster = async (session) => {
setError('');
try {
setRoster(rosterFromSession(await api(`/api/cohort-sessions/${session.id}`)));
} catch (err) {
setError(err.message);
}
};
return (
<Container fluid>
<PageBreadcrumb title="Program sessions" subtitle="Program" />
{error ? <div className="alert alert-danger">{error}</div> : null}
<Card>
<CardBody>
<h5 className="mb-3">This week</h5>
<Table responsive className="align-middle mb-0">
<thead>
<tr>
<th>Date</th>
<th>Cohort</th>
<th>Slot</th>
<th>Status</th>
<th>Meet</th>
<th />
</tr>
</thead>
<tbody>
{items.map((session) => (
<tr key={session.id}>
<td>{session.date} #{session.index}</td>
<td>{session.cohort?.name || '—'}</td>
<td>{session.slot}</td>
<td className="text-capitalize">{session.status}</td>
<td>
{session.meetUrl ? <a href={session.meetUrl} target="_blank" rel="noreferrer">Join</a> : '—'}
</td>
<td className="text-nowrap">
{session.status === 'scheduled' ? (
<>
<Button size="sm" variant="outline-danger" className="me-1" onClick={() => setCancel({ id: session.id, reason: '' })}>Cancel</Button>
<Button
size="sm"
variant="outline-primary"
className="me-1"
disabled={reminding === session.id}
onClick={async () => {
setError('');
setReminding(session.id);
try {
await api(`/api/cohort-sessions/${session.id}/whatsapp-reminder`, { method: 'POST' });
} catch (err) {
setError(err.message);
} finally {
setReminding('');
}
}}
>
{reminding === session.id ? 'Sending…' : 'WhatsApp'}
</Button>
</>
) : null}
{session.status !== 'cancelled' ? (
<Button size="sm" onClick={() => openRoster(session)}>Vitals</Button>
) : null}
</td>
</tr>
))}
{!items.length ? <tr><td colSpan={6} className="text-muted">No sessions this week.</td></tr> : null}
</tbody>
</Table>
</CardBody>
</Card>
<Modal show={Boolean(cancel.id)} onHide={() => setCancel({ id: '', reason: '' })}>
<ModalHeader closeButton><h5 className="modal-title">Cancel and push session</h5></ModalHeader>
<ModalBody>
<FormLabel>Reason</FormLabel>
<FormControl as="textarea" rows={3} value={cancel.reason} onChange={(e) => setCancel((current) => ({ ...current, reason: e.target.value }))} />
</ModalBody>
<ModalFooter>
<Button variant="light" onClick={() => setCancel({ id: '', reason: '' })}>Close</Button>
<Button variant="danger" disabled={!cancel.reason.trim()} onClick={async () => {
await api(`/api/cohort-sessions/${cancel.id}/cancel`, { method: 'POST', body: { reason: cancel.reason } });
setCancel({ id: '', reason: '' });
await load();
}}>Cancel and extend</Button>
</ModalFooter>
</Modal>
<SessionVitalsModal
roster={roster}
onChange={setRoster}
onClose={() => setRoster(null)}
onSaved={load}
/>
</Container>
);
}