228 lines
7.0 KiB
TypeScript
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>
|
|
);
|
|
}
|