feat(report): page shell with two-column layout and PDF download action
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import { pdf } from '@react-pdf/renderer';
|
||||||
|
import type { MuseumRecord } from '../../types';
|
||||||
|
import { DEFAULT_CONFIG, computeReportData } from './reportHelpers';
|
||||||
|
import type { ReportConfig } from './reportHelpers';
|
||||||
|
import { ReportDocument } from './ReportDocument';
|
||||||
|
import ReportForm from './ReportForm';
|
||||||
|
import ReportPreview from './ReportPreview';
|
||||||
|
import { getUniqueMuseums, getUniqueChannels } from '../../services/dataService';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: MuseumRecord[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReportPage({ data }: Props) {
|
||||||
|
const [config, setConfig] = useState<ReportConfig>(DEFAULT_CONFIG);
|
||||||
|
const [generating, setGenerating] = useState(false);
|
||||||
|
|
||||||
|
const allMuseums = getUniqueMuseums(data);
|
||||||
|
const allChannels = getUniqueChannels(data);
|
||||||
|
|
||||||
|
const patch = useCallback((p: Partial<ReportConfig>) => setConfig(c => ({ ...c, ...p })), []);
|
||||||
|
|
||||||
|
const handleGenerate = async () => {
|
||||||
|
setGenerating(true);
|
||||||
|
try {
|
||||||
|
const reportData = computeReportData(data, config);
|
||||||
|
const blob = await pdf(<ReportDocument data={reportData} />).toBlob();
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
const slug = (config.clientName || 'report').toLowerCase().replace(/\s+/g, '-');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `hihala-${slug}-${config.startDate.slice(0, 7)}.pdf`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('PDF generation failed:', err);
|
||||||
|
alert('Failed to generate PDF. Please try again.');
|
||||||
|
} finally {
|
||||||
|
setGenerating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="report-page">
|
||||||
|
<div className="report-header">
|
||||||
|
<h1 className="report-title">Report Builder</h1>
|
||||||
|
<p className="report-sub">Configure and download a client-ready PDF report.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="report-body">
|
||||||
|
<div className="report-form-col">
|
||||||
|
<ReportForm config={config} onChange={patch} allMuseums={allMuseums} allChannels={allChannels} />
|
||||||
|
</div>
|
||||||
|
<div className="report-preview-col">
|
||||||
|
<div className="report-preview-sticky">
|
||||||
|
<ReportPreview config={config} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="report-footer-bar">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="report-generate-btn"
|
||||||
|
onClick={handleGenerate}
|
||||||
|
disabled={generating}
|
||||||
|
>
|
||||||
|
{generating ? (
|
||||||
|
<>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="report-spin" aria-hidden="true">
|
||||||
|
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
||||||
|
</svg>
|
||||||
|
Generating…
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||||
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||||
|
<polyline points="7 10 12 15 17 10"/>
|
||||||
|
<line x1="12" y1="15" x2="12" y2="3"/>
|
||||||
|
</svg>
|
||||||
|
Download PDF
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user