122 lines
4.8 KiB
JavaScript
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>
|
|
);
|
|
}
|