feat: Enhance AccountSummary and TransactionTable components with refresh functionality and improve loading/empty states

This commit is contained in:
GitHub Copilot
2025-04-24 16:45:46 -04:00
parent 2cb3bdf117
commit 124b97a397
4 changed files with 82 additions and 59 deletions

View File

@@ -2,7 +2,10 @@ import React, { useState, useEffect } from "react";
import { useStore } from "@nanostores/react";
import type { Account } from "../types";
import { formatCurrency } from "../utils";
import { currentAccountId as currentAccountIdStore } from "../stores/transactionStore";
import {
currentAccountId as currentAccountIdStore,
refreshKey,
} from "../stores/transactionStore";
interface AccountSummaryProps {
// No props needed, data comes from store and fetch
@@ -10,6 +13,7 @@ interface AccountSummaryProps {
export default function AccountSummary({}: AccountSummaryProps) {
const currentAccountId = useStore(currentAccountIdStore);
const refreshCounter = useStore(refreshKey);
const [account, setAccount] = useState<Account | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -43,7 +47,7 @@ export default function AccountSummary({}: AccountSummaryProps) {
};
fetchDetails();
}, [currentAccountId]);
}, [currentAccountId, refreshCounter]);
// Determine content based on state
let balanceContent: React.ReactNode;

View File

@@ -45,10 +45,10 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
const dateObj = new Date(transactionToEdit.date);
// Check if date is valid before formatting
if (!isNaN(dateObj.getTime())) {
// Adjust for timezone offset to prevent date shifting
const timezoneOffset = dateObj.getTimezoneOffset() * 60000; //offset in milliseconds
const adjustedDate = new Date(dateObj.getTime() - timezoneOffset);
setDate(adjustedDate.toISOString().split("T")[0]);
// Directly format the date object (usually interpreted as UTC midnight)
// into the YYYY-MM-DD format required by the input.
// No timezone adjustment needed here.
setDate(dateObj.toISOString().split("T")[0]);
} else {
console.warn(
"Invalid date received for editing:",

View File

@@ -6,12 +6,14 @@ import {
startEditingTransaction,
currentAccountId as currentAccountIdStore,
triggerRefresh,
refreshKey,
} from "../stores/transactionStore";
interface TransactionTableProps {}
export default function TransactionTable({}: TransactionTableProps) {
const currentAccountId = useStore(currentAccountIdStore);
const refreshCounter = useStore(refreshKey);
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [isLoading, setIsLoading] = useState(false);
@@ -46,7 +48,7 @@ export default function TransactionTable({}: TransactionTableProps) {
};
fetchTransactions();
}, [currentAccountId]);
}, [currentAccountId, refreshCounter]);
const sortedTransactions = [...transactions].sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
@@ -109,6 +111,63 @@ export default function TransactionTable({}: TransactionTableProps) {
}
};
// Helper function to render loading state
const renderLoading = () => (
<tr>
<td colSpan={4} style={{ textAlign: "center", padding: "2rem" }}>
Loading transactions...
</td>
</tr>
);
// Helper function to render empty state
const renderEmpty = () => (
<tr>
<td
colSpan={4}
style={{
textAlign: "center",
fontStyle: "italic",
color: "#777",
}}
>
No transactions found for this account.
</td>
</tr>
);
// Helper function to render transaction rows
const renderRows = () =>
sortedTransactions.map((txn) => (
<tr key={txn.id} data-txn-id={txn.id}>
<td>{formatDate(txn.date)}</td>
<td>{txn.description}</td>
<td
className={`amount-col ${
txn.amount >= 0 ? "amount-positive" : "amount-negative"
}`}
>
{formatCurrency(txn.amount)}
</td>
<td>
<button
className="action-btn edit-btn"
title="Edit transaction"
onClick={() => handleEdit(txn)}
>
Edit
</button>
<button
className="action-btn delete-btn"
title="Delete transaction"
onClick={() => handleDelete(txn.id)}
>
Delete
</button>
</td>
</tr>
));
return (
<div id="transaction-section" className={isLoading ? "loading" : ""}>
{error && (
@@ -126,57 +185,13 @@ export default function TransactionTable({}: TransactionTableProps) {
</tr>
</thead>
<tbody id="transaction-table-body">
{isLoading ? (
<tr>
<td colSpan={4} style={{ textAlign: "center", padding: "2rem" }}>
Loading transactions...
</td>
</tr>
) : sortedTransactions.length === 0 && !error ? (
<tr>
<td
colSpan={4}
style={{
textAlign: "center",
fontStyle: "italic",
color: "#777",
}}
>
No transactions found for this account.
</td>
</tr>
) : (
!error &&
sortedTransactions.map((txn) => (
<tr key={txn.id} data-txn-id={txn.id}>
<td>{formatDate(txn.date)}</td>
<td>{txn.description}</td>
<td
className={`amount-col ${
txn.amount >= 0 ? "amount-positive" : "amount-negative"
}`}
>
{formatCurrency(txn.amount)}
</td>
<td>
<button
className="action-btn edit-btn"
title="Edit transaction"
onClick={() => handleEdit(txn)}
>
Edit
</button>
<button
className="action-btn delete-btn"
title="Delete transaction"
onClick={() => handleDelete(txn.id)}
>
Delete
</button>
</td>
</tr>
))
)}
{isLoading
? renderLoading()
: error
? null // Error message is shown above the table
: sortedTransactions.length === 0
? renderEmpty()
: renderRows()}
</tbody>
</table>
</div>

View File

@@ -1,7 +1,11 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "finance",
// Update to today's date
"observability": {
"logs": {
"enabled": true
}
},
"compatibility_date": "2025-04-24",
"assets": {
"directory": "./dist"