+ if (!selectedAccountId) {
+ alert("Please select an account");
+ return;
+ }
+
+ try {
+ const created = await createTransaction({
+ ...newTransaction,
+ financialAccount_id: selectedAccountId as number,
+ } as Transaction);
+
+ const selectedAccount = userAccounts.find(
+ (acc) => acc.id === selectedAccountId,
+ );
+
+ const transactionWithAccountName = {
+ ...created,
+ account_name: selectedAccount?.name || "Unknown",
+ };
+
+ setAllTransactions((prev) => [transactionWithAccountName, ...prev]);
+ setNewTransaction({
+ date: new Date().toISOString().split("T")[0],
+ description: "",
+ category: "",
+ amount: 0,
+ sender: "",
+ recipient: "",
+ });
+ setSelectedAccountId(userAccounts[0]?.id || "");
+ setShowAddForm(false);
+
+ if (expandedRowId !== null) {
+ setExpandedRowId(null);
+ }
+ } catch (error) {
+ console.error("Failed to create transaction:", error);
+ alert("Failed to create transaction");
+ }
+ };
+
+ if (isLoading) {
+ return (
+
+ {/* Search Bar */}
+
+
+
+
setSearchTerm(e.target.value)}
+ className="w-full pl-10 pr-4 py-2 bg-black/50 border border-green-500/20 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-green-500"
+ />
+
+
+
+ {/* Import CSV Modal */}
+
+
+
+ {showImportModal && selectedAccountForImport && (
+
{
+ setShowImportModal(false);
+ setSelectedAccountForImport(null);
+ }}
+ onSuccess={handleImportSuccess}
+ accountId={selectedAccountForImport}
+ />
+ )}
+
+
+
+ {/* Add Transaction Form */}
+ {showAddForm && (
+
+
+
New Transaction
+
+
+
+
+ setNewTransaction({ ...newTransaction, date: e.target.value })
+ }
+ className="bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white"
+ />
+
+ setNewTransaction({
+ ...newTransaction,
+ description: e.target.value,
+ })
+ }
+ className="bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white"
+ />
+
+ setNewTransaction({
+ ...newTransaction,
+ category: e.target.value,
+ })
+ }
+ className="bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white"
+ />
+
+ setNewTransaction({
+ ...newTransaction,
+ amount: parseFloat(e.target.value),
+ })
+ }
+ className="bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white"
+ />
+
+ {/* Account Selector */}
+
+
+
+ setNewTransaction({ ...newTransaction, sender: e.target.value })
+ }
+ className="bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white"
+ />
+
+ setNewTransaction({
+ ...newTransaction,
+ recipient: e.target.value,
+ })
+ }
+ className="bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white"
+ />
+
+
+
+
+
+ )}
+
+ {/* Transactions Table */}
+
{/* Header */}
-
+
Date
Description
Category
Amount
+
Account
From
To
+
{/* Body */}
- {transactions.map((tx, index) => (
-
- {/* Date */}
-
- {new Date(tx.date).toLocaleDateString()}
-
+ {displayedTransactions.map((tx, index) => {
+ const isExpanded = expandedRowId === tx.id;
+ const editValues = editingValues[tx.id] || {};
+ const currentAccountId =
+ editValues.financialAccount_id ||
+ userAccounts.find((acc) => acc.name === tx.account_name)?.id ||
+ "";
- {/* Description */}
-
- {tx.description || "N/A"}
-
+ return (
+
+ {/* Main Row */}
+
handleRowClick(tx.id)}
+ >
+
+ {formatDateForInput(tx.date)}
+
+
+ {tx.description || "N/A"}
+
+
+ {formatCategoryLabel(tx.category)}
+
+
+ {formatAmount(tx.amount)}
+
+
+ {tx.account_name || "Unknown Account"}
+
+
+ {tx.sender || "-"}
+
+
+ {tx.recipient || "-"}
+
+
+
+
+
- {/* Category */}
-
- {formatCategoryLabel(tx.category)}
-
-
- {/* Amount */}
-
- {tx.amount < 0
- ? `-$${Math.abs(tx.amount).toFixed(2)}`
- : `$${tx.amount.toFixed(2)}`}
-
+ {/* Expanded Edit Row */}
+ {isExpanded && (
+
+
+
+
+
+ handleFieldChange(tx.id, "date", e.target.value)
+ }
+ className="w-full bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white text-sm"
+ onClick={(e) => e.stopPropagation()}
+ />
+
+
+
+
+ handleFieldChange(
+ tx.id,
+ "description",
+ e.target.value,
+ )
+ }
+ className="w-full bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white text-sm"
+ onClick={(e) => e.stopPropagation()}
+ />
+
+
+
+
+ handleFieldChange(
+ tx.id,
+ "category",
+ e.target.value,
+ )
+ }
+ className="w-full bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white text-sm"
+ onClick={(e) => e.stopPropagation()}
+ />
+
+
+
+
+ handleFieldChange(
+ tx.id,
+ "amount",
+ parseFloat(e.target.value),
+ )
+ }
+ className="w-full bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white text-sm"
+ onClick={(e) => e.stopPropagation()}
+ />
+
- {/* From */}
-
{tx.sender}
+ {/* Account Selector for Edit Form */}
+
+
+
+
- {/* To */}
-
{tx.recipient}
-
- ))}
+
+
+
+ handleFieldChange(tx.id, "sender", e.target.value)
+ }
+ className="w-full bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white text-sm"
+ onClick={(e) => e.stopPropagation()}
+ />
+
+
+
+
+ handleFieldChange(
+ tx.id,
+ "recipient",
+ e.target.value,
+ )
+ }
+ className="w-full bg-black/50 border border-green-500/30 rounded px-3 py-2 text-white text-sm"
+ onClick={(e) => e.stopPropagation()}
+ />
+
+
+
+
+
+
+
+ )}
+
+ );
+ })}
- ) : null;
- return transactions && transactions.length === 0
- ? noTransactionFound
- : transactionTable;
-}
-//split on underscore, capitalize first letter of each word, then join with space
-function formatCategoryLabel(category: string): string {
- return category
- .split("_")
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
- .join(" ");
+ {/* Load More Button */}
+ {hasMore && (
+
+
+
+ )}
+
+ );
}
export default TransactionTable;
diff --git a/app/client/src/components/TransationForm.tsx b/app/client/src/components/TransationForm.tsx
deleted file mode 100644
index 6c0609d..0000000
--- a/app/client/src/components/TransationForm.tsx
+++ /dev/null
@@ -1,324 +0,0 @@
-import { useEffect, useState } from "react";
-import CsvUpload from "./csvread/CsvUpload";
-import "./userForm.css";
-import { getUserAccounts } from "../api/Account";
-import { validateTransactionForm } from "../utils/ValidateForms";
-import { postTranscations, putTranscations } from "../api/Transaction";
-import { handleCurrencyChange, handleCurrencyBlur } from "../utils/handleInput";
-import { type Transaction } from "../types/Transaction";
-import { type Account } from "@/types/AccountType";
-import { transactionCategory } from "@/enum/TransactionCategory";
-import type { AuthSession } from "@/types/authTypes";
-import SelectAccount from "@/components/SelectAccount";
-
-interface popupProp {
- toggle: () => void;
- session: AuthSession;
- setTransaction?: (editedTransaction: Transaction) => void;
- addTransaction?: (newTransaction: Transaction) => void;
- edit: boolean;
- selectedTransaction?: Transaction;
-}
-
-//Gets the keys of the enum
-type typeOfTransaction = keyof typeof transactionCategory;
-
-//Returns a form of for the user to enter their info
-export default function PopupForm({
- toggle,
- session,
- setTransaction,
- addTransaction,
- edit,
- selectedTransaction,
-}: popupProp) {
- //Handles the submiting the form
- const handleSubmit = () => {
- const transferAmount = Number(amount);
- let userTransaction: Transaction;
- let recipient = "";
- let sender = "";
-
- if (
- selectedType &&
- validateTransactionForm(
- selectedAccount,
- selectedType,
- transferAmount,
- selectedDate,
- undefined,
- )
- ) {
- let target;
-
- if (account) {
- if (selectedType === transactionCategory.INCOME) {
- target = account.find(
- (account) => account.id === selectedAccount,
- )?.name;
- if (target) {
- recipient = target;
- }
-
- sender = other;
- } else {
- recipient = other;
-
- target = account.find(
- (account) => account.id === selectedAccount,
- )?.name;
- if (target) {
- sender = target;
- }
- }
- }
-
- userTransaction = {
- id: 0,
- financialAccount_id: Number(selectedAccount),
- recipient: recipient,
- sender: sender,
- amount: transferAmount,
- category: selectedType,
- date: new Date(selectedDate),
- };
-
- if (edit && selectedTransaction) {
- try {
- //Editing selected transaction
- userTransaction.id = selectedTransaction.id;
-
- //Send a request to update the transaction
- putTranscations(session, userTransaction).then((result) => {
- //Determine if we're able to able to edit the transaction
- if (result && setTransaction) {
- setTransaction(userTransaction);
- }
- });
- } catch {
- alert("Failed to edit transaction");
- }
- } else {
- try {
- //Send a request to create the transaction
- postTranscations(session, userTransaction).then((response) => {
- //Successful put if response is returned
- if (response && response.id) {
- userTransaction.id = response.id;
-
- if (addTransaction) {
- addTransaction(userTransaction);
- }
- }
- });
- } catch {
- alert("Failed to create transaction");
- }
- }
-
- //Need to insert function that sets the state in parent component
-
- toggle();
- } else {
- alert("Please fill out the transaction form");
- }
- };
-
- //Handle when the user adds a file to the form
- /*const handleFile = (event:React.ChangeEvent
) =>{
-
- if(event.target && event.target.files && event.target.files[0]){
- setFile(event.target.files[0])
- } else {
- setFile(undefined)
- }
-
- //Insert cvs parsing function/validation function
- }*/
-
- //State of the user's account
- const [account, setAccount] = useState();
-
- useEffect(() => {
- getUserAccounts(session)
- .then((accounts) => {
- console.log(accounts);
- //Detemrine accounts exist
- if (accounts) {
- setAccount(accounts);
- } else {
- alert(
- "Failed to retrieve user's accounts, cannot make a transaction",
- );
- //toggle();
- }
- })
- .catch(() => {
- alert("Failed to retrieve user's accounts, cannot make a transaction");
- //toggle();
- });
- }, [session]);
-
- //Holds state of user input
- const [selectedAccount, setSelectedAccount] = useState(() => {
- if (edit && selectedTransaction) {
- return selectedTransaction.financialAccount_id;
- } else {
- return 0;
- }
- });
-
- const [selectedType, setSelectedType] = useState(() => {
- if (edit && selectedTransaction) {
- return selectedTransaction.category;
- } else {
- return "";
- }
- });
-
- const [amount, setAmount] = useState(() => {
- if (edit && selectedTransaction) {
- return selectedTransaction.amount.toFixed(2);
- } else {
- return "";
- }
- });
- //const [file, setFile] = useState(undefined)
-
- const [selectedDate, setSelectedDate] = useState(() => {
- if (edit && selectedTransaction) {
- return new Date(selectedTransaction.date).toISOString().slice(0, 10);
- } else {
- //Default
- return "";
- }
- });
-
- const [other, setOther] = useState(() => {
- if (edit && selectedTransaction) {
- if (selectedType === transactionCategory.INCOME) {
- return selectedTransaction.sender;
- } else {
- return selectedTransaction.recipient;
- }
- } else {
- //Default
- return "";
- }
- });
-
- //Holds the types of transfers
- const transCat: typeOfTransaction[] = Object.keys(
- transactionCategory,
- ) as typeOfTransaction[];
-
- return (
- <>
-
- >
- );
-}
diff --git a/app/client/src/components/csvread/CsvConfirm.tsx b/app/client/src/components/csvread/CsvConfirm.tsx
index fb9e52f..2edcadb 100644
--- a/app/client/src/components/csvread/CsvConfirm.tsx
+++ b/app/client/src/components/csvread/CsvConfirm.tsx
@@ -22,7 +22,7 @@ export default function CsvConfirmation({ rows, onConfirm, onBack }: Props) {
.reduce((sum, r) => sum + (r.amount ?? 0), 0);
return (
-
+
Summary
diff --git a/app/client/src/components/csvread/CsvDrop.tsx b/app/client/src/components/csvread/CsvDrop.tsx
index e6a2dc4..a0c24e3 100644
--- a/app/client/src/components/csvread/CsvDrop.tsx
+++ b/app/client/src/components/csvread/CsvDrop.tsx
@@ -1,4 +1,5 @@
//component to upload for CSV files
+// import { color } from "chart.js/helpers";
import React, { useState } from "react";
interface CsvDropProps {
@@ -31,10 +32,10 @@ export default function CsvDrop({ onFileSelect }: CsvDropProps) {
background: isDragging ? "#eef" : "#fafafa",
cursor: "pointer",
marginBottom: "1rem",
+ color: "#888",
}}
>
-
Drag & drop your CSV here, or click to select
- n
+
Drag & drop your CSV here, or click to select a file
+
Total rows: {rows.length}
Valid rows: {validCount}
diff --git a/app/client/src/components/csvread/CsvUpload.tsx b/app/client/src/components/csvread/CsvUpload.tsx
index 3fe42d1..97de7c6 100644
--- a/app/client/src/components/csvread/CsvUpload.tsx
+++ b/app/client/src/components/csvread/CsvUpload.tsx
@@ -10,16 +10,16 @@ import { parseCsvFile } from "../../utils/ParseCsv";
import type { TransactionDraft } from "../../utils/ConvertTransaction";
import SuccessScreen from "./csvSuccess";
import { uploadCsvTransactions } from "@/api/Transaction";
-import type { AuthSession } from "@/types/authTypes";
+// import type { AuthSession } from "@/types/authTypes";
import type { Transaction } from "@/types/Transaction";
export default function CsvUpload({
accountId,
- session,
+ // session,
onImported,
}: {
accountId: number;
- session: AuthSession;
+ // session: AuthSession;
onImported: (txs: Transaction[]) => void;
}) {
const [file, setFile] = useState
(null); // selected CSV file
@@ -104,7 +104,7 @@ export default function CsvUpload({
try {
const response = await uploadCsvTransactions(
- session,
+ // session,
accountId,
parsedData,
);
@@ -129,7 +129,7 @@ export default function CsvUpload({
};
return (
-
+
{importResult && (