fix: resolve 5 invoice/payment issues from user feedback

- Replace misleading 'sent' status with 'pending' (no email capability)
- Show assessment group name instead of raw 'regular_assessment' type
- Add owner last name to invoice table
- Fix payment creation Internal Server Error (PostgreSQL $2 type cast)
- Add edit/delete capability for payment records with invoice recalc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 11:53:54 -05:00
parent efa5aca35f
commit 69dad7cc74
7 changed files with 299 additions and 33 deletions

View File

@@ -15,7 +15,7 @@ interface Invoice {
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;
assessment_group_name: string; frequency: string; owner_name: string;
}
interface PreviewGroup {
@@ -43,7 +43,7 @@ interface Preview {
}
const statusColors: Record<string, string> = {
draft: 'gray', sent: 'blue', paid: 'green', partial: 'yellow', overdue: 'red', void: 'dark',
draft: 'gray', pending: 'blue', paid: 'green', partial: 'yellow', overdue: 'red', void: 'dark',
};
const frequencyColors: Record<string, string> = {
@@ -52,6 +52,13 @@ const frequencyColors: Record<string, string> = {
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<Preview | null>(null);
@@ -129,8 +136,9 @@ export function InvoicesPage() {
<Table striped highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Invoice #</Table.Th><Table.Th>Unit</Table.Th><Table.Th>Date</Table.Th>
<Table.Th>Due</Table.Th><Table.Th>Type</Table.Th><Table.Th>Period</Table.Th>
<Table.Th>Invoice #</Table.Th><Table.Th>Unit</Table.Th><Table.Th>Owner</Table.Th>
<Table.Th>Group</Table.Th><Table.Th>Date</Table.Th>
<Table.Th>Due</Table.Th><Table.Th>Period</Table.Th>
<Table.Th ta="right">Amount</Table.Th>
<Table.Th ta="right">Paid</Table.Th><Table.Th ta="right">Balance</Table.Th><Table.Th>Status</Table.Th>
</Table.Tr>
@@ -140,9 +148,18 @@ export function InvoicesPage() {
<Table.Tr key={i.id}>
<Table.Td fw={500}>{i.invoice_number}</Table.Td>
<Table.Td>{i.unit_number}</Table.Td>
<Table.Td>{getLastName(i.owner_name)}</Table.Td>
<Table.Td>
{i.assessment_group_name ? (
<Badge size="sm" variant="light" color={frequencyColors[i.frequency] || 'gray'}>
{i.assessment_group_name}
</Badge>
) : (
<Badge size="sm" variant="light">{i.invoice_type}</Badge>
)}
</Table.Td>
<Table.Td>{new Date(i.invoice_date).toLocaleDateString()}</Table.Td>
<Table.Td>{new Date(i.due_date).toLocaleDateString()}</Table.Td>
<Table.Td><Badge size="sm" variant="light">{i.invoice_type}</Badge></Table.Td>
<Table.Td>
{i.period_start && i.period_end ? (
<Text size="xs" c="dimmed">
@@ -161,7 +178,7 @@ export function InvoicesPage() {
<Table.Td><Badge color={statusColors[i.status] || 'gray'} size="sm">{i.status}</Badge></Table.Td>
</Table.Tr>
))}
{invoices.length === 0 && <Table.Tr><Table.Td colSpan={10}><Text ta="center" c="dimmed" py="lg">No invoices yet</Text></Table.Td></Table.Tr>}
{invoices.length === 0 && <Table.Tr><Table.Td colSpan={11}><Text ta="center" c="dimmed" py="lg">No invoices yet</Text></Table.Td></Table.Tr>}
</Table.Tbody>
</Table>