Files
fintrack/app/transactions/page.tsx

228 lines
7.0 KiB
TypeScript

"use client";
import { useCallback } from "react";
import { PageLayout, PageHeader } from "@/components/layout";
import { RefreshCw } from "lucide-react";
import {
TransactionFilters,
TransactionBulkActions,
TransactionTable,
TransactionPagination,
formatCurrency,
formatDate,
} from "@/components/transactions";
import { RuleCreateDialog } from "@/components/rules";
import { OFXImportDialog } from "@/components/import/ofx-import-dialog";
import { useQueryClient } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { Upload } from "lucide-react";
import { useTransactionsPage } from "@/hooks/use-transactions-page";
import { useTransactionMutations } from "@/hooks/use-transaction-mutations";
import { useTransactionRules } from "@/hooks/use-transaction-rules";
export default function TransactionsPage() {
const queryClient = useQueryClient();
// Main page state and logic
const {
metadata,
isLoadingMetadata,
searchQuery,
setSearchQuery,
selectedAccounts,
onAccountsChange,
selectedCategories,
onCategoriesChange,
showReconciled,
onReconciledChange,
period,
onPeriodChange,
customStartDate,
customEndDate,
onCustomStartDateChange,
onCustomEndDateChange,
isCustomDatePickerOpen,
onCustomDatePickerOpenChange,
showDuplicates,
onShowDuplicatesChange,
page,
pageSize,
onPageChange,
sortField,
sortOrder,
onSortChange,
selectedTransactions,
onToggleSelectAll,
onToggleSelectTransaction,
clearSelection,
transactionsData,
isLoadingTransactions,
invalidateTransactions,
duplicateIds,
transactionParams,
} = useTransactionsPage();
// Transaction mutations
const {
toggleReconciled,
markReconciled,
setCategory,
deleteTransaction,
bulkReconcile: handleBulkReconcile,
bulkSetCategory: handleBulkSetCategory,
updatingTransactionIds,
} = useTransactionMutations({
transactionParams,
transactionsData,
});
// Transaction rules
const {
ruleDialogOpen,
setRuleDialogOpen,
ruleGroup,
handleCreateRule,
handleSaveRule,
} = useTransactionRules({
transactionsData,
metadata,
});
const invalidateAll = useCallback(() => {
invalidateTransactions();
queryClient.invalidateQueries({ queryKey: ["banking-metadata"] });
}, [invalidateTransactions, queryClient]);
const handleBulkReconcileWithClear = useCallback(
(reconciled: boolean) => {
handleBulkReconcile(reconciled, selectedTransactions);
clearSelection();
},
[handleBulkReconcile, selectedTransactions, clearSelection],
);
const handleBulkSetCategoryWithClear = useCallback(
(categoryId: string | null) => {
handleBulkSetCategory(categoryId, selectedTransactions);
clearSelection();
},
[handleBulkSetCategory, selectedTransactions, clearSelection],
);
const filteredTransactions = transactionsData?.transactions || [];
const totalTransactions = transactionsData?.total || 0;
const hasMore = transactionsData?.hasMore || false;
// For filter comboboxes, we'll use empty arrays for now
// They can be enhanced later with separate queries if needed
const transactionsForAccountFilter: never[] = [];
const transactionsForCategoryFilter: never[] = [];
// Early return for loading state - prevents sidebar flash
if (isLoadingMetadata || !metadata) {
return (
<div className="flex h-screen bg-background">
<main className="flex-1 flex items-center justify-center">
<RefreshCw className="w-8 h-8 animate-spin text-muted-foreground" />
</main>
</div>
);
}
return (
<PageLayout>
<PageHeader
title="Transactions"
description={`${totalTransactions} transaction${totalTransactions > 1 ? "s" : ""}`}
actions={
<OFXImportDialog onImportComplete={invalidateAll}>
<Button className="md:h-10 md:px-5">
<Upload className="w-4 h-4 md:mr-2" />
<span className="hidden md:inline">Importer OFX</span>
</Button>
</OFXImportDialog>
}
/>
<TransactionFilters
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
selectedAccounts={selectedAccounts}
onAccountsChange={onAccountsChange}
selectedCategories={selectedCategories}
onCategoriesChange={onCategoriesChange}
showReconciled={showReconciled}
onReconciledChange={onReconciledChange}
period={period}
onPeriodChange={onPeriodChange}
customStartDate={customStartDate}
customEndDate={customEndDate}
onCustomStartDateChange={onCustomStartDateChange}
onCustomEndDateChange={onCustomEndDateChange}
isCustomDatePickerOpen={isCustomDatePickerOpen}
onCustomDatePickerOpenChange={onCustomDatePickerOpenChange}
showDuplicates={showDuplicates}
onShowDuplicatesChange={onShowDuplicatesChange}
accounts={metadata.accounts}
folders={metadata.folders}
categories={metadata.categories}
transactionsForAccountFilter={transactionsForAccountFilter}
transactionsForCategoryFilter={transactionsForCategoryFilter}
/>
<TransactionBulkActions
selectedCount={selectedTransactions.size}
categories={metadata.categories}
onReconcile={handleBulkReconcileWithClear}
onSetCategory={handleBulkSetCategoryWithClear}
/>
{isLoadingTransactions ? (
<div className="flex items-center justify-center py-12">
<RefreshCw className="w-8 h-8 animate-spin text-muted-foreground" />
</div>
) : (
<>
<TransactionTable
transactions={filteredTransactions}
accounts={metadata.accounts}
categories={metadata.categories}
selectedTransactions={selectedTransactions}
sortField={sortField}
sortOrder={sortOrder}
onSortChange={onSortChange}
onToggleSelectAll={onToggleSelectAll}
onToggleSelectTransaction={onToggleSelectTransaction}
onToggleReconciled={toggleReconciled}
onMarkReconciled={markReconciled}
onSetCategory={setCategory}
onCreateRule={handleCreateRule}
onDelete={deleteTransaction}
formatCurrency={formatCurrency}
formatDate={formatDate}
updatingTransactionIds={updatingTransactionIds}
duplicateIds={duplicateIds}
highlightDuplicates={showDuplicates}
/>
<TransactionPagination
page={page}
pageSize={pageSize}
total={totalTransactions}
hasMore={hasMore}
onPageChange={onPageChange}
/>
</>
)}
<RuleCreateDialog
open={ruleDialogOpen}
onOpenChange={setRuleDialogOpen}
group={ruleGroup}
categories={metadata.categories}
onSave={handleSaveRule}
/>
</PageLayout>
);
}