'use client'; import { useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { Plus, Download, FileText, FileSpreadsheet } from 'lucide-react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { DataTable } from '@/components/shared/data-table'; import { FilterBar } from '@/components/shared/filter-bar'; import { PageHeader } from '@/components/shared/page-header'; import { EmptyState } from '@/components/shared/empty-state'; import { TableSkeleton } from '@/components/shared/loading-skeleton'; import { ArchiveConfirmDialog } from '@/components/shared/archive-confirm-dialog'; import { PermissionGate } from '@/components/shared/permission-gate'; import { ExpenseFormDialog } from '@/components/expenses/expense-form-dialog'; import { expenseFilterDefinitions } from '@/components/expenses/expense-filters'; import { getExpenseColumns, type ExpenseRow } from '@/components/expenses/expense-columns'; import { usePaginatedQuery } from '@/hooks/use-paginated-query'; import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation'; import { apiFetch } from '@/lib/api/client'; export default function ExpensesPage() { const params = useParams<{ portSlug: string }>(); const portSlug = params?.portSlug ?? ''; const queryClient = useQueryClient(); const [createOpen, setCreateOpen] = useState(false); const [editExpense, setEditExpense] = useState(null); const [archiveExpense, setArchiveExpense] = useState(null); const { data, pagination, isLoading, isFetching, sort, setSort, setPage, setPageSize, filters, setFilter, clearFilters, } = usePaginatedQuery({ queryKey: ['expenses'], endpoint: '/api/v1/expenses', filterDefinitions: expenseFilterDefinitions, }); useRealtimeInvalidation({ 'expense:created': [['expenses']], 'expense:updated': [['expenses']], 'expense:archived': [['expenses']], }); const archiveMutation = useMutation({ mutationFn: (id: string) => apiFetch(`/api/v1/expenses/${id}`, { method: 'DELETE' }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['expenses'] }); setArchiveExpense(null); }, }); async function handleExport(type: 'csv' | 'pdf') { const res = await fetch(`/api/v1/expenses/export/${type}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(filters), credentials: 'include', }); if (!res.ok) return; const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `expenses.${type}`; a.click(); URL.revokeObjectURL(url); } const columns = getExpenseColumns({ portSlug, onEdit: (expense) => setEditExpense(expense), onArchive: (expense) => setArchiveExpense(expense), }); return (
handleExport('csv')}> Export CSV handleExport('pdf')}> Export PDF
} /> {isLoading ? ( ) : ( { setPage(p); setPageSize(ps); }} sort={sort} onSortChange={setSort} isLoading={isFetching && !isLoading} getRowId={(row) => row.id} emptyState={ setCreateOpen(true) }} /> } /> )} {editExpense && ( !open && setEditExpense(null)} expense={editExpense} /> )} !open && setArchiveExpense(null)} entityName={archiveExpense?.establishmentName ?? 'this expense'} entityType="Expense" isArchived={false} onConfirm={() => archiveExpense && archiveMutation.mutate(archiveExpense.id)} isLoading={archiveMutation.isPending} /> ); }