diff --git a/.env.local b/.env.local index 27570e5..acf8695 100644 --- a/.env.local +++ b/.env.local @@ -1 +1,6 @@ -DATABASE_URL="postgres://neondb_owner:npg_fthjFP8Ece2Z@ep-icy-leaf-afdbv5ts-pooler.c-2.us-west-2.aws.neon.tech/neondb?sslmode=require" +# .env +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=databas # Name of your PostgreSQL database +DB_USER=postgres # Your PostgreSQL username +DB_PASSWORD=blackEARTH3018334 \ No newline at end of file diff --git a/lib/db.js b/lib/db.js index 0bf2a63..fcf643c 100644 --- a/lib/db.js +++ b/lib/db.js @@ -1,13 +1,20 @@ -import bcrypt from 'bcrypt'; -import pkg from 'pg'; -const { Pool } = pkg; +const { Pool } = require('pg'); const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: { - rejectUnauthorized: false, - }, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + database: process.env.DB_NAME, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, }); -export { bcrypt }; -export default pool; +// Test the connection +pool.query('SELECT NOW()', (err, res) => { + if (err) { + console.error('Database connection error:', err); + } else { + console.log('Database connected successfully'); + } +}); + +module.exports = pool; \ No newline at end of file diff --git a/src/app/api/reports/insurance-analysis/route.js b/src/app/api/reports/insurance-analysis/route.js index 15ed5ea..d37234a 100644 --- a/src/app/api/reports/insurance-analysis/route.js +++ b/src/app/api/reports/insurance-analysis/route.js @@ -1,147 +1,71 @@ -import pool from '../../../../../lib/db'; import { NextResponse } from 'next/server'; +import pool from '../../../../../lib/db'; export async function GET(request) { try { const { searchParams } = new URL(request.url); const startDate = searchParams.get('startDate'); const endDate = searchParams.get('endDate'); - const analysisType = searchParams.get('type') || 'detailed'; // detailed or summary - if (analysisType === 'summary') { - // Monthly summary view - let summaryQuery = ` + let query = ` + WITH monthly_data AS ( SELECT - DATE_TRUNC('month', a.date) AS month_year, - COUNT(DISTINCT a.appointment_id) AS total_appointments, - SUM(COALESCE(pay.amount, 0)) AS total_payments, - SUM(COALESCE(ic.amount_approved, 0)) AS total_insurance_covered, - SUM(COALESCE(pay.amount, 0) - COALESCE(ic.amount_approved, 0)) AS total_out_of_pocket, - CASE - WHEN SUM(COALESCE(pay.amount, 0)) > 0 THEN - (SUM(COALESCE(ic.amount_approved, 0)) / SUM(pay.amount) * 100) - ELSE 0 - END AS overall_insurance_coverage_percentage, - COUNT(DISTINCT CASE WHEN ic.amount_approved > 0 THEN a.appointment_id END) AS appointments_with_insurance, - COUNT(DISTINCT CASE WHEN ic.amount_approved IS NULL OR ic.amount_approved = 0 THEN a.appointment_id END) AS appointments_without_insurance + DATE_TRUNC('month', a.date) as month_year, + COUNT(DISTINCT a.appointment_id) as total_appointments, + SUM(p.amount) as total_payments, + SUM(COALESCE(ic.amount_approved, 0)) as total_insurance_covered, + SUM(p.amount - COALESCE(ic.amount_approved, 0)) as total_out_of_pocket FROM appointment a - LEFT JOIN payment pay ON a.appointment_id = pay.appointment_id - LEFT JOIN insurance_claim ic ON a.appointment_id = ic.appointment_id AND ic.status = 'Approved' - WHERE a.date IS NOT NULL - `; - - const queryParams = []; - let paramIndex = 1; - - if (startDate) { - summaryQuery += ` AND a.date >= $${paramIndex}`; - queryParams.push(startDate); - paramIndex++; - } - - if (endDate) { - summaryQuery += ` AND a.date <= $${paramIndex}`; - queryParams.push(endDate); - paramIndex++; - } - - summaryQuery += ` - GROUP BY DATE_TRUNC('month', a.date) - ORDER BY month_year DESC - `; - - const summaryResult = await pool.query(summaryQuery, queryParams); - - return NextResponse.json({ - success: true, - type: 'summary', - data: summaryResult.rows, - summary: { - totalRecords: summaryResult.rows.length, - dateRange: { start: startDate, end: endDate } - } - }, { status: 200 }); - - } else { - // Detailed view - let detailedQuery = ` - SELECT - a.appointment_id, - p.patient_id, - p.name AS patient_name, - a.date AS appointment_date, - COALESCE(pay.amount, 0) AS total_payment, - COALESCE(ic.amount_approved, 0) AS insurance_covered, - COALESCE(pay.amount, 0) - COALESCE(ic.amount_approved, 0) AS out_of_pocket, - CASE - WHEN COALESCE(pay.amount, 0) > 0 THEN - (COALESCE(ic.amount_approved, 0) / pay.amount * 100) - ELSE 0 - END AS insurance_coverage_percentage, - ip.policy_name, - ip.coverage_percentage AS policy_coverage_rate, - ic.status AS claim_status, - ic.amount_claimed, - d.name AS doctor_name, - b.name AS branch_name - FROM appointment a - INNER JOIN patient p ON a.patient_id = p.patient_id - LEFT JOIN payment pay ON a.appointment_id = pay.appointment_id + INNER JOIN payment p ON a.appointment_id = p.appointment_id LEFT JOIN insurance_claim ic ON a.appointment_id = ic.appointment_id - LEFT JOIN insurance_policy ip ON p.insurance_id = ip.insurance_id - LEFT JOIN doctor d ON a.doctor_id = d.doctor_id - LEFT JOIN branch b ON a.branch_id = b.branch_id - WHERE (pay.amount IS NOT NULL OR ic.amount_approved IS NOT NULL) - `; - - const queryParams = []; - let paramIndex = 1; - - if (startDate) { - detailedQuery += ` AND a.date >= $${paramIndex}`; - queryParams.push(startDate); - paramIndex++; - } - - if (endDate) { - detailedQuery += ` AND a.date <= $${paramIndex}`; - queryParams.push(endDate); - paramIndex++; - } - - detailedQuery += ` ORDER BY a.date DESC`; - - const detailedResult = await pool.query(detailedQuery, queryParams); - - // Calculate summary statistics - const totalPayments = detailedResult.rows.reduce((sum, row) => sum + parseFloat(row.total_payment), 0); - const totalInsuranceCovered = detailedResult.rows.reduce((sum, row) => sum + parseFloat(row.insurance_covered), 0); - const totalOutOfPocket = detailedResult.rows.reduce((sum, row) => sum + parseFloat(row.out_of_pocket), 0); - const appointmentsWithInsurance = detailedResult.rows.filter(row => parseFloat(row.insurance_covered) > 0).length; + WHERE a.status = 'Completed' + ${startDate ? "AND a.date >= $1" : ""} + ${endDate ? `AND a.date <= $${startDate ? "2" : "1"}` : ""} + GROUP BY DATE_TRUNC('month', a.date) + ) + SELECT + to_char(month_year, 'YYYY-MM-DD') as month_year, + total_appointments, + total_payments::numeric(10,2), + total_insurance_covered::numeric(10,2), + total_out_of_pocket::numeric(10,2), + CASE + WHEN total_payments > 0 THEN + ROUND((total_insurance_covered / total_payments * 100)::numeric, 2) + ELSE 0 + END as coverage_percentage + FROM monthly_data + ORDER BY month_year; + `; + + const queryParams = []; + if (startDate) queryParams.push(startDate); + if (endDate) queryParams.push(endDate); + + console.log('Executing query with params:', queryParams); // Debug log + + const result = await pool.query(query, queryParams); + + console.log('Query results:', result.rows); // Debug log + + const summary = { + total_appointments: result.rows.reduce((sum, row) => sum + Number(row.total_appointments), 0), + total_payments: result.rows.reduce((sum, row) => sum + Number(row.total_payments), 0), + total_insurance_covered: result.rows.reduce((sum, row) => sum + Number(row.total_insurance_covered), 0), + total_out_of_pocket: result.rows.reduce((sum, row) => sum + Number(row.total_out_of_pocket), 0), + avg_coverage_percentage: result.rows.length > 0 + ? result.rows.reduce((sum, row) => sum + Number(row.coverage_percentage), 0) / result.rows.length + : 0 + }; - return NextResponse.json({ - success: true, - type: 'detailed', - data: detailedResult.rows, - summary: { - totalAppointments: detailedResult.rows.length, - totalPayments: totalPayments, - totalInsuranceCovered: totalInsuranceCovered, - totalOutOfPocket: totalOutOfPocket, - appointmentsWithInsurance: appointmentsWithInsurance, - appointmentsWithoutInsurance: detailedResult.rows.length - appointmentsWithInsurance, - overallInsuranceCoveragePercentage: totalPayments > 0 ? (totalInsuranceCovered / totalPayments * 100) : 0, - dateRange: { start: startDate, end: endDate } - } - }, { status: 200 }); - } + return NextResponse.json({ + success: true, + data: result.rows, + summary + }); } catch (error) { - console.error('Insurance coverage analysis error:', error); - return NextResponse.json({ - success: false, - error: 'Failed to generate insurance coverage analysis', - details: error.message - }, { status: 500 }); + console.error('Insurance analysis error:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); } } \ No newline at end of file diff --git a/src/app/reports/page.js b/src/app/reports/page.js index 3a519af..bf5f485 100644 --- a/src/app/reports/page.js +++ b/src/app/reports/page.js @@ -1,696 +1,213 @@ 'use client'; -import Sidebar from '../components/Sidebar'; -import { useEffect, useState } from 'react'; -import { BarChart, LineChart, PieChart } from '../components/Chart'; -export default function ReportsPage() { +import { useState, useEffect } from 'react'; +import { Chart as ChartJS } from 'chart.js/auto'; +import { Bar } from 'react-chartjs-2'; +import dynamic from 'next/dynamic'; + +// Dynamic imports to prevent hydration mismatch +const Sidebar = dynamic(() => import('../components/Sidebar'), { ssr: false }); +const ReportsTopBar = dynamic(() => import('../components/ReportsTopBar'), { ssr: false }); + +export default function InsuranceClaimsReport() { const [collapsed, setCollapsed] = useState(false); - const [activeReport, setActiveReport] = useState(null); - const [reportData, setReportData] = useState(null); - const [filters, setFilters] = useState({ - date: '', - startDate: '', - endDate: '', - branchId: '', - doctorId: '', - }); - const [loading, setLoading] = useState(false); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); const [error, setError] = useState(''); - const [userRole, setUserRole] = useState(''); + const [dateRange, setDateRange] = useState({ + startDate: '2025-01-01', + endDate: '2025-12-31' + }); useEffect(() => { - if (typeof window !== 'undefined') { - const role = localStorage.getItem('role'); - setUserRole(role || ''); - - // Redirect receptionists away from reports - if (role === 'reception') { - window.location.href = '/dashboard'; - return; - } - } - }, []); + fetchData(); + }, [dateRange]); - // Only show doctor revenue report for doctors - const reports = userRole === 'doctor' - ? [ - { - id: 'doctor-revenue', - title: 'Doctor Revenue Report', - description: 'Revenue and patient counts by doctor', - icon: '👨‍⚕️', - endpoint: '/api/reports/doctor-revenue', - type: 'date-range' - } - ] - : [ - { - id: 'branch-revenue', - title: 'Branch Revenue Report', - description: 'Revenue breakdown by branch', - icon: '🏢', - endpoint: '/api/reports/branch-revenue', - type: 'date-range' - }, - { - id: 'doctor-revenue', - title: 'Doctor Revenue Report', - description: 'Revenue and patient counts by doctor', - icon: '👨‍⚕️', - endpoint: '/api/reports/doctor-revenue', - type: 'date-range' - }, - { - id: 'insurance-claims', - title: 'Insurance Analysis Report', - description: 'Insurance coverage vs out-of-pocket analysis', - icon: '🛡️', - endpoint: '/api/reports/insurance-analysis', - type: 'date-range' - } - ]; - - // Fetch report data dynamically based on active report - const fetchReport = async (reportId) => { - if (!reportId) return; - - const selected = reports.find(r => r.id === reportId); - if (!selected) return; - - setLoading(true); - setError(''); - setReportData(null); - + async function fetchData() { try { - let url = selected.endpoint; - const params = new URLSearchParams(); - - if (selected.type === 'date-range') { - if (filters.startDate) params.append('startDate', filters.startDate); - if (filters.endDate) params.append('endDate', filters.endDate); - } + setLoading(true); + setError(''); + const params = new URLSearchParams(dateRange); + const res = await fetch(`/api/reports/insurance-analysis?${params}`); - // Special handling for insurance-analysis API - if (selected.id === 'insurance-claims') { - params.append('type', 'summary'); + if (!res.ok) { + throw new Error(`HTTP error! status: ${res.status}`); } - if (filters.branchId) params.append('branchId', filters.branchId); - if (filters.doctorId) params.append('doctorId', filters.doctorId); + const result = await res.json(); - if (params.toString()) { - url += '?' + params.toString(); - } - - const res = await fetch(url); - if (!res.ok) { - throw new Error(`Failed to fetch ${selected.title}: ${res.status}`); + if (result.success && result.data) { + console.log('Fetched data:', result.data); // Debug log + setData(result.data); + } else { + throw new Error(result.error || 'Failed to load data'); } - - const data = await res.json(); - setReportData(data); } catch (err) { - console.error('Report fetch error:', err); - setError(err.message); - setReportData(null); + console.error('Fetch error:', err); + setError(err.message || 'Failed to fetch report data'); + setData([]); } finally { setLoading(false); } + } + + const chartData = { + labels: data.map(item => new Date(item.month_year).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short' + })), + datasets: [ + { + label: 'Insurance Covered', + data: data.map(item => Number(item.total_insurance_covered || 0)), + backgroundColor: 'rgba(34,197,94,0.7)', + borderColor: 'rgba(34,197,94,1)', + borderWidth: 1, + stack: 'stack0' + }, + { + label: 'Out of Pocket', + data: data.map(item => Number(item.total_out_of_pocket || 0)), + backgroundColor: 'rgba(239,68,68,0.7)', + borderColor: 'rgba(239,68,68,1)', + borderWidth: 1, + stack: 'stack0' + } + ] }; - // Renders charts and tables for each report type - const renderReportData = () => { - if (!activeReport) { - return ( -
-
- - - -
-

