import { Row, Col, Card, Statistic, Typography, Space, Tag, Progress, Skeleton, Empty } from 'antd'; import { BankOutlined, RiseOutlined, FallOutlined, FileTextOutlined, CheckCircleOutlined, ClockCircleOutlined, WarningOutlined, } from '@ant-design/icons'; import { Line, Pie, Column } from '@ant-design/charts'; import dayjs from 'dayjs'; import { useCompany } from '@/hooks/useCompany'; import { useCompanyStore } from '@/stores/companyStore'; import { usePeriodStore } from '@/stores/periodStore'; import { useAccountBalances } from '@/api/queries/accountQueries'; import { useInvoices } from '@/api/queries/invoiceQueries'; import { useVatReport } from '@/api/queries/vatQueries'; import { formatCurrency, formatDate } from '@/lib/formatters'; import { accountingColors } from '@/styles/theme'; const { Title, Text } = Typography; // Types for chart data interface CashFlowDataPoint { month: string; inflow: number; outflow: number; balance: number; } interface ExpenseBreakdownItem { category: string; value: number; } interface RecentTransaction { id: string; date: string; description: string; amount: number; type: 'income' | 'expense'; } export default function Dashboard() { const { company } = useCompany(); const { activeCompany } = useCompanyStore(); const { currentFiscalYear } = usePeriodStore(); // Define date interval const periodStart = currentFiscalYear?.startDate || dayjs().startOf('year').format('YYYY-MM-DD'); const periodEnd = currentFiscalYear?.endDate || dayjs().endOf('year').format('YYYY-MM-DD'); const { data: balances = [], isLoading: balancesLoading } = useAccountBalances( activeCompany?.id, currentFiscalYear ? { startDate: dayjs(currentFiscalYear.startDate), endDate: dayjs(currentFiscalYear.endDate), } : undefined ); const { data: invoices = [], isLoading: invoicesLoading } = useInvoices(activeCompany?.id); const { data: vatReport, isLoading: vatLoading } = useVatReport( activeCompany?.id, periodStart, periodEnd ); const isLoading = balancesLoading || invoicesLoading || vatLoading; // Calculate metrics from data const cashAccounts = balances.filter(b => b.accountType === 'asset' && b.accountNumber.startsWith('10')); const cashPosition = cashAccounts.reduce((sum, acc) => sum + acc.netChange, 0); const receivableAccounts = balances.filter(b => b.accountNumber.startsWith('11')); const accountsReceivable = receivableAccounts.reduce((sum, acc) => sum + acc.netChange, 0); const payableAccounts = balances.filter(b => b.accountNumber.startsWith('20')); const accountsPayable = Math.abs(payableAccounts.reduce((sum, acc) => sum + acc.netChange, 0)); const revenueAccounts = balances.filter(b => b.accountType === 'revenue'); const monthlyRevenue = Math.abs(revenueAccounts.reduce((sum, acc) => sum + acc.netChange, 0)); const expenseAccounts = balances.filter(b => ['expense', 'cogs', 'personnel'].includes(b.accountType)); const monthlyExpenses = expenseAccounts.reduce((sum, acc) => sum + acc.netChange, 0); const vatLiability = vatReport?.netVat ?? 0; const pendingInvoices = invoices.filter(i => i.status === 'sent' || i.status === 'issued').length; const overdueInvoices = invoices.filter(i => (i.status === 'sent' || i.status === 'issued') && i.dueDate && dayjs(i.dueDate).isBefore(dayjs()) ).length; const metrics = { cashPosition, cashChange: 0, // Requires historical data accountsReceivable, arChange: 0, accountsPayable, apChange: 0, monthlyRevenue, revenueChange: 0, monthlyExpenses, expenseChange: 0, vatLiability, unreconciledCount: 0, // Requires bank reconciliation data pendingInvoices, overdueInvoices, }; // Calculate expense breakdown from balances const totalExpenses = expenseAccounts.reduce((sum, acc) => sum + Math.abs(acc.netChange), 0); const calculatePercentage = (accountType: string, accountPrefix?: string): number => { if (totalExpenses === 0) return 0; let relevantAccounts = balances.filter(b => b.accountType === accountType); if (accountPrefix && accountPrefix !== 'other') { relevantAccounts = relevantAccounts.filter(b => b.accountNumber.startsWith(accountPrefix)); } else if (accountPrefix === 'other') { // "Other" is everything not in specific categories const specificPrefixes = ['61', '62', '64']; relevantAccounts = relevantAccounts.filter(b => !specificPrefixes.some(prefix => b.accountNumber.startsWith(prefix)) ); } const categoryTotal = relevantAccounts.reduce((sum, acc) => sum + Math.abs(acc.netChange), 0); return Math.round((categoryTotal / totalExpenses) * 100); }; const personnelExpenses = balances.filter(b => b.accountType === 'personnel'); const personnelTotal = personnelExpenses.reduce((sum, acc) => sum + Math.abs(acc.netChange), 0); const personnelPercentage = totalExpenses > 0 ? Math.round((personnelTotal / totalExpenses) * 100) : 0; const expenseBreakdown: ExpenseBreakdownItem[] = [ { category: 'Personale', value: personnelPercentage }, { category: 'Lokaler', value: calculatePercentage('expense', '61') }, { category: 'IT & Software', value: calculatePercentage('expense', '62') }, { category: 'Marketing', value: calculatePercentage('expense', '64') }, { category: 'Andet', value: calculatePercentage('expense', 'other') }, ].filter(e => e.value > 0); // For cashFlowData and recentTransactions - show empty charts or loading const cashFlowData: CashFlowDataPoint[] = []; const recentTransactions: RecentTransaction[] = []; // Loading state if (isLoading) { return (