Rapport_React/src/views/gennovadcreport/ReportSection.jsx

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>
);
}