mirror of
https://github.com/acedanger/finance.git
synced 2025-12-05 22:50:12 -08:00
feat: Refactor transaction management with nanostores and convert components to React
- Added @nanostores/react for state management. - Created AccountSummary component to display account balance. - Replaced AddTransactionForm.astro with AddTransactionForm.tsx for better state handling. - Introduced TransactionTable.tsx for displaying transactions with edit/delete functionality. - Updated Sidebar.astro and MainContent.astro to use React components. - Implemented transactionStore.ts for managing current account ID and transaction editing state. - Removed obsolete AddTransactionForm.astro and related scripts. - Enhanced error handling and loading states in transaction forms. This fixes issues #7, #8, #9, #10, #11
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,116 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for data</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../prettify.css" />
|
||||
<link rel="stylesheet" href="../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../index.html">All files</a> data</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>41/41</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>41/41</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="store.ts"><a href="store.ts.html">store.ts</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="41" class="abs high">41/41</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="41" class="abs high">41/41</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../sorter.js"></script>
|
||||
<script src="../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for data/store.ts</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../prettify.css" />
|
||||
<link rel="stylesheet" href="../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../index.html">All files</a> / <a href="index.html">data</a> store.ts</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>41/41</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>41/41</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { Account, Transaction } from "../types";
|
||||
|
||||
// Temporary in-memory store for development
|
||||
export const accounts: Account[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Checking Account",
|
||||
last4: "4321",
|
||||
balance: 2500.0,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Savings Account",
|
||||
last4: "8765",
|
||||
balance: 10000.0,
|
||||
},
|
||||
];
|
||||
|
||||
export const transactions: Transaction[] = [
|
||||
{
|
||||
id: "1",
|
||||
accountId: "1",
|
||||
date: "2025-04-20",
|
||||
description: "Grocery Store",
|
||||
amount: -75.5,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
accountId: "1",
|
||||
date: "2025-04-21",
|
||||
description: "Salary Deposit",
|
||||
amount: 3000.0,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
accountId: "2",
|
||||
date: "2025-04-22",
|
||||
description: "Transfer to Savings",
|
||||
amount: 500.0,
|
||||
},
|
||||
];
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../sorter.js"></script>
|
||||
<script src="../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -23,30 +23,30 @@
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="strong">2.22% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>271/271</span>
|
||||
<span class='fraction'>211/9491</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="strong">56.57% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>39/39</span>
|
||||
<span class='fraction'>43/76</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="strong">23.25% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>6/6</span>
|
||||
<span class='fraction'>10/43</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="strong">2.22% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>271/271</span>
|
||||
<span class='fraction'>211/9491</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
@@ -79,93 +79,333 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="data"><a href="data/index.html">data</a></td>
|
||||
<td class="file low" data-value="finance"><a href="finance/index.html">finance</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="24" class="abs low">0/24</td>
|
||||
<td data-value="50" class="pct medium">50%</td>
|
||||
<td data-value="2" class="abs medium">1/2</td>
|
||||
<td data-value="50" class="pct medium">50%</td>
|
||||
<td data-value="2" class="abs medium">1/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="24" class="abs low">0/24</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/.astro"><a href="finance/.astro/index.html">finance/.astro</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/dist/_worker.js"><a href="finance/dist/_worker.js/index.html">finance/dist/_worker.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="58" class="abs low">0/58</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="58" class="abs low">0/58</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/dist/_worker.js/chunks"><a href="finance/dist/_worker.js/chunks/index.html">finance/dist/_worker.js/chunks</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5297" class="abs low">0/5297</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="7" class="abs low">0/7</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="7" class="abs low">0/7</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5297" class="abs low">0/5297</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/dist/_worker.js/chunks/astro"><a href="finance/dist/_worker.js/chunks/astro/index.html">finance/dist/_worker.js/chunks/astro</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2792" class="abs low">0/2792</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2792" class="abs low">0/2792</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/dist/_worker.js/pages"><a href="finance/dist/_worker.js/pages/index.html">finance/dist/_worker.js/pages</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="123" class="abs low">0/123</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="123" class="abs low">0/123</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/dist/_worker.js/pages/api"><a href="finance/dist/_worker.js/pages/api/index.html">finance/dist/_worker.js/pages/api</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="61" class="abs low">0/61</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="61" class="abs low">0/61</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/dist/_worker.js/pages/api/accounts"><a href="finance/dist/_worker.js/pages/api/accounts/index.html">finance/dist/_worker.js/pages/api/accounts</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/dist/_worker.js/pages/api/accounts/_id_"><a href="finance/dist/_worker.js/pages/api/accounts/_id_/index.html">finance/dist/_worker.js/pages/api/accounts/_id_</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="19" class="abs low">0/19</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="19" class="abs low">0/19</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/dist/_worker.js/pages/api/transactions"><a href="finance/dist/_worker.js/pages/api/transactions/index.html">finance/dist/_worker.js/pages/api/transactions</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="102" class="abs low">0/102</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="102" class="abs low">0/102</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/src"><a href="finance/src/index.html">finance/src</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="14" class="abs low">0/14</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="2" class="abs high">2/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="2" class="abs high">2/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="14" class="abs low">0/14</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/src/components"><a href="finance/src/components/index.html">finance/src/components</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="560" class="abs low">0/560</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="7" class="abs low">0/7</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="7" class="abs low">0/7</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="560" class="abs low">0/560</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="finance/src/data"><a href="finance/src/data/index.html">finance/src/data</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="41" class="abs high">41/41</td>
|
||||
<td data-value="37" class="abs high">37/37</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="41" class="abs high">41/41</td>
|
||||
<td data-value="37" class="abs high">37/37</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="pages/api/accounts"><a href="pages/api/accounts/index.html">pages/api/accounts</a></td>
|
||||
<td class="file low" data-value="finance/src/layouts"><a href="finance/src/layouts/index.html">finance/src/layouts</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="31" class="abs low">0/31</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="31" class="abs low">0/31</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/src/pages"><a href="finance/src/pages/index.html">finance/src/pages</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="155" class="abs low">0/155</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="155" class="abs low">0/155</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="finance/src/pages/api/accounts"><a href="finance/src/pages/api/accounts/index.html">finance/src/pages/api/accounts</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="11" class="abs high">11/11</td>
|
||||
<td data-value="9" class="abs high">9/9</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="11" class="abs high">11/11</td>
|
||||
<td data-value="9" class="abs high">9/9</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="pages/api/accounts/[id]"><a href="pages/api/accounts/[id]/index.html">pages/api/accounts/[id]</a></td>
|
||||
<td class="file high" data-value="finance/src/pages/api/accounts/[id]"><a href="finance/src/pages/api/accounts/[id]/index.html">finance/src/pages/api/accounts/[id]</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="22" class="abs high">22/22</td>
|
||||
<td data-value="18" class="abs high">18/18</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="3" class="abs high">3/3</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="22" class="abs high">22/22</td>
|
||||
<td data-value="18" class="abs high">18/18</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="pages/api/accounts/[id]/transactions"><a href="pages/api/accounts/[id]/transactions/index.html">pages/api/accounts/[id]/transactions</a></td>
|
||||
<td class="file high" data-value="finance/src/pages/api/accounts/[id]/transactions"><a href="finance/src/pages/api/accounts/[id]/transactions/index.html">finance/src/pages/api/accounts/[id]/transactions</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="15" class="abs high">15/15</td>
|
||||
<td data-value="12" class="abs high">12/12</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="2" class="abs high">2/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="15" class="abs high">15/15</td>
|
||||
<td data-value="12" class="abs high">12/12</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="pages/api/transactions"><a href="pages/api/transactions/index.html">pages/api/transactions</a></td>
|
||||
<td class="file high" data-value="finance/src/pages/api/transactions"><a href="finance/src/pages/api/transactions/index.html">finance/src/pages/api/transactions</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="56" class="abs high">56/56</td>
|
||||
<td data-value="42" class="abs high">42/42</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="8" class="abs high">8/8</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="56" class="abs high">56/56</td>
|
||||
<td data-value="42" class="abs high">42/42</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="pages/api/transactions/[id]"><a href="pages/api/transactions/[id]/index.html">pages/api/transactions/[id]</a></td>
|
||||
<td class="file high" data-value="finance/src/pages/api/transactions/[id]"><a href="finance/src/pages/api/transactions/[id]/index.html">finance/src/pages/api/transactions/[id]</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="126" class="abs high">126/126</td>
|
||||
<td data-value="93" class="abs high">93/93</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="25" class="abs high">25/25</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="2" class="abs high">2/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="126" class="abs high">126/126</td>
|
||||
<td data-value="93" class="abs high">93/93</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="finance/src/stores"><a href="finance/src/stores/index.html">finance/src/stores</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="17" class="abs low">0/17</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="17" class="abs low">0/17</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file empty" data-value="finance/src/types"><a href="finance/src/types/index.html">finance/src/types</a></td>
|
||||
<td data-value="0" class="pic empty">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct empty">0%</td>
|
||||
<td data-value="0" class="abs empty">0/0</td>
|
||||
<td data-value="0" class="pct empty">0%</td>
|
||||
<td data-value="1" class="abs empty">1/1</td>
|
||||
<td data-value="0" class="pct empty">0%</td>
|
||||
<td data-value="1" class="abs empty">1/1</td>
|
||||
<td data-value="0" class="pct empty">0%</td>
|
||||
<td data-value="0" class="abs empty">0/0</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
@@ -176,7 +416,7 @@
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
at 2025-04-24T19:21:13.627Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/accounts/[id]</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../../index.html">All files</a> pages/api/accounts/[id]</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>22/22</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>3/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>22/22</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="index.ts"><a href="index.ts.html">index.ts</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="22" class="abs high">22/22</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="3" class="abs high">3/3</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="22" class="abs high">22/22</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../../sorter.js"></script>
|
||||
<script src="../../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/accounts/[id]/index.ts</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../../index.html">All files</a> / <a href="index.html">pages/api/accounts/[id]</a> index.ts</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>22/22</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>3/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>22/22</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { APIRoute } from "astro";
|
||||
import { accounts } from "../../../../data/store";
|
||||
|
||||
export const GET: APIRoute = async ({ params }) => {
|
||||
const account = accounts.find((a) => a.id === params.id);
|
||||
|
||||
if (!account) {
|
||||
return new Response(JSON.stringify({ error: "Account not found" }), {
|
||||
status: 404,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(account), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
};
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../../sorter.js"></script>
|
||||
<script src="../../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/accounts/[id]/transactions</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../../../index.html">All files</a> pages/api/accounts/[id]/transactions</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>15/15</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>2/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>15/15</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="index.ts"><a href="index.ts.html">index.ts</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="15" class="abs high">15/15</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="2" class="abs high">2/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="15" class="abs high">15/15</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../../../sorter.js"></script>
|
||||
<script src="../../../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/accounts/[id]/transactions/index.ts</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../../../index.html">All files</a> / <a href="index.html">pages/api/accounts/[id]/transactions</a> index.ts</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>15/15</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>2/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>15/15</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { APIRoute } from "astro";
|
||||
import { transactions } from "../../../../../data/store";
|
||||
|
||||
export const GET: APIRoute = async ({ params }) => {
|
||||
const accountTransactions = transactions.filter(
|
||||
(t) => t.accountId === params.id
|
||||
);
|
||||
|
||||
return new Response(JSON.stringify(accountTransactions), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
};
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../../../sorter.js"></script>
|
||||
<script src="../../../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/accounts</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> pages/api/accounts</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>11/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>11/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="index.ts"><a href="index.ts.html">index.ts</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="11" class="abs high">11/11</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="11" class="abs high">11/11</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/accounts/index.ts</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">pages/api/accounts</a> index.ts</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>11/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>11/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { APIRoute } from "astro";
|
||||
import { accounts } from "../../../data/store";
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
return new Response(JSON.stringify(accounts), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
};
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/transactions/[id]</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../../index.html">All files</a> pages/api/transactions/[id]</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>126/126</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>25/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>2/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>126/126</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="index.ts"><a href="index.ts.html">index.ts</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="126" class="abs high">126/126</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="25" class="abs high">25/25</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="2" class="abs high">2/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="126" class="abs high">126/126</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../../sorter.js"></script>
|
||||
<script src="../../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,463 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/transactions/[id]/index.ts</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../../index.html">All files</a> / <a href="index.html">pages/api/transactions/[id]</a> index.ts</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>126/126</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>25/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>2/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>126/126</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a>
|
||||
<a name='L78'></a><a href='#L78'>78</a>
|
||||
<a name='L79'></a><a href='#L79'>79</a>
|
||||
<a name='L80'></a><a href='#L80'>80</a>
|
||||
<a name='L81'></a><a href='#L81'>81</a>
|
||||
<a name='L82'></a><a href='#L82'>82</a>
|
||||
<a name='L83'></a><a href='#L83'>83</a>
|
||||
<a name='L84'></a><a href='#L84'>84</a>
|
||||
<a name='L85'></a><a href='#L85'>85</a>
|
||||
<a name='L86'></a><a href='#L86'>86</a>
|
||||
<a name='L87'></a><a href='#L87'>87</a>
|
||||
<a name='L88'></a><a href='#L88'>88</a>
|
||||
<a name='L89'></a><a href='#L89'>89</a>
|
||||
<a name='L90'></a><a href='#L90'>90</a>
|
||||
<a name='L91'></a><a href='#L91'>91</a>
|
||||
<a name='L92'></a><a href='#L92'>92</a>
|
||||
<a name='L93'></a><a href='#L93'>93</a>
|
||||
<a name='L94'></a><a href='#L94'>94</a>
|
||||
<a name='L95'></a><a href='#L95'>95</a>
|
||||
<a name='L96'></a><a href='#L96'>96</a>
|
||||
<a name='L97'></a><a href='#L97'>97</a>
|
||||
<a name='L98'></a><a href='#L98'>98</a>
|
||||
<a name='L99'></a><a href='#L99'>99</a>
|
||||
<a name='L100'></a><a href='#L100'>100</a>
|
||||
<a name='L101'></a><a href='#L101'>101</a>
|
||||
<a name='L102'></a><a href='#L102'>102</a>
|
||||
<a name='L103'></a><a href='#L103'>103</a>
|
||||
<a name='L104'></a><a href='#L104'>104</a>
|
||||
<a name='L105'></a><a href='#L105'>105</a>
|
||||
<a name='L106'></a><a href='#L106'>106</a>
|
||||
<a name='L107'></a><a href='#L107'>107</a>
|
||||
<a name='L108'></a><a href='#L108'>108</a>
|
||||
<a name='L109'></a><a href='#L109'>109</a>
|
||||
<a name='L110'></a><a href='#L110'>110</a>
|
||||
<a name='L111'></a><a href='#L111'>111</a>
|
||||
<a name='L112'></a><a href='#L112'>112</a>
|
||||
<a name='L113'></a><a href='#L113'>113</a>
|
||||
<a name='L114'></a><a href='#L114'>114</a>
|
||||
<a name='L115'></a><a href='#L115'>115</a>
|
||||
<a name='L116'></a><a href='#L116'>116</a>
|
||||
<a name='L117'></a><a href='#L117'>117</a>
|
||||
<a name='L118'></a><a href='#L118'>118</a>
|
||||
<a name='L119'></a><a href='#L119'>119</a>
|
||||
<a name='L120'></a><a href='#L120'>120</a>
|
||||
<a name='L121'></a><a href='#L121'>121</a>
|
||||
<a name='L122'></a><a href='#L122'>122</a>
|
||||
<a name='L123'></a><a href='#L123'>123</a>
|
||||
<a name='L124'></a><a href='#L124'>124</a>
|
||||
<a name='L125'></a><a href='#L125'>125</a>
|
||||
<a name='L126'></a><a href='#L126'>126</a>
|
||||
<a name='L127'></a><a href='#L127'>127</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">8x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { APIRoute } from "astro";
|
||||
import { transactions, accounts } from "../../../../data/store";
|
||||
import type { Transaction } from "../../../../types";
|
||||
|
||||
export const PUT: APIRoute = async ({ request, params }) => {
|
||||
const { id } = params;
|
||||
|
||||
if (!id) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Transaction ID is required" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const updates = (await request.json()) as Partial<Transaction>;
|
||||
const transactionIndex = transactions.findIndex((t) => t.id === id);
|
||||
|
||||
if (transactionIndex === -1) {
|
||||
return new Response(JSON.stringify({ error: "Transaction not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const oldTransaction = transactions[transactionIndex];
|
||||
|
||||
// Get the old account first
|
||||
const oldAccount = accounts.find((a) => a.id === oldTransaction.accountId);
|
||||
if (!oldAccount) {
|
||||
return new Response(JSON.stringify({ error: "Account not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
// If account is changing, validate new account exists
|
||||
let newAccount = oldAccount;
|
||||
if (updates.accountId && updates.accountId !== oldTransaction.accountId) {
|
||||
const foundAccount = accounts.find((a) => a.id === updates.accountId);
|
||||
if (!foundAccount) {
|
||||
return new Response(JSON.stringify({ error: "Account not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
newAccount = foundAccount;
|
||||
}
|
||||
|
||||
// First, remove the old transaction's effect on the old account
|
||||
oldAccount.balance -= oldTransaction.amount;
|
||||
|
||||
// Create updated transaction
|
||||
const updatedTransaction: Transaction = {
|
||||
...oldTransaction,
|
||||
...updates,
|
||||
id: id, // Ensure ID doesn't change
|
||||
};
|
||||
|
||||
// Then add the new transaction's effect to the appropriate account
|
||||
if (newAccount === oldAccount) {
|
||||
// If same account, just add the new amount
|
||||
oldAccount.balance += updatedTransaction.amount;
|
||||
} else {
|
||||
// If different account, add to the new account
|
||||
newAccount.balance += updatedTransaction.amount;
|
||||
}
|
||||
|
||||
// Update transaction in array
|
||||
transactions[transactionIndex] = updatedTransaction;
|
||||
|
||||
return new Response(JSON.stringify(updatedTransaction), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: "Invalid request body" }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const DELETE: APIRoute = async ({ params }) => {
|
||||
const { id } = params;
|
||||
|
||||
if (!id) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Transaction ID is required" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const transactionIndex = transactions.findIndex((t) => t.id === id);
|
||||
|
||||
if (transactionIndex === -1) {
|
||||
return new Response(JSON.stringify({ error: "Transaction not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const transaction = transactions[transactionIndex];
|
||||
const account = accounts.find((a) => a.id === transaction.accountId);
|
||||
|
||||
if (!account) {
|
||||
return new Response(JSON.stringify({ error: "Account not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
// Update account balance
|
||||
account.balance -= transaction.amount;
|
||||
|
||||
// Remove transaction from array
|
||||
transactions.splice(transactionIndex, 1);
|
||||
|
||||
return new Response(null, { status: 204 });
|
||||
};
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../../sorter.js"></script>
|
||||
<script src="../../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/transactions</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> pages/api/transactions</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>56/56</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>8/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>56/56</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="index.ts"><a href="index.ts.html">index.ts</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="56" class="abs high">56/56</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="8" class="abs high">8/8</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="56" class="abs high">56/56</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for pages/api/transactions/index.ts</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">pages/api/transactions</a> index.ts</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>56/56</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>8/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>56/56</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { APIRoute } from "astro";
|
||||
import { transactions, accounts } from "../../../data/store";
|
||||
import type { Transaction } from "../../../types";
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const transaction = (await request.json()) as Omit<Transaction, "id">;
|
||||
|
||||
// Validate required fields
|
||||
if (
|
||||
!transaction.accountId ||
|
||||
!transaction.date ||
|
||||
!transaction.description ||
|
||||
transaction.amount === undefined
|
||||
) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Missing required fields" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Validate account exists
|
||||
const account = accounts.find((a) => a.id === transaction.accountId);
|
||||
if (!account) {
|
||||
return new Response(JSON.stringify({ error: "Account not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
// Create new transaction with generated ID
|
||||
const newTransaction: Transaction = {
|
||||
...transaction,
|
||||
id: (transactions.length + 1).toString(), // Simple ID generation for demo
|
||||
};
|
||||
|
||||
// Update account balance
|
||||
account.balance += transaction.amount;
|
||||
|
||||
// Add to transactions array
|
||||
transactions.push(newTransaction);
|
||||
|
||||
return new Response(JSON.stringify(newTransaction), {
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: "Invalid request body" }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
};
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2025-04-24T15:21:01.227Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
36
package-lock.json
generated
36
package-lock.json
generated
@@ -10,9 +10,11 @@
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "^12.5.1",
|
||||
"@astrojs/react": "^4.2.5",
|
||||
"@nanostores/react": "^1.0.0",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"astro": "^5.7.5",
|
||||
"nanostores": "^1.0.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
@@ -1499,6 +1501,25 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@nanostores/react": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nanostores/react/-/react-1.0.0.tgz",
|
||||
"integrity": "sha512-eDduyNy+lbQJMg6XxZ/YssQqF6b4OXMFEZMYKPJCCmBevp1lg0g+4ZRi94qGHirMtsNfAWKNwsjOhC+q1gvC+A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.0.0 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^1.0.0",
|
||||
"react": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
@@ -5204,6 +5225,21 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/nanostores": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.0.1.tgz",
|
||||
"integrity": "sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.0.0 || >=22.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/neotraverse": {
|
||||
"version": "0.6.18",
|
||||
"resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz",
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "^12.5.1",
|
||||
"@astrojs/react": "^4.2.5",
|
||||
"@nanostores/react": "^1.0.0",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"astro": "^5.7.5",
|
||||
"nanostores": "^1.0.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
|
||||
71
src/components/AccountSummary.tsx
Normal file
71
src/components/AccountSummary.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
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";
|
||||
|
||||
interface AccountSummaryProps {
|
||||
// No props needed, data comes from store and fetch
|
||||
}
|
||||
|
||||
export default function AccountSummary({}: AccountSummaryProps) {
|
||||
const currentAccountId = useStore(currentAccountIdStore);
|
||||
const [account, setAccount] = useState<Account | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentAccountId) {
|
||||
setAccount(null); // Clear account details if no account selected
|
||||
setError(null);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchDetails = async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch(`/api/accounts/${currentAccountId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch account details");
|
||||
}
|
||||
const data: Account = await response.json();
|
||||
setAccount(data);
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "An unknown error occurred"
|
||||
);
|
||||
setAccount(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchDetails();
|
||||
}, [currentAccountId]);
|
||||
|
||||
// Determine content based on state
|
||||
let balanceContent: React.ReactNode;
|
||||
if (isLoading) {
|
||||
balanceContent = <span className="loading-inline">Loading...</span>;
|
||||
} else if (error) {
|
||||
balanceContent = <span className="error-message">Error</span>;
|
||||
} else if (account) {
|
||||
balanceContent = formatCurrency(account.balance);
|
||||
} else {
|
||||
balanceContent = "N/A"; // Or some placeholder
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="account-summary">
|
||||
<h4>Account Summary</h4>
|
||||
{/* Keep the ID for potential external manipulation if needed, though ideally not */}
|
||||
<p>
|
||||
Balance: <span id="account-balance">{balanceContent}</span>
|
||||
</p>
|
||||
{/* Display error details if needed */}
|
||||
{/* {error && <small className="error-message">{error}</small>} */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
---
|
||||
// TODO: Enhance form validation and submission
|
||||
// - Add more robust client-side validation
|
||||
// - Implement better error message display
|
||||
// - Add loading states during submission
|
||||
// - Consider adding form reset confirmation if there are unsaved changes
|
||||
// This component handles both creating and editing transactions
|
||||
---
|
||||
<section class="add-transaction-section">
|
||||
<button
|
||||
id="toggle-form-btn"
|
||||
class="toggle-form-btn"
|
||||
aria-expanded="false"
|
||||
aria-controls="add-transaction-form"
|
||||
>
|
||||
Add Transaction +
|
||||
</button>
|
||||
<form id="add-transaction-form" class="collapsible-form collapsed" novalidate>
|
||||
<h4 id="form-title">New Transaction</h4>
|
||||
<div id="form-error" class="error-message hidden"></div>
|
||||
<input type="hidden" id="txn-id" name="id">
|
||||
<div class="form-group">
|
||||
<label for="txn-date">Date</label>
|
||||
<input type="date" id="txn-date" name="date" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="txn-description">Description</label>
|
||||
<input type="text" id="txn-description" name="description" required minlength="2" maxlength="100" placeholder="e.g. Groceries">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="txn-amount">Amount</label>
|
||||
<input type="number" id="txn-amount" name="amount" step="0.01" required placeholder="e.g. -25.50 or 1200.00">
|
||||
<small class="help-text">Use negative numbers for expenses (e.g., -50.00)</small>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button type="submit" id="submit-btn" class="form-submit-btn">Save Transaction</button>
|
||||
<button type="button" id="cancel-btn" class="cancel-btn hidden">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.error-message {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.help-text {
|
||||
color: #666;
|
||||
font-size: 0.8em;
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
}
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.cancel-btn {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
padding: 8px 15px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cancel-btn:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
.form-submit-btn.loading {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// --- DOM Elements ---
|
||||
const toggleBtn = document.getElementById('toggle-form-btn');
|
||||
const form = document.getElementById('add-transaction-form') as HTMLFormElement;
|
||||
const formTitle = document.getElementById('form-title');
|
||||
const errorDiv = document.getElementById('form-error');
|
||||
const accountSelect = document.getElementById('account-select') as HTMLSelectElement;
|
||||
const cancelBtn = document.getElementById('cancel-btn');
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const txnIdInput = document.getElementById('txn-id') as HTMLInputElement;
|
||||
const dateInput = document.getElementById('txn-date') as HTMLInputElement;
|
||||
|
||||
let isEditMode = false;
|
||||
let isSubmitting = false;
|
||||
|
||||
// Set default date to today
|
||||
if (dateInput) {
|
||||
dateInput.value = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
// --- Helper Functions ---
|
||||
function showError(message: string) {
|
||||
if (errorDiv) {
|
||||
errorDiv.textContent = message;
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function hideError() {
|
||||
if (errorDiv) {
|
||||
errorDiv.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function clearForm() {
|
||||
if (form) {
|
||||
form.reset();
|
||||
// Reset date to today
|
||||
if (dateInput) {
|
||||
dateInput.value = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
// Reset edit mode
|
||||
setEditMode(false);
|
||||
// Clear any errors
|
||||
hideError();
|
||||
}
|
||||
}
|
||||
|
||||
function setEditMode(enabled: boolean) {
|
||||
isEditMode = enabled;
|
||||
if (formTitle) formTitle.textContent = enabled ? 'Edit Transaction' : 'New Transaction';
|
||||
if (submitBtn) submitBtn.textContent = enabled ? 'Update Transaction' : 'Save Transaction';
|
||||
if (cancelBtn) cancelBtn.classList.toggle('hidden', !enabled);
|
||||
if (toggleBtn) toggleBtn.classList.toggle('hidden', enabled);
|
||||
}
|
||||
|
||||
function setLoading(enabled: boolean) {
|
||||
isSubmitting = enabled;
|
||||
if (submitBtn) {
|
||||
const button = submitBtn as HTMLButtonElement;
|
||||
button.classList.toggle('loading', enabled);
|
||||
button.disabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
if (!form) return null;
|
||||
|
||||
const formData = new FormData(form);
|
||||
const errors: string[] = [];
|
||||
|
||||
// Check required fields
|
||||
const description = formData.get('description') as string;
|
||||
const amount = formData.get('amount') as string;
|
||||
const date = formData.get('date');
|
||||
|
||||
if (!description || description.trim().length < 2) {
|
||||
errors.push('Description must be at least 2 characters long');
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
errors.push('Amount is required');
|
||||
} else {
|
||||
const amountNum = parseFloat(amount);
|
||||
if (isNaN(amountNum)) {
|
||||
errors.push('Amount must be a valid number');
|
||||
} else if (amountNum === 0) {
|
||||
errors.push('Amount cannot be zero');
|
||||
}
|
||||
}
|
||||
|
||||
if (!date) {
|
||||
errors.push('Date is required');
|
||||
} else {
|
||||
const dateObj = new Date(date as string);
|
||||
if (isNaN(dateObj.getTime())) {
|
||||
errors.push('Invalid date format');
|
||||
}
|
||||
}
|
||||
|
||||
return errors.length > 0 ? errors : null;
|
||||
}
|
||||
|
||||
// --- Form Toggle ---
|
||||
if (toggleBtn && form) {
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
const isExpanded = toggleBtn.getAttribute('aria-expanded') === 'true';
|
||||
toggleBtn.setAttribute('aria-expanded', String(!isExpanded));
|
||||
form.classList.toggle('collapsed');
|
||||
toggleBtn.textContent = isExpanded ? 'Add Transaction +' : 'Hide Form -';
|
||||
if (!isExpanded) {
|
||||
form.querySelector('input')?.focus();
|
||||
}
|
||||
hideError();
|
||||
});
|
||||
}
|
||||
|
||||
// Cancel button handler
|
||||
if (cancelBtn) {
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
clearForm();
|
||||
form?.classList.add('collapsed');
|
||||
});
|
||||
}
|
||||
|
||||
// --- Form Submission ---
|
||||
if (form) {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
hideError();
|
||||
|
||||
if (isSubmitting) return;
|
||||
|
||||
// Validate form
|
||||
const errors = validateForm();
|
||||
if (errors) {
|
||||
showError(errors.join('. '));
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const currentAccountId = accountSelect?.value;
|
||||
|
||||
if (!currentAccountId) {
|
||||
throw new Error('No account selected');
|
||||
}
|
||||
|
||||
const amount = parseFloat(formData.get('amount') as string);
|
||||
const transaction = {
|
||||
accountId: currentAccountId,
|
||||
date: formData.get('date'),
|
||||
description: (formData.get('description') as string).trim(),
|
||||
amount: amount
|
||||
};
|
||||
|
||||
const transactionId = txnIdInput.value;
|
||||
const method = transactionId ? 'PUT' : 'POST';
|
||||
const url = transactionId
|
||||
? `/api/transactions/${transactionId}`
|
||||
: '/api/transactions';
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(transaction)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || `Failed to ${isEditMode ? 'update' : 'create'} transaction`);
|
||||
}
|
||||
|
||||
// Transaction saved successfully
|
||||
const savedTransaction = await response.json();
|
||||
|
||||
// Update UI
|
||||
const eventName = isEditMode ? 'transactionUpdated' : 'transactionCreated';
|
||||
document.dispatchEvent(new CustomEvent(eventName, {
|
||||
detail: { transaction: savedTransaction }
|
||||
}));
|
||||
|
||||
// Clear and collapse form
|
||||
clearForm();
|
||||
form.classList.add('collapsed');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.setAttribute('aria-expanded', 'false');
|
||||
toggleBtn.textContent = 'Add Transaction +';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : 'An unexpected error occurred');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Edit Transaction Handler ---
|
||||
document.addEventListener('editTransaction', ((event: CustomEvent) => {
|
||||
const transaction = event.detail.transaction;
|
||||
if (!form || !transaction) return;
|
||||
|
||||
// Populate form
|
||||
txnIdInput.value = transaction.id;
|
||||
if (dateInput) dateInput.value = transaction.date;
|
||||
(form.querySelector('#txn-description') as HTMLInputElement).value = transaction.description;
|
||||
(form.querySelector('#txn-amount') as HTMLInputElement).value = transaction.amount.toString();
|
||||
|
||||
// Show form in edit mode
|
||||
setEditMode(true);
|
||||
form.classList.remove('collapsed');
|
||||
form.querySelector('input')?.focus();
|
||||
}) as EventListener);
|
||||
</script>
|
||||
260
src/components/AddTransactionForm.tsx
Normal file
260
src/components/AddTransactionForm.tsx
Normal file
@@ -0,0 +1,260 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useStore } from "@nanostores/react";
|
||||
import type { Transaction } from "../types";
|
||||
// Import store atoms and actions
|
||||
import {
|
||||
currentAccountId as currentAccountIdStore,
|
||||
transactionToEdit as transactionToEditStore,
|
||||
cancelEditingTransaction,
|
||||
transactionSaved,
|
||||
} from "../stores/transactionStore";
|
||||
|
||||
// Remove props that now come from the store
|
||||
interface AddTransactionFormProps {}
|
||||
|
||||
export default function AddTransactionForm({}: AddTransactionFormProps) {
|
||||
// --- Read state from store ---
|
||||
const currentAccountId = useStore(currentAccountIdStore);
|
||||
const transactionToEdit = useStore(transactionToEditStore);
|
||||
|
||||
// --- State Variables ---
|
||||
const [date, setDate] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [amount, setAmount] = useState("");
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const isEditMode = !!editingId;
|
||||
|
||||
// --- Effects ---
|
||||
// Effect to set default date on mount
|
||||
useEffect(() => {
|
||||
// Only set default date if not editing
|
||||
if (!transactionToEdit) {
|
||||
setDate(new Date().toISOString().split("T")[0]);
|
||||
}
|
||||
}, [transactionToEdit]); // Rerun if edit mode changes
|
||||
|
||||
// Effect to populate form when editing
|
||||
useEffect(() => {
|
||||
if (transactionToEdit) {
|
||||
setEditingId(transactionToEdit.id);
|
||||
// Format date correctly for input type="date"
|
||||
try {
|
||||
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]);
|
||||
} else {
|
||||
console.warn(
|
||||
"Invalid date received for editing:",
|
||||
transactionToEdit.date
|
||||
);
|
||||
setDate(""); // Set to empty if invalid
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing date for editing:", e);
|
||||
setDate(""); // Set to empty on error
|
||||
}
|
||||
setDescription(transactionToEdit.description);
|
||||
setAmount(transactionToEdit.amount.toString());
|
||||
setError(null); // Clear errors when starting edit
|
||||
} else {
|
||||
// Reset form if transactionToEdit becomes null (e.g., after saving or cancelling)
|
||||
// but only if not already resetting via handleCancel or handleSubmit
|
||||
if (!isLoading) {
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
}, [transactionToEdit, isLoading]); // Add isLoading dependency
|
||||
|
||||
// --- Helper Functions ---
|
||||
const resetForm = () => {
|
||||
setEditingId(null);
|
||||
setDate(new Date().toISOString().split("T")[0]);
|
||||
setDescription("");
|
||||
setAmount("");
|
||||
setError(null);
|
||||
// Don't reset isLoading here, it's handled in submit/cancel
|
||||
};
|
||||
|
||||
const validateForm = (): string[] => {
|
||||
const errors: string[] = [];
|
||||
if (!description || description.trim().length < 2) {
|
||||
errors.push("Description must be at least 2 characters long");
|
||||
}
|
||||
if (!amount) {
|
||||
errors.push("Amount is required");
|
||||
} else {
|
||||
const amountNum = parseFloat(amount);
|
||||
if (isNaN(amountNum)) {
|
||||
errors.push("Amount must be a valid number");
|
||||
} else if (amountNum === 0) {
|
||||
errors.push("Amount cannot be zero");
|
||||
}
|
||||
}
|
||||
if (!date) {
|
||||
errors.push("Date is required");
|
||||
} else {
|
||||
try {
|
||||
const dateObj = new Date(date + "T00:00:00"); // Treat input as local date
|
||||
if (isNaN(dateObj.getTime())) {
|
||||
errors.push("Invalid date format");
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push("Invalid date format");
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
// --- Event Handlers ---
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
|
||||
if (isLoading || !currentAccountId) {
|
||||
if (!currentAccountId) setError("No account selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
const validationErrors = validateForm();
|
||||
if (validationErrors.length > 0) {
|
||||
setError(validationErrors.join(". "));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// Ensure date is sent in a consistent format (e.g., YYYY-MM-DD)
|
||||
// The API should handle parsing this.
|
||||
const transactionData = {
|
||||
accountId: currentAccountId,
|
||||
date: date, // Send as YYYY-MM-DD string
|
||||
description: description.trim(),
|
||||
amount: parseFloat(amount),
|
||||
};
|
||||
|
||||
const method = editingId ? "PUT" : "POST";
|
||||
const url = editingId
|
||||
? `/api/transactions/${editingId}`
|
||||
: "/api/transactions";
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(transactionData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMsg = `Failed to ${
|
||||
isEditMode ? "update" : "create"
|
||||
} transaction`;
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMsg = errorData.error || errorMsg;
|
||||
} catch (jsonError) {
|
||||
// Ignore if response is not JSON
|
||||
errorMsg = `${response.status}: ${response.statusText}`;
|
||||
}
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
const savedTransaction: Transaction = await response.json();
|
||||
transactionSaved(savedTransaction); // Call store action instead of prop callback
|
||||
resetForm(); // Reset form on success
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "An unexpected error occurred"
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
resetForm();
|
||||
cancelEditingTransaction(); // Call store action instead of prop callback
|
||||
};
|
||||
|
||||
// --- JSX ---
|
||||
return (
|
||||
<form id="add-transaction-form-react" onSubmit={handleSubmit} noValidate>
|
||||
<h4>{isEditMode ? "Edit Transaction" : "New Transaction"}</h4>
|
||||
{error && <div className="error-message">{error}</div>}
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="txn-date-react">Date</label>
|
||||
<input
|
||||
type="date"
|
||||
id="txn-date-react"
|
||||
name="date"
|
||||
value={date}
|
||||
onChange={(e) => setDate(e.target.value)}
|
||||
required
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="txn-description-react">Description</label>
|
||||
<input
|
||||
type="text"
|
||||
id="txn-description-react"
|
||||
name="description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
required
|
||||
minLength={2}
|
||||
maxLength={100}
|
||||
placeholder="e.g. Groceries"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="txn-amount-react">Amount</label>
|
||||
<input
|
||||
type="number"
|
||||
id="txn-amount-react"
|
||||
name="amount"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
step="0.01"
|
||||
required
|
||||
placeholder="e.g. -25.50 or 1200.00"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<small className="help-text">
|
||||
Use negative numbers for expenses (e.g., -50.00)
|
||||
</small>
|
||||
</div>
|
||||
<div className="button-group">
|
||||
<button
|
||||
type="submit"
|
||||
className={`form-submit-btn ${isLoading ? "loading" : ""}`}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading
|
||||
? "Saving..."
|
||||
: isEditMode
|
||||
? "Update Transaction"
|
||||
: "Save Transaction"}
|
||||
</button>
|
||||
{isEditMode && (
|
||||
<button
|
||||
type="button"
|
||||
className="cancel-btn"
|
||||
onClick={handleCancel}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
---
|
||||
import TransactionTable from './TransactionTable.astro';
|
||||
import type { Account, Transaction } from '../types';
|
||||
import TransactionTable from './TransactionTable.tsx';
|
||||
import type { Account } from '../types';
|
||||
|
||||
interface Props {
|
||||
account: Account;
|
||||
transactions: Transaction[];
|
||||
}
|
||||
|
||||
const { account, transactions } = Astro.props;
|
||||
const { account } = Astro.props;
|
||||
---
|
||||
<main class="main-content">
|
||||
<header class="main-header">
|
||||
<h1>Transactions for <span id="current-account-name">{account.name} (***{account.last4})</span></h1>
|
||||
</header>
|
||||
/* Make table updatable */
|
||||
<TransactionTable transactions={transactions} client:load /> {}
|
||||
<TransactionTable client:load />
|
||||
</main>
|
||||
@@ -1,11 +1,10 @@
|
||||
---
|
||||
import AddTransactionForm from './AddTransactionForm.astro';
|
||||
import AccountSummary from './AccountSummary.astro';
|
||||
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[];
|
||||
/* The account to show in the sidebar. */
|
||||
initialAccount: Account;
|
||||
}
|
||||
|
||||
@@ -14,6 +13,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>
|
||||
@@ -26,9 +29,36 @@ const { accounts, initialAccount } = Astro.props;
|
||||
</select>
|
||||
</nav>
|
||||
|
||||
<AccountSummary account={initialAccount} /> {}
|
||||
{/* Use the React AccountSummary component, remove account prop */}
|
||||
<AccountSummary client:load />
|
||||
|
||||
/* Make form toggle interactive */
|
||||
<AddTransactionForm 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 />
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
</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');
|
||||
|
||||
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>
|
||||
184
src/components/TransactionTable.tsx
Normal file
184
src/components/TransactionTable.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useStore } from "@nanostores/react";
|
||||
import type { Transaction } from "../types";
|
||||
import { formatCurrency, formatDate } from "../utils";
|
||||
import {
|
||||
startEditingTransaction,
|
||||
currentAccountId as currentAccountIdStore,
|
||||
triggerRefresh,
|
||||
} from "../stores/transactionStore";
|
||||
|
||||
interface TransactionTableProps {}
|
||||
|
||||
export default function TransactionTable({}: TransactionTableProps) {
|
||||
const currentAccountId = useStore(currentAccountIdStore);
|
||||
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentAccountId) {
|
||||
setTransactions([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchTransactions = async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/accounts/${currentAccountId}/transactions`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch transactions");
|
||||
}
|
||||
const data: Transaction[] = await response.json();
|
||||
setTransactions(data);
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "An unknown error occurred"
|
||||
);
|
||||
setTransactions([]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchTransactions();
|
||||
}, [currentAccountId]);
|
||||
|
||||
const sortedTransactions = [...transactions].sort(
|
||||
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
|
||||
);
|
||||
|
||||
const handleDelete = async (txnId: string) => {
|
||||
if (!confirm("Are you sure you want to delete this transaction?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Attempting to delete transaction: ${txnId}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/transactions/${txnId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMsg = "Failed to delete transaction";
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMsg = errorData.error || errorMsg;
|
||||
} catch (jsonError) {
|
||||
errorMsg = `${response.status}: ${response.statusText}`;
|
||||
}
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
console.log(`Transaction ${txnId} deleted successfully.`);
|
||||
|
||||
setTransactions((currentTransactions) =>
|
||||
currentTransactions.filter((txn) => txn.id !== txnId)
|
||||
);
|
||||
|
||||
triggerRefresh();
|
||||
} catch (error) {
|
||||
alert(
|
||||
error instanceof Error ? error.message : "Failed to delete transaction"
|
||||
);
|
||||
console.error("Delete error:", error);
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = (transaction: Transaction) => {
|
||||
console.log(`Attempting to edit transaction: ${transaction.id}`);
|
||||
startEditingTransaction(transaction);
|
||||
|
||||
const addTransactionSection = document.getElementById(
|
||||
"add-transaction-section"
|
||||
);
|
||||
const toggleAddTxnBtn = document.getElementById("toggle-add-txn-btn");
|
||||
if (addTransactionSection?.classList.contains("collapsed")) {
|
||||
addTransactionSection.classList.replace("collapsed", "expanded");
|
||||
toggleAddTxnBtn?.setAttribute("aria-expanded", "true");
|
||||
addTransactionSection.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="transaction-section" className={isLoading ? "loading" : ""}>
|
||||
{error && (
|
||||
<div className="error-message" style={{ padding: "1rem" }}>
|
||||
Error loading transactions: {error}
|
||||
</div>
|
||||
)}
|
||||
<table className="transaction-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Description</th>
|
||||
<th className="amount-col">Amount</th>
|
||||
<th>Actions</th>
|
||||
</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>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,11 @@ import Sidebar from '../components/Sidebar.astro';
|
||||
import MainContent from '../components/MainContent.astro';
|
||||
import type { Account, Transaction } from '../types';
|
||||
|
||||
export interface Props {
|
||||
account: Account;
|
||||
transactions: Transaction[];
|
||||
}
|
||||
|
||||
// Fetch accounts from API
|
||||
const accountsResponse = await fetch('http://localhost:4321/api/accounts');
|
||||
const accounts: Account[] = await accountsResponse.json();
|
||||
@@ -48,7 +53,7 @@ TODO: Performance & Monitoring
|
||||
<BaseLayout title="Bank Transactions Dashboard">
|
||||
<div class="dashboard-layout">
|
||||
<Sidebar accounts={accounts} initialAccount={initialAccount} />
|
||||
<MainContent account={initialAccount} transactions={initialTransactions} />
|
||||
<MainContent account={initialAccount} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -56,36 +61,17 @@ TODO: Performance & Monitoring
|
||||
// Import types for client-side script
|
||||
type Transaction = import('../types').Transaction;
|
||||
type Account = import('../types').Account;
|
||||
type TransactionEventDetail = import('../types/events').TransactionEventDetail;
|
||||
|
||||
// Import store atoms and actions
|
||||
import { currentAccountId, startEditingTransaction } from '../stores/transactionStore';
|
||||
|
||||
// --- DOM Elements ---
|
||||
const accountSelect = document.getElementById('account-select') as HTMLSelectElement;
|
||||
const currentAccountNameSpan = document.getElementById('current-account-name');
|
||||
const accountBalanceSpan = document.getElementById('account-balance');
|
||||
const transactionTableBody = document.getElementById('transaction-table-body');
|
||||
const transactionSection = document.getElementById('transaction-section');
|
||||
const addTransactionSection = document.getElementById('add-transaction-section');
|
||||
const toggleAddTxnBtn = document.getElementById('toggle-add-txn-btn');
|
||||
|
||||
// --- Helper Functions ---
|
||||
function formatCurrency(amount: number): string {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
||||
}
|
||||
|
||||
function formatDate(dateString: string): string {
|
||||
const date = new Date(dateString + 'T00:00:00');
|
||||
return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: 'numeric' }).format(date);
|
||||
}
|
||||
|
||||
async function fetchAccountTransactions(accountId: string): Promise<Transaction[]> {
|
||||
try {
|
||||
const response = await fetch(`/api/accounts/${accountId}/transactions`);
|
||||
if (!response.ok) throw new Error('Failed to fetch transactions');
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching transactions:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAccountDetails(accountId: string): Promise<Account | null> {
|
||||
try {
|
||||
const response = await fetch(`/api/accounts/${accountId}`);
|
||||
@@ -97,157 +83,59 @@ TODO: Performance & Monitoring
|
||||
}
|
||||
}
|
||||
|
||||
// --- Update UI Function ---
|
||||
// --- Update UI Function (Further Simplified) ---
|
||||
async function updateUIForAccount(accountId: string): Promise<void> {
|
||||
console.log("Updating UI for account:", accountId);
|
||||
console.log("Updating Account Header for account:", accountId);
|
||||
|
||||
if (transactionSection) {
|
||||
transactionSection.classList.add('loading');
|
||||
}
|
||||
// Update the store with the current account ID
|
||||
currentAccountId.set(accountId);
|
||||
|
||||
// Only update the non-React part (header span)
|
||||
currentAccountNameSpan?.classList.add('loading-inline');
|
||||
|
||||
try {
|
||||
// Fetch latest account details and transactions
|
||||
const [account, transactions] = await Promise.all([
|
||||
fetchAccountDetails(accountId),
|
||||
fetchAccountTransactions(accountId)
|
||||
]);
|
||||
const account = await fetchAccountDetails(accountId);
|
||||
|
||||
if (!account || !transactionTableBody || !currentAccountNameSpan || !accountBalanceSpan) {
|
||||
console.error("Required data or UI elements not found!");
|
||||
if (!account || !currentAccountNameSpan) {
|
||||
console.error("Account data or header element not found!");
|
||||
if (currentAccountNameSpan) currentAccountNameSpan.textContent = 'Error';
|
||||
return;
|
||||
}
|
||||
|
||||
// Update header
|
||||
currentAccountNameSpan.textContent = `${account.name} (***${account.last4})`;
|
||||
|
||||
// Update summary
|
||||
accountBalanceSpan.textContent = formatCurrency(account.balance);
|
||||
|
||||
// Update table
|
||||
updateTransactionTable(transactions);
|
||||
} catch (error) {
|
||||
console.error('Error updating UI:', error);
|
||||
if (transactionTableBody) {
|
||||
transactionTableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" style="text-align: center; color: #dc3545;">
|
||||
Failed to load transactions. Please try again.
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
console.error('Error updating account header:', error);
|
||||
if (currentAccountNameSpan) currentAccountNameSpan.textContent = 'Error';
|
||||
} finally {
|
||||
if (transactionSection) {
|
||||
transactionSection.classList.remove('loading');
|
||||
}
|
||||
currentAccountNameSpan?.classList.remove('loading-inline');
|
||||
}
|
||||
}
|
||||
|
||||
function updateTransactionTable(transactions: Transaction[]): void {
|
||||
if (!transactionTableBody) return;
|
||||
|
||||
const sortedTransactions = [...transactions].sort((a, b) =>
|
||||
new Date(b.date).getTime() - new Date(a.date).getTime()
|
||||
);
|
||||
|
||||
transactionTableBody.innerHTML = '';
|
||||
|
||||
if (sortedTransactions.length === 0) {
|
||||
transactionTableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" style="text-align: center; font-style: italic; color: #777;">
|
||||
No transactions found for this account.
|
||||
</td>
|
||||
</tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
sortedTransactions.forEach(txn => {
|
||||
const row = document.createElement('tr');
|
||||
row.setAttribute('data-txn-id', txn.id);
|
||||
row.innerHTML = `
|
||||
<td>${formatDate(txn.date)}</td>
|
||||
<td>${txn.description}</td>
|
||||
<td class="amount-col ${txn.amount >= 0 ? 'amount-positive' : 'amount-negative'}">
|
||||
${formatCurrency(txn.amount)}
|
||||
</td>
|
||||
<td>
|
||||
<button class="action-btn edit-btn" title="Edit transaction">Edit</button>
|
||||
<button class="action-btn delete-btn" title="Delete transaction">Delete</button>
|
||||
</td>
|
||||
`;
|
||||
transactionTableBody.appendChild(row);
|
||||
});
|
||||
|
||||
setupTransactionButtons();
|
||||
}
|
||||
|
||||
function setupTransactionButtons(): void {
|
||||
const transactionRows = transactionTableBody?.querySelectorAll('tr[data-txn-id]');
|
||||
transactionRows?.forEach(row => {
|
||||
const txnId = row.getAttribute('data-txn-id');
|
||||
if (!txnId) return;
|
||||
|
||||
// Delete button handler
|
||||
const deleteBtn = row.querySelector('.delete-btn');
|
||||
deleteBtn?.addEventListener('click', () => handleDeleteTransaction(txnId));
|
||||
|
||||
// Edit button handler
|
||||
const editBtn = row.querySelector('.edit-btn');
|
||||
editBtn?.addEventListener('click', () => handleEditTransaction(txnId));
|
||||
});
|
||||
}
|
||||
|
||||
// --- Transaction Actions ---
|
||||
async function handleDeleteTransaction(txnId: string): Promise<void> {
|
||||
if (!confirm('Are you sure you want to delete this transaction?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (transactionSection) {
|
||||
transactionSection.classList.add('loading');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/transactions/${txnId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to delete transaction');
|
||||
}
|
||||
|
||||
// Refresh the current account view
|
||||
const currentAccountId = accountSelect?.value;
|
||||
if (currentAccountId) {
|
||||
await updateUIForAccount(currentAccountId);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : 'Failed to delete transaction');
|
||||
} finally {
|
||||
if (transactionSection) {
|
||||
transactionSection.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEditTransaction(txnId: string): Promise<void> {
|
||||
try {
|
||||
// Find transaction in current transactions list
|
||||
const currentAccountId = accountSelect?.value;
|
||||
if (!currentAccountId) return;
|
||||
|
||||
const transactions = await fetchAccountTransactions(currentAccountId);
|
||||
const accountId = currentAccountId.get();
|
||||
if (!accountId) return;
|
||||
|
||||
const response = await fetch(`/api/accounts/${accountId}/transactions`);
|
||||
if (!response.ok) throw new Error('Failed to fetch transactions for edit');
|
||||
const transactions: Transaction[] = await response.json();
|
||||
const transaction = transactions.find(t => t.id === txnId);
|
||||
|
||||
|
||||
if (!transaction) {
|
||||
throw new Error('Transaction not found');
|
||||
throw new Error('Transaction not found for editing');
|
||||
}
|
||||
|
||||
// Trigger edit mode in form
|
||||
document.dispatchEvent(new CustomEvent<TransactionEventDetail>('editTransaction', {
|
||||
detail: { transaction }
|
||||
}));
|
||||
startEditingTransaction(transaction);
|
||||
|
||||
// Manually expand the form section if it's collapsed
|
||||
if (addTransactionSection?.classList.contains('collapsed')) {
|
||||
addTransactionSection.classList.replace('collapsed', 'expanded');
|
||||
toggleAddTxnBtn?.setAttribute('aria-expanded', 'true');
|
||||
addTransactionSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : 'Failed to load transaction for editing');
|
||||
@@ -262,24 +150,11 @@ TODO: Performance & Monitoring
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for transaction events
|
||||
document.addEventListener('transactionCreated', ((event: CustomEvent<TransactionEventDetail>) => {
|
||||
const currentAccountId = accountSelect?.value;
|
||||
if (currentAccountId) {
|
||||
updateUIForAccount(currentAccountId);
|
||||
}
|
||||
}) as EventListener);
|
||||
|
||||
document.addEventListener('transactionUpdated', ((event: CustomEvent<TransactionEventDetail>) => {
|
||||
const currentAccountId = accountSelect?.value;
|
||||
if (currentAccountId) {
|
||||
updateUIForAccount(currentAccountId);
|
||||
}
|
||||
}) as EventListener);
|
||||
|
||||
// Initial load with transactions if available
|
||||
const initialAccountId = accountSelect?.value;
|
||||
if (initialAccountId) {
|
||||
updateUIForAccount(initialAccountId);
|
||||
// --- Initial Load ---
|
||||
const initialAccountIdValue = accountSelect?.value;
|
||||
if (initialAccountIdValue) {
|
||||
updateUIForAccount(initialAccountIdValue);
|
||||
} else {
|
||||
currentAccountId.set(null);
|
||||
}
|
||||
</script>
|
||||
48
src/stores/transactionStore.ts
Normal file
48
src/stores/transactionStore.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { atom } from "nanostores";
|
||||
import type { Transaction } from "../types";
|
||||
|
||||
// Atom to hold the ID of the currently selected account
|
||||
export const currentAccountId = atom<string | null>(null);
|
||||
|
||||
// Atom to hold the transaction object when editing, or null otherwise
|
||||
export const transactionToEdit = atom<Transaction | null>(null);
|
||||
|
||||
// Atom to trigger refreshes in components that depend on external changes
|
||||
export const refreshKey = atom<number>(0);
|
||||
|
||||
// Action to increment the refresh key, forcing dependent effects to re-run
|
||||
export function triggerRefresh() {
|
||||
refreshKey.set(refreshKey.get() + 1);
|
||||
}
|
||||
|
||||
// Action to set the transaction to be edited
|
||||
export function startEditingTransaction(transaction: Transaction) {
|
||||
transactionToEdit.set(transaction);
|
||||
// Optionally, trigger UI changes like expanding the form here
|
||||
// document.getElementById('add-transaction-section')?.classList.replace('collapsed', 'expanded');
|
||||
// document.getElementById('toggle-add-txn-btn')?.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
|
||||
// Action to clear the edit state
|
||||
export function cancelEditingTransaction() {
|
||||
transactionToEdit.set(null);
|
||||
// Optionally, collapse the form
|
||||
// document.getElementById('add-transaction-section')?.classList.replace('expanded', 'collapsed');
|
||||
// document.getElementById('toggle-add-txn-btn')?.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
// Action triggered after a transaction is saved (created or updated)
|
||||
export function transactionSaved(transaction: Transaction) {
|
||||
// Clear edit state if the saved transaction was the one being edited
|
||||
if (transactionToEdit.get()?.id === transaction.id) {
|
||||
transactionToEdit.set(null);
|
||||
}
|
||||
// Potentially trigger UI updates or refreshes here
|
||||
// This might involve dispatching a custom event or calling a refresh function
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("transactionSaved", { detail: { transaction } })
|
||||
);
|
||||
|
||||
// Trigger a general refresh after saving too, to update balance
|
||||
triggerRefresh();
|
||||
}
|
||||
Reference in New Issue
Block a user