Select a Report

-

Choose a report from the available options to view analytics and insights

-
- ); - } - - if (loading) { - return ( -
-
-

Generating report...

-
- ); - } - - if (error) { - return ( -
-
- - - -
-

Report Error

-

{error}

-
-
-
- ); - } - - if (!reportData || !reportData.data || reportData.data.length === 0) { - return ( -
-
- - - -
-

No Data Available

-

There is no data to display for the selected report and filters.

-
- ); - } - - switch (activeReport) { - case 'branch-revenue': { - // reportData is an array directly from the API - const data = Array.isArray(reportData) ? reportData : []; - - const chartData = { - labels: data.map(b => b.branch_name || 'Unknown Branch'), - datasets: [ - { - label: 'Total Revenue (Rs.)', - data: data.map(b => Number(b.total_revenue) || 0), - backgroundColor: 'rgba(34,197,94,0.8)', - borderColor: 'rgba(34,197,94,1)', - borderWidth: 1 + const chartOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'top', + labels: { + padding: 20, + font: { size: 12 } + } + }, + title: { + display: true, + text: 'Insurance Coverage vs Out of Pocket Expenses', + font: { size: 16, weight: 'bold' }, + padding: { bottom: 30 } + }, + tooltip: { + callbacks: { + label: function(context) { + let label = context.dataset.label || ''; + if (label) { + label += ': '; } - ] - }; - - const chartOptions = { - responsive: true, - plugins: { - legend: { position: 'top' }, - title: { display: true, text: 'Revenue by Branch' } - }, - scales: { - y: { beginAtZero: true } - } - }; - - const totalRevenue = data.reduce((sum, branch) => sum + (Number(branch.total_revenue) || 0), 0); - const avgRevenue = data.length > 0 ? totalRevenue / data.length : 0; - - return ( -
- {/* Summary Cards */} -
-
-
-
-

Total Revenue

-

Rs. {totalRevenue.toLocaleString()}

-
-
- 💰 -
-
-
- -
-
-
-

Total Branches

-

{data.length}

-
-
- 🏢 -
-
-
- -
-
-
-

Avg Revenue

-

Rs. {Math.round(avgRevenue).toLocaleString()}

-
-
- 📊 -
-
-
-
- - {/* Chart */} -
- -
- - {/* Table */} -
-
-

Branch Revenue Details

-
-
- - - - - - - - - - {data.map((branch, i) => ( - - - - - - ))} - -
BranchAppointmentsTotal Revenue
{branch.branch_name || 'Unknown'}{branch.appointment_count || 0}Rs. {(Number(branch.total_revenue) || 0).toLocaleString()}
-
-
-
- ); - } - - case 'doctor-revenue': { - // reportData is an array directly from the API - const data = Array.isArray(reportData) ? reportData : []; - - const chartData = { - labels: data.map(d => d.doctor_name || 'Unknown Doctor'), - datasets: [ - { - label: 'Revenue (Rs.)', - data: data.map(d => Number(d.doctor_revenue) || 0), - backgroundColor: 'rgba(59,130,246,0.8)', - borderColor: 'rgba(59,130,246,1)', - borderWidth: 1 + if (context.parsed.y !== null) { + label += `Rs. ${context.parsed.y.toLocaleString()}`; } - ] - }; - - const chartOptions = { - responsive: true, - plugins: { - legend: { position: 'top' }, - title: { display: true, text: 'Revenue by Doctor' } - }, - scales: { - y: { beginAtZero: true } + return label; } - }; - - const totalRevenue = data.reduce((sum, doctor) => sum + (Number(doctor.doctor_revenue) || 0), 0); - const totalPatients = data.reduce((sum, doctor) => sum + (Number(doctor.total_appointments) || 0), 0); - - return ( -
- {/* Summary Cards */} -
-
-
-
-

Total Revenue

-

Rs. {totalRevenue.toLocaleString()}

-
-
- 💰 -
-
-
- -
-
-
-

Active Doctors

-

{data.length}

-
-
- 👨‍⚕️ -
-
-
- -
-
-
-

Total Appointments

-

{totalPatients.toLocaleString()}

-
-
- -
-
-
-
+ } + } + }, + scales: { + x: { + stacked: true, + title: { + display: true, + text: 'Month', + font: { size: 12, weight: 'bold' } + }, + grid: { + display: false + } + }, + y: { + stacked: true, + beginAtZero: true, + title: { + display: true, + text: 'Amount (Rs.)', + font: { size: 12, weight: 'bold' } + } + } + } + }; - {/* Chart */} -
- -
+ // Calculate summary statistics + const summary = { + totalPayments: data.reduce((sum, item) => sum + Number(item.total_payments || 0), 0), + totalInsurance: data.reduce((sum, item) => sum + Number(item.total_insurance_covered || 0), 0), + totalOutOfPocket: data.reduce((sum, item) => sum + Number(item.total_out_of_pocket || 0), 0), + averageCoverage: data.length ? + (data.reduce((sum, item) => sum + Number(item.total_insurance_covered || 0), 0) / + data.reduce((sum, item) => sum + Number(item.total_payments || 0), 0) * 100).toFixed(1) : 0 + }; - {/* Table */} -
-
-

