import BankPicker from "@/components/features/BankReconciliation/BankPicker" import { selectedBankAccountAtom } from "@/components/features/BankReconciliation/bankRecAtoms" import CompanySelector from "@/components/features/BankReconciliation/CompanySelector" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" import { Empty, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty" import ErrorBanner from "@/components/ui/error-banner" import { FileDropzone } from "@/components/ui/file-dropzone" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { H3, Paragraph } from "@/components/ui/typography" import { useCurrentCompany } from "@/hooks/useCurrentCompany" import { formatDate } from "@/lib/date" import { flt, formatCurrency } from "@/lib/numbers" import _ from "@/lib/translate" import { cn } from "@/lib/utils" import { BankStatementImportLog } from "@/types/Accounts/BankStatementImportLog" import { useFrappeCreateDoc, useFrappeFileUpload, useFrappeGetDocList, useFrappeUpdateDoc } from "frappe-react-sdk" import { useAtom, useAtomValue } from "jotai" import { ListIcon, Loader2Icon } from "lucide-react" import { useState } from "react" import { useNavigate } from "react-router" const BankStatementImporter = () => { const selectedCompany = useCurrentCompany() const [selectedBankAccount] = useAtom(selectedBankAccountAtom) const [files, setFiles] = useState([]) const [password, setPassword] = useState("") const { upload, error, loading } = useFrappeFileUpload() const navigate = useNavigate() const { createDoc, loading: createLoading, error: createError } = useFrappeCreateDoc() const { updateDoc, error: updateError } = useFrappeUpdateDoc() const isPdf = files[0]?.name?.toLowerCase().endsWith(".pdf") ?? false const onUpload = () => { if (!selectedBankAccount) { return } const id = `new-bank-statement-import-log-${Date.now()}` // For protected PDFs, persist the password on the Bank Account so it is reused for // every statement of this account (and is available before the import doc is created). const ensurePassword = isPdf && password ? updateDoc("Bank Account", selectedBankAccount.name, { statement_password: password }) : Promise.resolve() ensurePassword.then(() => upload(files[0], { isPrivate: true, doctype: "Bank Statement Import Log", docname: id, fieldname: 'file' })).then((file) => { return createDoc("Bank Statement Import Log", // @ts-expect-error - not filling everything else { name: id, file: file.file_url, bank_account: selectedBankAccount.name }) }).then((doc) => { navigate(`/statement-importer/${doc.name}`) }) } return (
{error && } {createError && } {updateError && }
{selectedCompany &&
} {selectedBankAccount &&

{_("Upload your bank statement file to start the import process. We support CSV, XLSX and PDF files.")}

{isPdf &&
setPassword(e.target.value)} placeholder={_("Only if the PDF is password protected")} className="max-w-sm" />

{_("Leave blank to use the password already saved for this bank account (if any). It is stored encrypted and reused for future statements.")}

}
}
{selectedBankAccount && }
) } const StatementInstructions = () => { return {_("Statement Import Instructions")} {_("We support uploading CSV, XLSX, XLS and PDF files. Please make sure the file contains the correct columns.")} {_("The file should contain the following columns with a distinct header row. You can upload most bank statements as is without changing the columns.")} {_("For PDF statements, we auto-detect the tables on each page. You can then confirm each detected table, map its columns, and exclude anything that is not transactions (e.g. ads or summaries). Password-protected PDFs are supported - the password is saved on the bank account and reused.")} {_("Column Name")} {_("Maps To")} {_("Description")} Date/Transaction Date/Value Date {_("Date")} {_("The date of the transaction")} Amount {_("Amount")} {_('This can contain "CR"/"DR" values or positive/negative values. You could also have a separate column for CR/DR.')} Withdrawal/Deposit {_("Withdrawal")}/{_("Deposit")} {_("The withdrawal or deposit amounts - only required if there's no amount column.")} Description/Particulars/Remarks/Narration/Detail {_("Description")} {_("The description of the transaction")} Reference/Ref/Transaction ID/Cheque/Check {_("Reference")} {_("The reference number of the transaction")}
} const StatementImportLog = () => { const bankAccount = useAtomValue(selectedBankAccountAtom) const { data, error } = useFrappeGetDocList("Bank Statement Import Log", { fields: ["name", "file", "status", "number_of_transactions", "start_date", "end_date", "closing_balance", "creation"], filters: [["bank_account", "=", bankAccount?.name ?? ""]], orderBy: { field: "creation", order: "desc" }, limit: 10 }, bankAccount ? undefined : null, { revalidateOnFocus: false }) const navigate = useNavigate() const onViewDetails = (name: string) => { navigate(`/statement-importer/${name}`) } return (

{_("Previous Imports")}

{error && } {data && data.length > 0 ? ( {_("Imported On")} {_("Status")} {_("Transaction Dates")} {_("Number of Transactions")} {_("Closing Balance")} {_("File")} {data?.map((item) => ( onViewDetails(item.name)} className="cursor-pointer hover:bg-surface-gray-2"> {formatDate(item.creation, 'Do MMM YYYY')} {item.status} {item.start_date && item.end_date ? ( {formatDate(item.start_date, 'Do MMM YYYY')} to {formatDate(item.end_date, 'Do MMM YYYY')} ) : ( - )} {item.number_of_transactions} {formatCurrency(flt(item.closing_balance, 2))} {item.file.split('/').pop()} ))}
) : {_("No bank statements imported yet")} }
) } export default BankStatementImporter