import { useState, useEffect } from 'react'; import { Title, Table, Group, Button, Stack, Text, Badge, Modal, NumberInput, Select, Loader, Center, Card, Alert, } from '@mantine/core'; import { useForm } from '@mantine/form'; import { useDisclosure } from '@mantine/hooks'; import { notifications } from '@mantine/notifications'; import { IconSend, IconInfoCircle, IconCheck, IconX } from '@tabler/icons-react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import api from '../../services/api'; interface Invoice { id: string; invoice_number: string; unit_number: string; unit_id: string; invoice_date: string; due_date: string; invoice_type: string; description: string; amount: string; amount_paid: string; balance_due: string; status: string; period_start: string; period_end: string; assessment_group_name: string; frequency: string; owner_name: string; } interface PreviewGroup { id: string; name: string; frequency: string; active_units: number; regular_assessment: string; special_assessment: string; is_billing_month: boolean; total_amount: number; period_description: string; } interface Preview { month: number; year: number; month_name: string; groups: PreviewGroup[]; summary: { total_groups_billing: number; total_invoices: number; total_amount: number; }; } const statusColors: Record = { draft: 'gray', pending: 'blue', paid: 'green', partial: 'yellow', overdue: 'red', void: 'dark', }; const frequencyColors: Record = { monthly: 'blue', quarterly: 'teal', annual: 'violet', }; const fmt = (v: string | number) => parseFloat(String(v || '0')).toLocaleString('en-US', { style: 'currency', currency: 'USD' }); /** Extract last name from "First Last" format */ const getLastName = (ownerName: string | null) => { if (!ownerName) return '-'; const parts = ownerName.trim().split(/\s+/); return parts.length > 1 ? parts[parts.length - 1] : ownerName; }; export function InvoicesPage() { const [bulkOpened, { open: openBulk, close: closeBulk }] = useDisclosure(false); const [preview, setPreview] = useState(null); const [previewLoading, setPreviewLoading] = useState(false); const queryClient = useQueryClient(); const { data: invoices = [], isLoading } = useQuery({ queryKey: ['invoices'], queryFn: async () => { const { data } = await api.get('/invoices'); return data; }, }); const bulkForm = useForm({ initialValues: { month: new Date().getMonth() + 1, year: new Date().getFullYear() }, }); // Fetch preview when month/year changes const fetchPreview = async (month: number, year: number) => { setPreviewLoading(true); try { const { data } = await api.post('/invoices/generate-preview', { month, year }); setPreview(data); } catch { setPreview(null); } setPreviewLoading(false); }; useEffect(() => { if (bulkOpened) { fetchPreview(bulkForm.values.month, bulkForm.values.year); } }, [bulkOpened, bulkForm.values.month, bulkForm.values.year]); const bulkMutation = useMutation({ mutationFn: (values: any) => api.post('/invoices/generate-bulk', values), onSuccess: (res) => { queryClient.invalidateQueries({ queryKey: ['invoices'] }); queryClient.invalidateQueries({ queryKey: ['journal-entries'] }); const groupInfo = res.data.groups?.map((g: any) => `${g.group_name}: ${g.invoices_created}`).join(', ') || ''; notifications.show({ message: `Generated ${res.data.created} invoices${groupInfo ? ` (${groupInfo})` : ''}`, color: 'green', }); closeBulk(); setPreview(null); }, onError: (err: any) => { notifications.show({ message: err.response?.data?.message || 'Error', color: 'red' }); }, }); const lateFeesMutation = useMutation({ mutationFn: () => api.post('/invoices/apply-late-fees', { grace_period_days: 15, late_fee_amount: 25 }), onSuccess: (res) => { queryClient.invalidateQueries({ queryKey: ['invoices'] }); notifications.show({ message: `Applied ${res.data.applied} late fees`, color: 'yellow' }); }, }); if (isLoading) return
; const totalOutstanding = invoices.filter(i => i.status !== 'paid' && i.status !== 'void').reduce((s, i) => s + parseFloat(i.balance_due || '0'), 0); return ( Invoices Total Invoices{invoices.length} Outstanding{fmt(totalOutstanding)} Invoice #UnitOwner GroupDate DuePeriod Amount PaidBalanceStatus {invoices.map((i) => ( {i.invoice_number} {i.unit_number} {getLastName(i.owner_name)} {i.assessment_group_name ? ( {i.assessment_group_name} ) : ( {i.invoice_type} )} {new Date(i.invoice_date).toLocaleDateString()} {new Date(i.due_date).toLocaleDateString()} {i.period_start && i.period_end ? ( {new Date(i.period_start).toLocaleDateString(undefined, { month: 'short', year: 'numeric' })} {i.period_start !== i.period_end && ( <> - {new Date(i.period_end).toLocaleDateString(undefined, { month: 'short', year: 'numeric' })} )} ) : ( - )} {fmt(i.amount)} {fmt(i.amount_paid)} {fmt(i.balance_due)} {i.status} ))} {invoices.length === 0 && No invoices yet}
{ closeBulk(); setPreview(null); }} title="Generate Assessments" size="lg">
bulkMutation.mutate(v))}>