Doctor Revenue Details

-
-
- - - - - - - - - - - - {data.map((doctor, i) => ( - - - - - - - - ))} - -
DoctorAppointmentsCompletedRevenueOutstanding
Dr. {doctor.doctor_name}{doctor.total_appointments || 0}{doctor.completed_appointments || 0}Rs. {(Number(doctor.doctor_revenue) || 0).toLocaleString()}Rs. {(Number(doctor.outstanding_amount) || 0).toLocaleString()}
+ return ( +
+ + +
+ + +
+
+
+

Insurance Analysis Report

+
+ setDateRange(prev => ({ ...prev, startDate: e.target.value }))} + className="border rounded px-3 py-2" + /> + setDateRange(prev => ({ ...prev, endDate: e.target.value }))} + className="border rounded px-3 py-2" + />
-
- ); - } - case 'insurance-claims': { - // Need to update the API endpoint to use insurance-analysis - const data = Array.isArray(reportData) ? reportData : []; - - const chartData = { - labels: data.map(c => { - if (c.month_year) { - const date = new Date(c.month_year); - return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short' }); - } - return 'Unknown'; - }), - datasets: [ - { - label: 'Total Appointments', - data: data.map(c => Number(c.total_appointments) || 0), - backgroundColor: 'rgba(59,130,246,0.8)', - borderColor: 'rgba(59,130,246,1)', - borderWidth: 1 - }, - { - label: 'Insurance Covered (Rs.)', - data: data.map(c => Number(c.total_insurance_covered) || 0), - backgroundColor: 'rgba(139,92,246,0.8)', - borderColor: 'rgba(139,92,246,1)', - borderWidth: 1 - } - ] - }; - - const chartOptions = { - responsive: true, - plugins: { - legend: { position: 'top' }, - title: { display: true, text: 'Insurance Analysis by Month' } - }, - scales: { - y: { beginAtZero: true } - } - }; - - const totalPayments = data.reduce((sum, item) => sum + (Number(item.total_payments) || 0), 0); - const totalInsurance = data.reduce((sum, item) => sum + (Number(item.total_insurance_covered) || 0), 0); - const totalOutOfPocket = data.reduce((sum, item) => sum + (Number(item.total_out_of_pocket) || 0), 0); - const avgCoverage = data.length > 0 ? (totalInsurance / totalPayments * 100) : 0; - - return ( -
{/* Summary Cards */} -
-
-
-
-

Total Payments

-

Rs. {totalPayments.toLocaleString()}

-
-
- -
-
+
+
+

Total Payments

+

Rs. {summary.totalPayments.toLocaleString()}

- -
-
-
-

Insurance Covered

-

Rs. {totalInsurance.toLocaleString()}

-
-
- 🛡️ -
-
+
+

Insurance Covered

+

Rs. {summary.totalInsurance.toLocaleString()}

- -
-
-
-

Out of Pocket

-

Rs. {totalOutOfPocket.toLocaleString()}

-
-
- -
-
+
+

Out of Pocket

+

Rs. {summary.totalOutOfPocket.toLocaleString()}

- -
-
-
-

Coverage Rate

-

{avgCoverage.toFixed(1)}%

-
-
- -
-
+
+

Average Coverage

+

{summary.averageCoverage}%

{/* Chart */} -
- -
- - {/* Table */} -
-
-

Monthly Insurance Analysis

-
-
- - - - - - - - - - - - - {data.map((item, i) => ( - - - - - - - - - ))} - -
MonthAppointmentsTotal PaymentsInsuranceOut of PocketCoverage %
- {item.month_year ? new Date(item.month_year).toLocaleDateString('en-US', { year: 'numeric', month: 'long' }) : 'Unknown'} - {item.total_appointments || 0}Rs. {(Number(item.total_payments) || 0).toLocaleString()}Rs. {(Number(item.total_insurance_covered) || 0).toLocaleString()}Rs. {(Number(item.total_out_of_pocket) || 0).toLocaleString()} - - {(Number(item.overall_insurance_coverage_percentage) || 0).toFixed(1)}% - -
+ {loading ? ( +
+
-
-
- ); - } - - default: - return ( -
-
- - - -
-

Report Under Development

-

This report visualization is currently being developed.

-
- ); - } - }; - - return ( -
- {/* Fixed Sidebar */} -
- -
- - {/* Main Content */} -
-
- {/* Header */} -
-

- 📊 - Management Reports -

-

Comprehensive business intelligence and analytics

-
- -
- {/* Report Selection Panel */} -
-
-

- 📋 - Available Reports -

- -
- {reports.map((report) => ( -
setActiveReport(report.id)} - > -
- {report.icon} -
-

{report.title}

-

{report.description}

-
-
-
- ))} -
- - {/* Filters Panel */} - {activeReport && reports.find(r => r.id === activeReport)?.type === 'date-range' && ( -
-

- 🔍 - Filters -

-
-
- - setFilters(prev => ({...prev, startDate: e.target.value}))} - className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" - /> -
- -
- - setFilters(prev => ({...prev, endDate: e.target.value}))} - className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" - /> -
- - -
-
- )} + ) : error ? ( +
{error}
+ ) : data.length === 0 ? ( +
+ No data available for selected period
-
- - {/* Report Display Panel */} -
-
- {activeReport && ( -
-
-
- {reports.find(r => r.id === activeReport)?.icon} -
-

- {reports.find(r => r.id === activeReport)?.title} -

-

- {reports.find(r => r.id === activeReport)?.description} -

-
-
- -
-
- )} - - {renderReportData()} + ) : ( +
+
-
+ )}
); -} +} \ No newline at end of file