Refactor transaction components and styles

- Updated imports to use absolute paths for types and utilities.
- Enhanced TransactionTable component with mobile responsiveness and card-based layout.
- Improved loading and error handling in transaction fetching logic.
- Refactored transaction update API to streamline validation and data preparation.
- Added new styles for Radix UI components and improved global styles for better mobile experience.
- Implemented collapsible sections and improved button interactions in the UI.
- Updated tests to reflect changes in component structure and imports.
This commit is contained in:
GitHub Copilot
2025-05-07 17:10:21 -04:00
parent 7b3f9afa1a
commit d8678e68ed
19 changed files with 1285 additions and 443 deletions

View File

@@ -1,11 +1,9 @@
---
import type { Account } from "../types";
import AccountSummary from "./AccountSummary.tsx"; // Import the React component instead of the Astro one
import AddTransactionForm from "./AddTransactionForm.tsx";
import type { Account } from '@types';
interface Props {
accounts: Account[];
initialAccount: Account;
accounts: Account[];
initialAccount: Account;
}
const { accounts, initialAccount } = Astro.props;
@@ -14,18 +12,10 @@ const { accounts, initialAccount } = Astro.props;
<aside class="sidebar">
<div class="sidebar-header">
<h2>My finances</h2>
{/* Add button to toggle form visibility */}
<button
id="toggle-add-txn-btn"
aria-expanded="false"
aria-controls="add-transaction-section"
>
+ New Txn
</button>
</div>
<nav class="account-nav">
<h3>Accounts</h3>
<select id="account-select" name="account">
<select id="account-select" name="account" class="form-input">
{
accounts.map((account) => (
<option
@@ -39,43 +29,176 @@ const { accounts, initialAccount } = Astro.props;
</select>
</nav>
{/* Use the React AccountSummary component, remove account prop */}
<AccountSummary client:load />
{/* Section to contain the React form, initially hidden */}
<section id="add-transaction-section" class="collapsible collapsed">
{
/*
Use the React component here.
It now gets its state (currentAccountId, transactionToEdit)
directly from the Nano Store.
*/
}
<AddTransactionForm client:load />
<!-- Add Transaction Section with Toggle - Moved up to be right after account dropdown -->
<section id="add-transaction-section" class="add-transaction-section">
<button
type="button"
class="toggle-form-btn"
id="toggle-add-txn-btn"
aria-controls="add-transaction-form"
aria-expanded="false"
>
Add Transaction
</button>
<div id="add-transaction-form" class="collapsible-form collapsed">
<AddTransactionForm client:load />
</div>
</section>
<!-- Account Summary Section - Always visible -->
<div class="account-summary-section" id="account-summary-section">
<AccountSummary client:load />
</div>
</aside>
{/* Keep the script for toggling visibility for now */}
<script>
const toggleButton = document.getElementById("toggle-add-txn-btn");
const formSection = document.getElementById("add-transaction-section");
<!-- Toggle button for sidebar on mobile -->
<button
type="button"
class="sidebar-toggle"
id="sidebar-toggle"
aria-controls="sidebar-content"
aria-expanded="true"
>
<span>Toggle sidebar</span>
<span class="sidebar-toggle-icon">▲</span>
</button>
if (toggleButton && formSection) {
toggleButton.addEventListener("click", () => {
const isExpanded =
toggleButton.getAttribute("aria-expanded") === "true";
toggleButton.setAttribute("aria-expanded", String(!isExpanded));
formSection.classList.toggle("collapsed");
formSection.classList.toggle("expanded");
// Optional: Focus first field when expanding
if (!isExpanded) {
// Cast the result to HTMLElement before calling focus
(
formSection.querySelector(
"input, select, textarea",
) as HTMLElement
)?.focus();
}
});
}
<script>
// Add Transaction form toggle
const toggleAddTxnBtn = document.getElementById("toggle-add-txn-btn");
const addTransactionForm = document.getElementById("add-transaction-form");
toggleAddTxnBtn?.addEventListener("click", () => {
const isExpanded =
toggleAddTxnBtn.getAttribute("aria-expanded") === "true";
toggleAddTxnBtn.setAttribute(
"aria-expanded",
isExpanded ? "false" : "true",
);
if (isExpanded) {
addTransactionForm?.classList.add("collapsed");
} else {
addTransactionForm?.classList.remove("collapsed");
}
});
// Sidebar toggle for mobile
const sidebarToggleBtn = document.getElementById("sidebar-toggle");
const sidebar = document.querySelector(".sidebar");
sidebarToggleBtn?.addEventListener("click", () => {
const isExpanded =
sidebarToggleBtn.getAttribute("aria-expanded") === "true";
sidebarToggleBtn.setAttribute(
"aria-expanded",
isExpanded ? "false" : "true",
);
if (isExpanded) {
sidebar?.classList.add("sidebar-collapsed");
} else {
sidebar?.classList.remove("sidebar-collapsed");
}
});
// Check if we're on mobile and collapse sidebar by default
const checkMobile = () => {
const isMobile = window.innerWidth < 1024;
if (isMobile && sidebar && sidebarToggleBtn) {
// Start with sidebar collapsed on mobile
sidebar.classList.add("sidebar-collapsed");
sidebarToggleBtn.setAttribute("aria-expanded", "false");
} else if (sidebar && sidebarToggleBtn) {
sidebar.classList.remove("sidebar-collapsed");
sidebarToggleBtn.setAttribute("aria-expanded", "true");
}
};
// Check on load and window resize
window.addEventListener("load", checkMobile);
window.addEventListener("resize", checkMobile);
</script>
<style>
.sidebar {
padding: 20px;
background-color: #f9fafb;
border-right: 1px solid #e5e7eb;
height: 100%;
overflow-y: auto;
}
.sidebar-collapsed {
display: none;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.account-nav {
margin-bottom: 15px;
}
.account-nav h3 {
margin-bottom: 8px;
font-size: 16px;
}
/* Ensure account summary is always visible */
.account-summary-section {
margin-bottom: 15px;
padding-bottom: 5px;
}
.add-transaction-section {
margin-bottom: 15px;
}
.add-transaction-section .toggle-form-btn {
display: block;
width: 100%;
margin-bottom: 0;
}
.collapsible-form.collapsed {
display: none;
}
.sidebar-toggle {
display: none;
position: fixed;
bottom: 20px;
right: 20px;
background-color: #f9fafb;
border: 1px solid #e5e7eb;
padding: 10px;
cursor: pointer;
z-index: 100;
width: auto;
min-width: 140px;
text-align: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
.sidebar-toggle span {
pointer-events: none;
}
.sidebar-toggle-icon {
margin-left: 8px;
pointer-events: none;
}
@media (max-width: 1024px) {
.sidebar-toggle {
display: block;
}
}
</style>