|
|
|
@@ -0,0 +1,167 @@ |
|
|
|
"use client"; |
|
|
|
|
|
|
|
import React, { useState, useMemo } from 'react'; |
|
|
|
import { |
|
|
|
Box, |
|
|
|
Card, |
|
|
|
CardContent, |
|
|
|
Typography, |
|
|
|
MenuItem, |
|
|
|
TextField, |
|
|
|
Button, |
|
|
|
Grid, |
|
|
|
Divider |
|
|
|
} from '@mui/material'; |
|
|
|
import PrintIcon from '@mui/icons-material/Print'; |
|
|
|
import { REPORTS, ReportDefinition } from '@/config/reportConfig'; |
|
|
|
import { getSession } from "next-auth/react"; |
|
|
|
|
|
|
|
export default function ReportPage() { |
|
|
|
const [selectedReportId, setSelectedReportId] = useState<string>(''); |
|
|
|
const [criteria, setCriteria] = useState<Record<string, string>>({}); |
|
|
|
const [loading, setLoading] = useState(false); |
|
|
|
|
|
|
|
// Find the configuration for the currently selected report |
|
|
|
const currentReport = useMemo(() => |
|
|
|
REPORTS.find((r) => r.id === selectedReportId), |
|
|
|
[selectedReportId]); |
|
|
|
|
|
|
|
const handleReportChange = (event: React.ChangeEvent<HTMLInputElement>) => { |
|
|
|
setSelectedReportId(event.target.value); |
|
|
|
setCriteria({}); // Clear criteria when switching reports |
|
|
|
}; |
|
|
|
|
|
|
|
const handleFieldChange = (name: string, value: string) => { |
|
|
|
setCriteria((prev) => ({ ...prev, [name]: value })); |
|
|
|
}; |
|
|
|
|
|
|
|
const handlePrint = async () => { |
|
|
|
if (!currentReport) return; |
|
|
|
|
|
|
|
// 1. Mandatory Field Validation |
|
|
|
const missingFields = currentReport.fields |
|
|
|
.filter(field => field.required && !criteria[field.name]) |
|
|
|
.map(field => field.label); |
|
|
|
|
|
|
|
if (missingFields.length > 0) { |
|
|
|
alert(`Please enter the following mandatory fields:\n- ${missingFields.join('\n- ')}`); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
setLoading(true); |
|
|
|
try { |
|
|
|
const token = localStorage.getItem("accessToken"); |
|
|
|
const queryParams = new URLSearchParams(criteria).toString(); |
|
|
|
const url = `${currentReport.apiEndpoint}?${queryParams}`; |
|
|
|
|
|
|
|
const response = await fetch(url, { |
|
|
|
method: 'GET', |
|
|
|
headers: { |
|
|
|
'Authorization': `Bearer ${token}`, |
|
|
|
'Accept': 'application/pdf', |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); |
|
|
|
|
|
|
|
const blob = await response.blob(); |
|
|
|
const downloadUrl = window.URL.createObjectURL(blob); |
|
|
|
const link = document.createElement('a'); |
|
|
|
link.href = downloadUrl; |
|
|
|
|
|
|
|
const contentDisposition = response.headers.get('Content-Disposition'); |
|
|
|
let fileName = `${currentReport.title}.pdf`; |
|
|
|
if (contentDisposition?.includes('filename=')) { |
|
|
|
fileName = contentDisposition.split('filename=')[1].split(';')[0].replace(/"/g, ''); |
|
|
|
} |
|
|
|
|
|
|
|
link.setAttribute('download', fileName); |
|
|
|
document.body.appendChild(link); |
|
|
|
link.click(); |
|
|
|
link.remove(); |
|
|
|
window.URL.revokeObjectURL(downloadUrl); |
|
|
|
} catch (error) { |
|
|
|
console.error("Failed to generate report:", error); |
|
|
|
alert("An error occurred while generating the report. Please try again."); |
|
|
|
} finally { |
|
|
|
setLoading(false); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
return ( |
|
|
|
<Box sx={{ p: 4, maxWidth: 1000, margin: '0 auto' }}> |
|
|
|
<Typography variant="h4" gutterBottom fontWeight="bold"> |
|
|
|
Report Management |
|
|
|
</Typography> |
|
|
|
|
|
|
|
<Card sx={{ mb: 4, boxShadow: 3 }}> |
|
|
|
<CardContent> |
|
|
|
<Typography variant="h6" gutterBottom> |
|
|
|
Select Report Type |
|
|
|
</Typography> |
|
|
|
<TextField |
|
|
|
select |
|
|
|
fullWidth |
|
|
|
label="Report List" |
|
|
|
value={selectedReportId} |
|
|
|
onChange={handleReportChange} |
|
|
|
helperText="Please select which report you want to generate" |
|
|
|
> |
|
|
|
{REPORTS.map((report) => ( |
|
|
|
<MenuItem key={report.id} value={report.id}> |
|
|
|
{report.title} |
|
|
|
</MenuItem> |
|
|
|
))} |
|
|
|
</TextField> |
|
|
|
</CardContent> |
|
|
|
</Card> |
|
|
|
|
|
|
|
{currentReport && ( |
|
|
|
<Card sx={{ boxShadow: 3, animation: 'fadeIn 0.5s' }}> |
|
|
|
<CardContent> |
|
|
|
<Typography variant="h6" color="primary" gutterBottom> |
|
|
|
Search Criteria: {currentReport.title} |
|
|
|
</Typography> |
|
|
|
<Divider sx={{ mb: 3 }} /> |
|
|
|
|
|
|
|
<Grid container spacing={3}> |
|
|
|
{currentReport.fields.map((field) => ( |
|
|
|
<Grid item xs={12} sm={6} key={field.name}> |
|
|
|
<TextField |
|
|
|
fullWidth |
|
|
|
label={field.label} |
|
|
|
type={field.type} |
|
|
|
placeholder={field.placeholder} |
|
|
|
InputLabelProps={field.type === 'date' ? { shrink: true } : {}} |
|
|
|
onChange={(e) => handleFieldChange(field.name, e.target.value)} |
|
|
|
value={criteria[field.name] || ''} |
|
|
|
select={field.type === 'select'} |
|
|
|
> |
|
|
|
{field.type === 'select' && field.options?.map((opt) => ( |
|
|
|
<MenuItem key={opt.value} value={opt.value}> |
|
|
|
{opt.label} |
|
|
|
</MenuItem> |
|
|
|
))} |
|
|
|
</TextField> |
|
|
|
</Grid> |
|
|
|
))} |
|
|
|
</Grid> |
|
|
|
|
|
|
|
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'flex-end' }}> |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
size="large" |
|
|
|
startIcon={<PrintIcon />} |
|
|
|
onClick={handlePrint} |
|
|
|
disabled={loading} |
|
|
|
sx={{ px: 4 }} |
|
|
|
> |
|
|
|
{loading ? "Generating..." : "Print Report"} |
|
|
|
</Button> |
|
|
|
</Box> |
|
|
|
</CardContent> |
|
|
|
</Card> |
|
|
|
)} |
|
|
|
</Box> |
|
|
|
); |
|
|
|
} |