import type { Column } from './components'; import type { ConfirmState } from './ConfirmDialog'; import type { SendTarget } from './SendDocumentDialog'; import type { Payment } from './shared'; import { useState } from 'react'; import { Filter, MoreHorizontal, Plus, Send, Trash2, Wallet } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { EmptyState, ErrorState, Pagination, SearchBox, SectionShell, SimpleTable } from './components'; import { ConfirmDialog } from './ConfirmDialog'; import { SendDocumentDialog } from './SendDocumentDialog'; import { PdfPane } from './PdfPane'; import { formatDate, formatMoney } from './format'; import { PAGE_SIZE } from './shared'; import { useListFilters } from './useInvoicesSection'; import { useCurrency, useLookups, useResourceList, useResourceMutations } from './useInvoiceShelfData'; // Payments — upstream's PaymentIndexView. A payment is a receipt against an invoice, so the interesting // columns are which invoice and by what method; the PDF is the receipt itself. // // There is no status here and so no tabs: a payment either exists or it does not. type Props = { onEdit: (id: number | 'new') => void }; export const PaymentsListView = ({ onEdit }: Props) => { const { filters, patch, select, setPage, clearFilters } = useListFilters(); const currency = useCurrency(); const { customers, paymentMethods } = useLookups(); const [showFilters, setShowFilters] = useState(false); const [selection, setSelection] = useState>(new Set()); const [confirm, setConfirm] = useState(null); const [sendTarget, setSendTarget] = useState(null); const { rows, meta, isLoading, error } = useResourceList('payments', { page: filters.page, limit: PAGE_SIZE, payment_number: filters.search || undefined, customer_id: filters.customerId || undefined, payment_method_id: filters.status || undefined, orderByField: 'created_at', orderBy: 'desc', }); const { remove, removeMany } = useResourceMutations('payments'); const selected = rows.find((r) => r.id === filters.selected) ?? null; const toggleRow = (id: number) => setSelection((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); const allChecked = rows.length > 0 && rows.every((r) => selection.has(r.id)); const columns: Column[] = [ { key: 'check', label: '', width: '36px', render: (row) => ( e.stopPropagation()} className="flex items-center"> toggleRow(row.id)} /> ), }, { key: 'payment_date', label: 'Date', width: 'minmax(90px, 0.8fr)', render: (row) => formatDate(row.payment_date, row.formatted_payment_date), }, { key: 'payment_number', label: 'Number', width: 'minmax(100px, 0.9fr)', render: (row) => {row.payment_number}, }, { key: 'name', label: 'Customer', width: 'minmax(120px, 1.3fr)', render: (row) => {row.customer?.name ?? '—'}, }, { key: 'payment_mode', label: 'Mode', width: 'minmax(90px, 0.9fr)', render: (row) => row.payment_method?.name ?? '—', }, { key: 'invoice_number', label: 'Invoice', width: 'minmax(90px, 0.9fr)', render: (row) => row.invoice?.invoice_number ?? '—', }, { key: 'amount', label: 'Amount', width: 'minmax(100px, 1fr)', align: 'right', render: (row) => formatMoney(row.amount, currency), }, ]; const rowMenu = (row: Payment) => ( onEdit(row.id)}>Edit select(row.id)}>View receipt setSendTarget({ resource: 'payments', id: row.id, number: row.payment_number, customerName: row.customer?.name ?? '', customerEmail: row.customer?.email ?? null, }) } > Send receipt setConfirm({ title: `Delete payment ${row.payment_number}?`, description: 'The amount goes back onto the linked invoice as due. This does not refund anything — it only removes the record.', confirmLabel: 'Delete', destructive: true, onConfirm: () => { remove.mutate(row.id); if (filters.selected === row.id) select(null); }, }) } > Delete ); if (error) return ; const toolbar = ( <> setSelection(allChecked ? new Set() : new Set(rows.map((r) => r.id)))} /> patch({ q: v })} placeholder="Payment number…" />
{selection.size > 0 && ( )}
); return ( <>
}> {showFilters && (
{filters.isFiltered && ( )}
)} r.id} selectedKey={filters.selected} onRowClick={(r) => select(r.id === filters.selected ? null : r.id)} rowMenu={rowMenu} empty={ isLoading ? ( ) : ( ) } />
{selected && (
select(null)} />
)}
setConfirm(null)} /> setSendTarget(null)} /> ); };