VirtualPR/frontend/src/views/virtualpr/reports/index.jsx

111 lines
4.5 KiB
JavaScript

import { useEffect, useState } from 'react';
import { Button, Card, CardBody, Col, Container, Row, Table } from 'react-bootstrap';
import PageBreadcrumb from '@/components/PageBreadcrumb';
import { api } from '@/lib/api';
export default function ReportsPage() {
const [patients, setPatients] = useState([]);
const [utilization, setUtilization] = useState([]);
const [productivity, setProductivity] = useState({ sessionsRun: 0, attendance: 0, possibleAttendance: 0, reassignments: [] });
const [error, setError] = useState('');
useEffect(() => {
Promise.all([
api('/api/reports/patients'),
api('/api/reports/utilization'),
api('/api/reports/productivity'),
]).then(([a, b, c]) => {
setPatients(a.items || []);
setUtilization(b.items || []);
setProductivity(c);
}).catch((err) => setError(err.message));
}, []);
const download = (type) => {
fetch(`/api/reports/export?type=${type}`, { credentials: 'include' })
.then((res) => res.blob())
.then((blob) => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${type}-report.csv`;
link.click();
URL.revokeObjectURL(url);
})
.catch((err) => setError(err.message));
};
return (
<Container fluid>
<PageBreadcrumb title="Reports" subtitle="Program" />
{error ? <div className="alert alert-danger">{error}</div> : null}
<div className="d-flex gap-2 mb-3">
<Button variant="outline-primary" onClick={() => download('patients')}>Export patients CSV</Button>
<Button variant="outline-primary" onClick={() => download('utilization')}>Export utilization CSV</Button>
<Button variant="outline-primary" onClick={() => download('vitals')}>Export vitals CSV</Button>
</div>
<Row className="g-3">
<Col lg={7}>
<Card>
<CardBody>
<h5 className="mb-3">Patient progress</h5>
<Table responsive size="sm" className="mb-0">
<thead><tr><th>Patient</th><th>Cohort</th><th>PRT</th><th>First session</th><th>Completed</th><th>Assigned</th><th>Attendance</th></tr></thead>
<tbody>
{patients.map((item) => (
<tr key={item.id}>
<td>{item.name}<div className="small text-muted">{item.diseaseType}</div></td>
<td>{item.cohort?.name || '—'}</td>
<td>{item.prt ? `${item.prt.firstName} ${item.prt.lastName}` : '—'}</td>
<td>{item.firstSession || '—'}</td>
<td>{item.completed}</td>
<td>{item.assigned}</td>
<td>{item.completion}</td>
</tr>
))}
{!patients.length ? <tr><td colSpan={7} className="text-muted">No confirmed patients.</td></tr> : null}
</tbody>
</Table>
</CardBody>
</Card>
</Col>
<Col lg={5}>
<Card className="mb-3">
<CardBody>
<h5 className="mb-3">Utilization</h5>
<Table responsive size="sm" className="mb-0">
<thead><tr><th>PRT</th><th>Cohorts</th><th>Idle</th><th>Cancelled</th></tr></thead>
<tbody>
{utilization.map((item) => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.used}/{item.max}</td>
<td>{item.idle}</td>
<td>{item.cancelled}</td>
</tr>
))}
</tbody>
</Table>
</CardBody>
</Card>
<Card>
<CardBody>
<h5 className="mb-2">Productivity</h5>
<p className="mb-1">Sessions run: {productivity.sessionsRun}</p>
<p className="mb-3">Attendance: {productivity.attendance}/{productivity.possibleAttendance}</p>
<h6>Recent reassignments</h6>
{(productivity.reassignments || []).length ? (
<ul className="mb-0">
{productivity.reassignments.map((item) => (
<li key={item.id}>{item.date}: {item.cohort} {item.from} → {item.to || 'open'} ({item.reason})</li>
))}
</ul>
) : <p className="text-muted mb-0">None yet.</p>}
</CardBody>
</Card>
</Col>
</Row>
</Container>
);
}