import React, { useState, useEffect } from 'react';
import {
TrendingUp,
CreditCard,
BarChart3,
Package,
ShoppingCart,
PieChart,
Users,
Bell,
DollarSign,
ArrowUpRight,
ArrowDownRight,
ArrowRight,
Search,
Download,
Filter,
Clock,
Shield,
Building,
AlertCircle,
RotateCcw
} from 'lucide-react';
export default function ReportIndex() {
const isReceptionist = window.__APP_DATA__?.role === 'receptionist';
const [activeTab, setActiveTab] = useState(isReceptionist ? 'Expense Report' : 'Profit Report');
const [profitData, setProfitData] = useState(null);
const [expiryReminders, setExpiryReminders] = useState([]);
const [loading, setLoading] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [selectedBranch, setSelectedBranch] = useState(isReceptionist ? (window.__APP_DATA__?.branch?.id || '') : '');
const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState('');
const [branches, setBranches] = useState([]);
// Additional Report States
const [expenseData, setExpenseData] = useState([]);
const [collectionData, setCollectionData] = useState([]);
const [lowStockData, setLowStockData] = useState([]);
const [movementsData, setMovementsData] = useState([]);
const [salesData, setSalesData] = useState([]);
const [investmentData, setInvestmentData] = useState([]);
const [investmentSummary, setInvestmentSummary] = useState({ total_invested: 0, total_roi_returned: 0 });
const [salaryData, setSalaryData] = useState([]);
const [selectedItem, setSelectedItem] = useState(null);
const tabs = isReceptionist
? ['Expense Report', 'Collection Report', 'Low Stock Report', 'Inventory Report', 'Product Sales', 'Expiry Reminders']
: ['Profit Report', 'Expense Report', 'Collection Report', 'Low Stock Report', 'Inventory Report', 'Product Sales', 'Investment Report', 'Salary Report', 'Expiry Reminders'];
const buildQueryString = () => {
const params = new URLSearchParams();
if (selectedBranch && selectedBranch !== 'All Branches') params.append('branch_id', selectedBranch);
if (startDate) params.append('start_date', startDate);
if (endDate) params.append('end_date', endDate);
return params.toString();
};
const resetFilters = () => {
setStartDate('');
setEndDate('');
setSearchQuery('');
if (!isReceptionist) {
setSelectedBranch('');
}
};
useEffect(() => {
fetchMetadata();
}, []);
useEffect(() => {
if (activeTab === 'Profit Report') {
fetchProfitData();
} else if (activeTab === 'Expiry Reminders') {
fetchExpiryReminders();
} else if (activeTab === 'Expense Report') {
fetchExpenseReport();
} else if (activeTab === 'Collection Report') {
fetchCollectionReport();
} else if (activeTab === 'Low Stock Report') {
fetchLowStockReport();
} else if (activeTab === 'Inventory Report') {
fetchMovementsReport();
} else if (activeTab === 'Product Sales') {
fetchSalesData();
} else if (activeTab === 'Investment Report') {
fetchInvestmentReport();
} else if (activeTab === 'Salary Report') {
fetchSalaryReport();
}
}, [activeTab, selectedBranch, startDate, endDate]);
const fetchMetadata = async () => {
try {
const res = await fetch('/api/expense-branches');
const data = await res.json();
setBranches(data || []);
} catch (error) {
console.error('Error fetching branches:', error);
}
};
const fetchProfitData = async () => {
setLoading(true);
try {
const res = await fetch(`/api/reports/profit?${buildQueryString()}`);
const data = await res.json();
setProfitData(data);
} catch (error) {
console.error('Error fetching profit data:', error);
} finally {
setLoading(false);
}
};
const fetchExpiryReminders = async () => {
setLoading(true);
try {
const res = await fetch(`/api/reports/expiry-reminders?${buildQueryString()}`);
const data = await res.json();
setExpiryReminders(data.reminders || []);
} catch (error) {
console.error('Error fetching expiry reminders:', error);
} finally {
setLoading(false);
}
};
const fetchExpenseReport = async () => {
setLoading(true);
try {
const res = await fetch(`/api/expenses?${buildQueryString()}`);
const data = await res.json();
setExpenseData(data || []);
} catch (error) {
console.error('Error fetching expenses:', error);
} finally {
setLoading(false);
}
};
const fetchCollectionReport = async () => {
setLoading(true);
try {
const res = await fetch(`/api/collections?${buildQueryString()}`);
const data = await res.json();
setCollectionData(data || []);
} catch (error) {
console.error('Error fetching collections:', error);
} finally {
setLoading(false);
}
};
const fetchLowStockReport = async () => {
setLoading(true);
try {
const res = await fetch(`/api/inventory/products?${buildQueryString()}`);
const data = await res.json();
setLowStockData(data.filter(p => parseFloat(p.current_stock) <= parseFloat(p.reorder_level)) || []);
} catch (error) {
console.error('Error fetching low stock:', error);
} finally {
setLoading(false);
}
};
const fetchMovementsReport = async () => {
setLoading(true);
try {
const res = await fetch(`/api/inventory/movements?${buildQueryString()}`);
const data = await res.json();
setMovementsData(data || []);
} catch (error) {
console.error('Error fetching movements:', error);
} finally {
setLoading(false);
}
};
const fetchSalesData = async () => {
setLoading(true);
try {
const res = await fetch(`/api/inventory/sales?${buildQueryString()}`);
const data = await res.json();
setSalesData(data || []);
} catch (error) {
console.error('Error fetching sales:', error);
} finally {
setLoading(false);
}
};
const fetchInvestmentReport = async () => {
setLoading(true);
try {
const res = await fetch(`/api/reports/investments?${buildQueryString()}`);
const data = await res.json();
console.log("INVESTMENT REPORT DATA:", data);
setInvestmentData(data.investors || []);
setInvestmentSummary({
total_invested: data.total_invested || 0,
total_roi_returned: data.total_roi_returned || 0
});
} catch (error) {
console.error('Error fetching investments:', error);
} finally {
setLoading(false);
}
};
const fetchSalaryReport = async () => {
setLoading(true);
try {
const res = await fetch(`/api/expenses/salary-history?${buildQueryString()}`);
const data = await res.json();
setSalaryData(data || []);
} catch (error) {
console.error('Error fetching salary report:', error);
} finally {
setLoading(false);
}
};
const exportToExcel = (data, fileName) => {
if (!data || data.length === 0) {
alert('No data to export');
return;
}
const headers = Object.keys(data[0]);
const csvRows = [];
csvRows.push('\ufeff' + headers.join(',')); // Added BOM for Excel UTF-8 support
for (const row of data) {
const values = headers.map(header => {
let cell = row[header] === null || row[header] === undefined ? '' : row[header];
cell = typeof cell === 'string' ? `"${cell.replace(/"/g, '""')}"` : cell;
return cell;
});
csvRows.push(values.join(','));
}
const csvString = csvRows.join('\n');
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.setAttribute('hidden', '');
a.setAttribute('href', url);
a.setAttribute('download', `${fileName}.csv`);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
const exportTabContent = () => {
let dataToExport = [];
let name = activeTab.toLowerCase().replace(/ /g, '_');
if (activeTab === 'Profit Report') {
dataToExport = (profitData?.transactions || []).map(t => ({
Date: new Date(t.date).toLocaleDateString(),
Type: t.type,
Category: t.category,
Description: t.description,
Branch: t.branch,
Amount: t.amount
}));
} else if (activeTab === 'Expense Report') {
dataToExport = expenseData.map(e => ({
Date: new Date(e.date).toLocaleDateString(),
Category: e.category?.name || 'N/A',
Branch: e.branch?.name || 'N/A',
Amount: e.amount
}));
} else if (activeTab === 'Collection Report') {
dataToExport = collectionData.map(c => ({
Date: new Date(c.date).toLocaleDateString(),
Type: c.type?.name || 'N/A',
Branch: c.branch?.name || 'N/A',
Amount: c.amount
}));
} else if (activeTab === 'Low Stock Report') {
dataToExport = lowStockData.map(p => ({
Product: p.name,
Branch: p.branch?.name || 'N/A',
Current_Stock: p.current_stock,
Reorder_Level: p.reorder_level
}));
} else if (activeTab === 'Inventory Report') {
dataToExport = movementsData.map(m => ({
Date: new Date(m.date).toLocaleDateString(),
Product: m.product_name,
Branch: m.branch,
Reason: m.reason,
Change: m.change,
Stock_After: m.new_stock
}));
} else if (activeTab === 'Product Sales') {
dataToExport = salesData.map(s => ({
Date: new Date(s.date).toLocaleDateString(),
Transaction_ID: s.transaction_id,
Branch: s.branch?.name || 'N/A',
Method: s.payment_method?.name || 'N/A',
SubTotal: s.sub_total,
VAT: s.vat_amount,
Total: s.total_amount
}));
} else if (activeTab === 'Investment Report') {
dataToExport = investmentData.map(inv => ({
Investor: inv.investor_name,
Date: new Date(inv.investment_date).toLocaleDateString(),
ROI_Plan: inv.roi_type === 'Percentage' ? `${inv.roi_value}% ${inv.roi_period}` : `${inv.roi_value} AED ${inv.roi_period}`,
Invested_Amount: inv.investment_amount,
Returns_Earned: inv.returns_earned,
Total_Pending: inv.total_pending
}));
} else if (activeTab === 'Salary Report') {
dataToExport = salaryData.map(s => ({
Date: new Date(s.date).toLocaleDateString(),
Type: s.is_bulk ? 'Bulk Release' : 'Individual Release',
Staff_Count: s.count,
Branch: s.branch,
Remarks: s.remarks,
Amount: s.amount
}));
} else if (activeTab === 'Expiry Reminders') {
dataToExport = expiryReminders.map(r => ({
Staff: r.entity_name,
Branch: r.branch_name,
Document: r.document_name,
Doc_Number: r.document_number || '---',
Expiry_Date: new Date(r.expiry_date).toLocaleDateString(),
Status: r.days_left <= 0 ? 'Expired' : 'Expiring Soon'
}));
}
exportToExcel(dataToExport, name);
};
const comingSoon = (tabName) => (
{tabName}
This specialized reporting module is currently under development.
);
return (
<>
{/* Global Reports Title & Filters */}
Global Reports
Comprehensive financial and operational oversight.
{/* Standard Tabs */}
{tabs.map((tab) => {
const isActive = activeTab === tab;
return (
);
})}
{activeTab === 'Profit Report' && (
{/* Summary Cards */}
Total Income
{profitData?.total_income?.toLocaleString() || '0.00'} AED
{profitData?.transactions?.filter(t => t.type === 'Income').length || 0} Records
Total Expenses
{profitData?.total_expense?.toLocaleString() || '0.00'} AED
{profitData?.transactions?.filter(t => t.type === 'Expense').length || 0} Records
Net Profit
{profitData?.net_profit?.toLocaleString() || '0.00'} AED
{/* Detailed Breakdown Card */}
Detailed Breakdown
| Date |
Transaction Type |
Details / Source |
Branch |
Amount |
{profitData?.transactions?.length > 0 ? (
profitData.transactions.map((t, idx) => (
|
{new Date(t.date).toLocaleDateString()}
|
{t.type}
|
{t.category}
{t.description}
|
{t.branch}
|
{t.type === 'Income' ? '+' : '-'}{t.amount.toLocaleString()} AED
{t.is_adjusted && (
)}
|
))
) : (
|
No transaction data found
|
)}
)}
{activeTab === 'Expiry Reminders' && (
{/* Search Bar */}
{/* Active Expiry Reminders Banner */}
Active Expiry Reminders
{expiryReminders.length} Alerts
{expiryReminders.length} Records found across branches
{/* Detailed Breakdown Card */}
Detailed Breakdown
| Staff Name |
Branch |
Document Type |
Doc Number |
Reminder Started On |
Expiry Date |
Status |
{expiryReminders.length > 0 ? (
expiryReminders.map((alert, idx) => (
|
{alert.entity_name}
|
{alert.branch_name}
|
{alert.document_name}
|
{alert.document_number || '---'}
|
{alert.reminder_started_on || '---'}
|
{new Date(alert.expiry_date).toLocaleDateString()}
|
{alert.days_left <= 0 ? 'Expired' : 'Expiring Soon'}
|
))
) : (
|
|
)}
)}
{activeTab === 'Expense Report' && (
{/* Summary Card */}
Total Expenses
{expenseData.reduce((sum, e) => sum + parseFloat(e.amount || 0), 0).toLocaleString()} AED
{expenseData.length} Expense Records
Expense Records
| Date |
Category |
Branch |
Amount |
{expenseData.map((e, idx) => (
| {new Date(e.date).toLocaleDateString()} |
{e.category?.name} |
{e.branch?.name} |
{e.amount.toLocaleString()} AED |
))}
)}
{activeTab === 'Collection Report' && (
{/* Summary Card */}
Total Collections
{collectionData.reduce((sum, c) => sum + parseFloat(c.amount || 0), 0).toLocaleString()} AED
{collectionData.length} Collection Records
Collection Records
| Date |
Type |
Branch |
Amount |
{collectionData.map((c, idx) => (
| {new Date(c.date).toLocaleDateString()} |
{c.type?.name} |
{c.branch?.name} |
{c.amount.toLocaleString()} AED
{c.is_adjusted && (
)}
|
))}
)}
{activeTab === 'Low Stock Report' && (
{/* Summary Card */}
Low Stock Items
{lowStockData.length} Products
Products below reorder level
Low Stock Inventory
| Product |
Branch |
Stock |
Min Level |
Status |
{lowStockData.map((p, idx) => (
| {p.name} |
{p.branch?.name} |
{p.current_stock} |
{p.reorder_level} |
Low Stock
|
))}
)}
{activeTab === 'Inventory Report' && (
{/* Summary Card */}
Total Movements
{movementsData.length} Records
Stock Additions & Deductions
Inventory Alerts & Movements
Global audit log of all stock adjustments and alerts.
| Date |
Product |
Branch |
Movement Type |
Change |
Stock After |
{movementsData.length > 0 ? (
movementsData.map((m, idx) => (
| {new Date(m.date).toLocaleDateString()} |
{m.product_name}
SKU: {m.sku}
|
{m.branch} |
{m.reason} |
= 0
? 'bg-emerald-50 text-emerald-600 border border-emerald-100'
: 'bg-red-50 text-red-600 border border-red-100'
}`}>
{m.change >= 0 ? '+' : ''}{m.change}
|
{m.new_stock} |
))
) : (
|
No inventory data found
|
)}
)}
{activeTab === 'Product Sales' && (
{/* Summary Card */}
Total Sales
{salesData.reduce((sum, s) => sum + parseFloat(s.total_amount || 0), 0).toLocaleString()} AED
{salesData.length} Sales Transactions
Sales Records
| Date |
Transaction ID |
Branch |
Method |
Sub-Total |
VAT (5%) |
Total Amount |
{salesData.length > 0 ? (
salesData.map((s, idx) => (
| {new Date(s.date).toLocaleDateString()} |
{s.transaction_id} |
{s.branch?.name} |
{s.payment_method} |
{parseFloat(s.subtotal_amount || 0).toFixed(2)} AED |
{parseFloat(s.vat_amount || 0).toFixed(2)} AED |
{parseFloat(s.total_amount).toLocaleString()} AED
{s.is_adjusted && (
)}
|
))
) : (
|
No sales records found
|
)}
)}
{activeTab === 'Investment Report' && (
{/* Summary Cards */}
Total Active Investment
{(investmentSummary?.total_invested || 0).toLocaleString()} AED
{investmentData?.length || 0} Investors
Total ROI Returned
{(investmentSummary?.total_roi_returned || 0).toLocaleString()} AED
Total Payouts Made
Investment Performance
Overview of investor contributions, returns and pending payouts.
| Investor Name |
Investment Date |
Invested Amount |
Returns Earned |
Pending to Pay |
{investmentData.length > 0 ? (
investmentData.map((inv, idx) => (
|
{inv.investor_name}
{inv.roi_type === 'Percentage' ? `${inv.roi_value}%` : `${inv.roi_value} AED`} {inv.roi_period}
|
{new Date(inv.investment_date).toLocaleDateString()} |
{parseFloat(inv.investment_amount).toLocaleString()} AED |
{parseFloat(inv.returns_earned).toLocaleString()} AED |
{parseFloat(inv.total_pending).toLocaleString()} AED |
))
) : (
|
No investments found
|
)}
)}
{activeTab === 'Salary Report' && (
{/* Summary Card */}
Total Released Salary
{salaryData.reduce((sum, s) => sum + parseFloat(s.amount || 0), 0).toLocaleString()} AED
{salaryData.length} Payout Batches
Salary Release History
Overview of all individual and bulk salary payouts.
| Date |
Batch/Staff Details |
Branch |
Remarks |
Total Amount |
{salaryData.length > 0 ? (
salaryData.map((s, idx) => (
| {new Date(s.date).toLocaleDateString()} |
{s.is_bulk ? : }
{s.is_bulk ? 'Bulk Release' : 'Individual Release'}
{s.count} Staff {s.count === 1 ? 'Member' : 'Members'}
|
{s.branch} |
{s.remarks} |
{parseFloat(s.amount).toLocaleString()} AED |
))
) : (
|
No salary records found
|
)}
)}
{activeTab !== 'Profit Report' && activeTab !== 'Expiry Reminders' &&
activeTab !== 'Expense Report' && activeTab !== 'Collection Report' &&
activeTab !== 'Low Stock Report' && activeTab !== 'Inventory Report' &&
activeTab !== 'Product Sales' && activeTab !== 'Investment Report' &&
activeTab !== 'Salary Report' && comingSoon(activeTab)}
{/* Details Modal */}
{selectedItem && (
Adjustment Details
Transaction: #{selectedItem.transaction_id || 'N/A'}
Original Total
{(selectedItem.original_amount || 0).toLocaleString('en-AE', { minimumFractionDigits: 2 })} AED
Adjusted Total
{(parseFloat(selectedItem.amount) || 0).toLocaleString('en-AE', { minimumFractionDigits: 2 })} AED
Remarks / Reason
"{selectedItem.remarks || 'No remarks provided for this adjustment.'}"
)}
>
);
}