Added more descriptive names to account ui
This commit is contained in:
parent
b4044de503
commit
c9632b3d98
4 changed files with 175 additions and 11 deletions
113
templates/account_summary.html
Normal file
113
templates/account_summary.html
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Account {{account.account_name}}{% endblock title %}
|
||||||
|
{% block body %}
|
||||||
|
<div class="flex">
|
||||||
|
<span class="text-lg grow">{{account.account_name}}</span>
|
||||||
|
<div>
|
||||||
|
<a href="/accounts/id/{{account.account_id}}/transactions/add">+</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<h2>Net amount</h2>
|
||||||
|
<div class="ars-input">
|
||||||
|
<label>
|
||||||
|
<span>Dates</span>
|
||||||
|
<button style="float: right;" class="ars-button" onclick="onDateChange(event)">Update</button>
|
||||||
|
<input id="amount-date-range" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div style="height: 400px; width: 800px; position: relative;">
|
||||||
|
<canvas id="amount-trend"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<h2>Last transactions</h2>
|
||||||
|
<button class="ars-button" onclick="onRecategorize()">Recategorize</button>
|
||||||
|
<a class="ars-button" href="/accounts/id/{{account.account_id}}/transactions">More</a>
|
||||||
|
<table width="100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="40%">Description</th>
|
||||||
|
<th width="20%">Date</th>
|
||||||
|
<th width="10%">Amount</th>
|
||||||
|
<th width="10%">Acc</th>
|
||||||
|
<th width="15%">Category</th>
|
||||||
|
<th width="5%">Link</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for tx in transactions %}
|
||||||
|
<tr>
|
||||||
|
<td>{{tx.description}}</td>
|
||||||
|
<td>{{tx.tx_date}}</td>
|
||||||
|
<td>{{tx.amount/100}}</td>
|
||||||
|
<td>{{tx.accumulated/100}}</td>
|
||||||
|
<td>{% if tx.category %}{{categories[tx.category]}}{% endif %}</td>
|
||||||
|
<td><a href="/transaction/{{ tx.transaction_id }}">Go to</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
border-spacing: 0.2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/litepicker/dist/litepicker.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script>
|
||||||
|
function onRecategorize() {
|
||||||
|
fetch(
|
||||||
|
'/api/v1/accounts/id/{{account.account_id}}/recategorize',
|
||||||
|
{method: 'POST'}
|
||||||
|
).then(e=>console.log(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDateChange(e) {
|
||||||
|
let date_val = document.getElementById('amount-date-range').value.split(' - ');
|
||||||
|
|
||||||
|
let params = new URLSearchParams(window.location.search);
|
||||||
|
params.set('from', date_val[0]);
|
||||||
|
params.set('to', date_val[1]);
|
||||||
|
window.location.search = params.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSelect(e) {
|
||||||
|
let params = new URLSearchParams(window.location.search);
|
||||||
|
params.set("entries", e.target.value);
|
||||||
|
window.location.search = params.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateEl = document.getElementById('amount-date-range');
|
||||||
|
|
||||||
|
const picker = new Litepicker({
|
||||||
|
element: dateEl,
|
||||||
|
singleMode: false,
|
||||||
|
maxDate: new Date(),
|
||||||
|
startDate: "{{date_from}}",
|
||||||
|
endDate: "{{date_to}}"
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function formatDate(date) {
|
||||||
|
return date.substr(0, date.indexOf('T'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{% for txag in tx_agg -%}
|
||||||
|
{x: formatDate("{{txag.tx_date}}"), y: {{txag.accumulated/100}} },
|
||||||
|
{% endfor %}
|
||||||
|
];
|
||||||
|
|
||||||
|
const ctx = document.getElementById('amount-trend');
|
||||||
|
|
||||||
|
const chart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
datasets: [{label: 'Account', data: data}],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock body %}
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ async fn main() {
|
||||||
"/",
|
"/",
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(routes::ui::index))
|
.route("/", get(routes::ui::index))
|
||||||
.route("/accounts/id/:id", get(routes::ui::account::list))
|
.route("/accounts/id/:id", get(routes::ui::account::show))
|
||||||
.route(
|
.route(
|
||||||
"/accounts/id/:id/transactions/add",
|
"/accounts/id/:id/transactions/add",
|
||||||
get(routes::ui::account::add_transactions_view),
|
get(routes::ui::account::add_transactions_view),
|
||||||
|
|
@ -44,6 +44,10 @@ async fn main() {
|
||||||
"/accounts/id/:id/transactions/add",
|
"/accounts/id/:id/transactions/add",
|
||||||
post(routes::ui::account::add_transactions_action),
|
post(routes::ui::account::add_transactions_action),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/accounts/id/:id/transactions",
|
||||||
|
get(routes::ui::account::list_transactions),
|
||||||
|
)
|
||||||
.route("/transaction/:id", get(routes::ui::transaction::view))
|
.route("/transaction/:id", get(routes::ui::transaction::view))
|
||||||
.route("/transaction/:id", post(routes::ui::transaction::update))
|
.route("/transaction/:id", post(routes::ui::transaction::update))
|
||||||
.route(
|
.route(
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ use accounters::models::{account::Account, categories::Category, transaction::Tr
|
||||||
pub struct AccountViewParams {
|
pub struct AccountViewParams {
|
||||||
from: Option<String>,
|
from: Option<String>,
|
||||||
to: Option<String>,
|
to: Option<String>,
|
||||||
entries: Option<i32>,
|
|
||||||
page: Option<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_date(s: &str) -> Option<DateTime<Utc>> {
|
fn parse_date(s: &str) -> Option<DateTime<Utc>> {
|
||||||
|
|
@ -30,17 +28,12 @@ fn parse_date(s: &str) -> Option<DateTime<Utc>> {
|
||||||
Utc.with_ymd_and_hms(year, month, day, 0, 0, 0).single()
|
Utc.with_ymd_and_hms(year, month, day, 0, 0, 0).single()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list(
|
pub async fn show(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
State(tmpls): State<Arc<Tera>>,
|
State(tmpls): State<Arc<Tera>>,
|
||||||
uid: UserToken,
|
uid: UserToken,
|
||||||
Path(account_id): Path<i32>,
|
Path(account_id): Path<i32>,
|
||||||
Query(AccountViewParams {
|
Query(AccountViewParams { from, to }): Query<AccountViewParams>,
|
||||||
from,
|
|
||||||
to,
|
|
||||||
entries,
|
|
||||||
page,
|
|
||||||
}): Query<AccountViewParams>,
|
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
|
|
||||||
|
|
@ -87,6 +80,60 @@ pub async fn list(
|
||||||
.collect();
|
.collect();
|
||||||
ctx.insert("categories", &categories);
|
ctx.insert("categories", &categories);
|
||||||
|
|
||||||
|
let txs = match Transaction::list(db.as_ref(), account.get_id(), 10, 0, false).await {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
format!("Error at loading transactions: {e}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.insert("account", &account);
|
||||||
|
ctx.insert("transactions", &txs);
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[(CONTENT_TYPE, "text/html;charset=utf-8")],
|
||||||
|
tmpls.render("account_summary.html", &ctx).unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct AccountTxListParams {
|
||||||
|
entries: Option<i32>,
|
||||||
|
page: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_transactions(
|
||||||
|
State(db): State<Arc<SqlitePool>>,
|
||||||
|
State(tmpls): State<Arc<Tera>>,
|
||||||
|
uid: UserToken,
|
||||||
|
Path(account_id): Path<i32>,
|
||||||
|
Query(AccountTxListParams { entries, page }): Query<AccountTxListParams>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
|
||||||
|
let account = match Account::get_by_id(db.as_ref(), account_id).await {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
format!("{e}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if account.get_user() != uid.user_id {
|
||||||
|
return (
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
String::from("You cannot access this resource"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let n_entries = entries.unwrap_or(10).max(10);
|
let n_entries = entries.unwrap_or(10).max(10);
|
||||||
let page = page.unwrap_or(0).max(0);
|
let page = page.unwrap_or(0).max(0);
|
||||||
|
|
||||||
|
|
@ -119,7 +166,7 @@ pub async fn list(
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[(CONTENT_TYPE, "text/html;charset=utf-8")],
|
[(CONTENT_TYPE, "text/html;charset=utf-8")],
|
||||||
tmpls.render("accounts.html", &ctx).unwrap(),
|
tmpls.render("account_txs.html", &ctx).unwrap(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue