import { useState } from 'react'; import { Title, Table, Group, Button, Stack, Text, Badge, Modal, NumberInput, Select, TextInput, Loader, Center, ActionIcon, Tooltip, } from '@mantine/core'; import { DateInput } from '@mantine/dates'; import { useForm } from '@mantine/form'; import { useDisclosure } from '@mantine/hooks'; import { notifications } from '@mantine/notifications'; import { IconPlus, IconEdit, IconTrash } from '@tabler/icons-react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import api from '../../services/api'; import { useIsReadOnly } from '../../stores/authStore'; interface Payment { id: string; unit_id: string; unit_number: string; invoice_id: string; invoice_number: string; payment_date: string; amount: string; payment_method: string; reference_number: string; status: string; notes: string; } export function PaymentsPage() { const [opened, { open, close }] = useDisclosure(false); const [editing, setEditing] = useState(null); const [deleteConfirm, setDeleteConfirm] = useState(null); const queryClient = useQueryClient(); const isReadOnly = useIsReadOnly(); const { data: payments = [], isLoading } = useQuery({ queryKey: ['payments'], queryFn: async () => { const { data } = await api.get('/payments'); return data; }, }); const { data: invoices = [] } = useQuery({ queryKey: ['invoices-unpaid'], queryFn: async () => { const { data } = await api.get('/invoices'); return data.filter((i: any) => i.status !== 'paid' && i.status !== 'void'); }, }); const form = useForm({ initialValues: { invoice_id: '', amount: 0, payment_method: 'check', reference_number: '', payment_date: new Date(), notes: '', }, }); const invalidateAll = () => { queryClient.invalidateQueries({ queryKey: ['payments'] }); queryClient.invalidateQueries({ queryKey: ['invoices'] }); queryClient.invalidateQueries({ queryKey: ['invoices-unpaid'] }); queryClient.invalidateQueries({ queryKey: ['accounts'] }); queryClient.invalidateQueries({ queryKey: ['journal-entries'] }); }; const createMutation = useMutation({ mutationFn: (values: any) => { const inv = invoices.find((i: any) => i.id === values.invoice_id); return api.post('/payments', { ...values, unit_id: inv?.unit_id, payment_date: values.payment_date.toISOString().split('T')[0], }); }, onSuccess: () => { invalidateAll(); notifications.show({ message: 'Payment recorded', color: 'green' }); close(); setEditing(null); form.reset(); }, onError: (err: any) => { notifications.show({ message: err.response?.data?.message || 'Error', color: 'red' }); }, }); const updateMutation = useMutation({ mutationFn: (values: any) => { return api.put(`/payments/${editing!.id}`, { payment_date: values.payment_date.toISOString().split('T')[0], amount: values.amount, payment_method: values.payment_method, reference_number: values.reference_number, notes: values.notes, }); }, onSuccess: () => { invalidateAll(); notifications.show({ message: 'Payment updated', color: 'green' }); close(); setEditing(null); form.reset(); }, onError: (err: any) => { notifications.show({ message: err.response?.data?.message || 'Error', color: 'red' }); }, }); const deleteMutation = useMutation({ mutationFn: (id: string) => api.delete(`/payments/${id}`), onSuccess: () => { invalidateAll(); notifications.show({ message: 'Payment deleted', color: 'orange' }); setDeleteConfirm(null); close(); setEditing(null); form.reset(); }, onError: (err: any) => { notifications.show({ message: err.response?.data?.message || 'Error', color: 'red' }); }, }); const handleEdit = (payment: Payment) => { setEditing(payment); form.setValues({ invoice_id: payment.invoice_id || '', amount: parseFloat(payment.amount || '0'), payment_method: payment.payment_method || 'check', reference_number: payment.reference_number || '', payment_date: new Date(payment.payment_date), notes: payment.notes || '', }); open(); }; const handleNew = () => { setEditing(null); form.reset(); open(); }; const handleSubmit = (values: any) => { if (editing) { updateMutation.mutate(values); } else { createMutation.mutate(values); } }; const fmt = (v: string) => parseFloat(v || '0').toLocaleString('en-US', { style: 'currency', currency: 'USD' }); const formatPeriod = (inv: any) => { if (inv.period_start && inv.period_end) { const start = new Date(inv.period_start).toLocaleDateString(undefined, { month: 'short' }); const end = new Date(inv.period_end).toLocaleDateString(undefined, { month: 'short', year: 'numeric' }); return inv.period_start === inv.period_end ? start : `${start}-${end}`; } return ''; }; const invoiceOptions = invoices.map((i: any) => { const period = formatPeriod(i); const periodStr = period ? ` - ${period}` : ''; return { value: i.id, label: `${i.invoice_number} - ${i.unit_number || 'Unit'}${periodStr} - Balance: $${parseFloat(i.balance_due || i.amount).toFixed(2)}`, }; }); if (isLoading) return
; return ( Payments {!isReadOnly && } DateUnitInvoice AmountMethod ReferenceStatus {!isReadOnly && } {payments.map((p) => ( {new Date(p.payment_date).toLocaleDateString()} {p.unit_number} {p.invoice_number} {fmt(p.amount)} {p.payment_method} {p.reference_number} {p.status} {!isReadOnly && ( handleEdit(p)}> )} ))} {payments.length === 0 && ( No payments recorded yet )}
{/* Create / Edit Payment Modal */} { close(); setEditing(null); form.reset(); }} title={editing ? 'Edit Payment' : 'Record Payment'}>
{!editing && ( {editing ? ( <> ) : ( )}
{/* Delete Confirmation Modal */} setDeleteConfirm(null)} title="Delete Payment" size="sm" > Are you sure you want to delete this payment of{' '} {deleteConfirm ? fmt(deleteConfirm.amount) : ''}{' '} for unit {deleteConfirm?.unit_number}? This will also remove the associated journal entry and recalculate the invoice balance.
); }