105 lines
3.8 KiB
JavaScript
105 lines
3.8 KiB
JavaScript
import { useState } from 'react';
|
|
import { Button, Card, Spinner, Table } from 'react-bootstrap';
|
|
import clsx from 'clsx';
|
|
import { exportTableToXlsx } from './exportXlsx';
|
|
import { formatCell, headerRows, rowValue } from './helpers';
|
|
|
|
export default function ReportSection({ title, table, loading, error, filename, stickyFirstColumn }) {
|
|
const [exporting, setExporting] = useState(false);
|
|
const [exportError, setExportError] = useState('');
|
|
const { columns, rows, total, bordered } = table;
|
|
const headers = headerRows(columns);
|
|
|
|
const handleExport = async () => {
|
|
setExporting(true);
|
|
setExportError('');
|
|
try {
|
|
await exportTableToXlsx(table, filename);
|
|
} catch (err) {
|
|
setExportError(err.message || 'Unable to export report');
|
|
} finally {
|
|
setExporting(false);
|
|
}
|
|
};
|
|
|
|
const cellClass = (index) =>
|
|
clsx(stickyFirstColumn && index === 0 && 'sticky-first-col', columns[index]?.className);
|
|
|
|
return (
|
|
<Card className="mb-3">
|
|
<Card.Header className="d-flex justify-content-between align-items-center">
|
|
<h5 className="card-title mb-0">{title}</h5>
|
|
<Button size="sm" variant="primary" onClick={handleExport} disabled={loading || exporting || !columns.length}>
|
|
{exporting ? 'Exporting...' : 'Export'}
|
|
</Button>
|
|
</Card.Header>
|
|
<Card.Body className="position-relative">
|
|
{error ? <div className="alert alert-danger mb-2">{error}</div> : null}
|
|
{exportError ? <div className="alert alert-danger mb-2">{exportError}</div> : null}
|
|
{loading ? (
|
|
<div className="gennova-report-loading">
|
|
<Spinner animation="border" />
|
|
</div>
|
|
) : null}
|
|
{columns.length ? (
|
|
<div className="gennova-report-scroll">
|
|
<Table
|
|
className={clsx(
|
|
'table-centered table-nowrap mb-0 gennova-report-table',
|
|
bordered ? 'transplant-summary-table' : 'table-borderless',
|
|
)}
|
|
>
|
|
<thead className="table-light">
|
|
{headers.map((cells, rowIndex) => (
|
|
<tr key={rowIndex}>
|
|
{cells.map((cell) => (
|
|
<th
|
|
key={`${rowIndex}-${cell.column}`}
|
|
colSpan={cell.colSpan}
|
|
rowSpan={cell.rowSpan}
|
|
className={clsx(cellClass(cell.column), cell.colSpan > 1 && 'text-center')}
|
|
>
|
|
{cell.label}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</thead>
|
|
<tbody>
|
|
{rows.length ? (
|
|
rows.map((row, rowIndex) => (
|
|
<tr key={rowIndex}>
|
|
{columns.map((column, index) => (
|
|
<td key={index} className={cellClass(index)}>
|
|
{formatCell(rowValue(row, column), column.format)}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))
|
|
) : (
|
|
<tr>
|
|
<td colSpan={columns.length} className="text-center text-muted py-3">
|
|
No data
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{total ? (
|
|
<tr className="table-secondary fw-bold">
|
|
{total.map((value, index) => (
|
|
<td key={index} className={cellClass(index)}>
|
|
{formatCell(value, columns[index]?.format)}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
) : null}
|
|
</tbody>
|
|
</Table>
|
|
</div>
|
|
) : (
|
|
<div className="text-center text-muted py-3">{loading ? 'Loading...' : 'No data'}</div>
|
|
)}
|
|
</Card.Body>
|
|
</Card>
|
|
);
|
|
}
|