Code cleanup

This commit is contained in:
Manuel Forcén Muñoz 2024-05-22 23:24:13 +02:00
parent c9632b3d98
commit b676be8cb2
9 changed files with 33 additions and 273 deletions

View file

@ -22,7 +22,6 @@
</div> </div>
<div class="mb-2"> <div class="mb-2">
<h2>Last transactions</h2> <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> <a class="ars-button" href="/accounts/id/{{account.account_id}}/transactions">More</a>
<table width="100%"> <table width="100%">
<thead> <thead>
@ -57,13 +56,6 @@
<script src="https://cdn.jsdelivr.net/npm/litepicker/dist/litepicker.js"></script> <script src="https://cdn.jsdelivr.net/npm/litepicker/dist/litepicker.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script> <script>
function onRecategorize() {
fetch(
'/api/v1/accounts/id/{{account.account_id}}/recategorize',
{method: 'POST'}
).then(e=>console.log(e));
}
function onDateChange(e) { function onDateChange(e) {
let date_val = document.getElementById('amount-date-range').value.split(' - '); let date_val = document.getElementById('amount-date-range').value.split(' - ');

View file

@ -7,21 +7,8 @@
<a href="/accounts/id/{{account.account_id}}/transactions/add">+</a> <a href="/accounts/id/{{account.account_id}}/transactions/add">+</a>
</div> </div>
</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"> <div class="mb-2">
<h2>Last transactions</h2> <h2>Transactions</h2>
<button class="ars-button" onclick="onRecategorize()">Recategorize</button> <button class="ars-button" onclick="onRecategorize()">Recategorize</button>
<table width="100%"> <table width="100%">
<thead> <thead>
@ -73,8 +60,6 @@
border-spacing: 0.2rem; border-spacing: 0.2rem;
} }
</style> </style>
<script src="https://cdn.jsdelivr.net/npm/litepicker/dist/litepicker.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script> <script>
function onRecategorize() { function onRecategorize() {
fetch( fetch(
@ -83,50 +68,15 @@
).then(e=>console.log(e)); ).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) { function onSelect(e) {
let params = new URLSearchParams(window.location.search); let params = new URLSearchParams(window.location.search);
params.set("entries", e.target.value); params.set("entries", e.target.value);
window.location.search = params.toString(); 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) { function formatDate(date) {
return date.substr(0, date.indexOf('T')); 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> </script>
{% endblock body %} {% endblock body %}

View file

@ -3,7 +3,7 @@
{% block body %} {% block body %}
<div class="mb-8"> <div class="mb-8">
<div> <div>
<a href="/classifiers/new_category">New</a> <a class="ars-button" href="/classifiers/new_category">New</a>
</div> </div>
<table width="100%"> <table width="100%">
<thead> <thead>
@ -27,7 +27,7 @@
<div> <div>
<div> <div>
<a href="/classifiers/new_rule">New</a> <a class="ars-button" href="/classifiers/new_rule">New</a>
</div> </div>
<table width="100%"> <table width="100%">
<thead> <thead>

View file

@ -38,37 +38,30 @@ async fn main() {
.route("/accounts/id/:id", get(routes::ui::account::show)) .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)
) .post(routes::ui::account::add_transactions_action),
.route(
"/accounts/id/:id/transactions/add",
post(routes::ui::account::add_transactions_action),
) )
.route( .route(
"/accounts/id/:id/transactions", "/accounts/id/:id/transactions",
get(routes::ui::account::list_transactions), get(routes::ui::account::list_transactions),
) )
.route("/transaction/:id", get(routes::ui::transaction::view)) .route(
.route("/transaction/:id", post(routes::ui::transaction::update)) "/transaction/:id",
get(routes::ui::transaction::view).post(routes::ui::transaction::update),
)
.route( .route(
"/classifiers", "/classifiers",
get(routes::ui::classifier::view_classifiers), get(routes::ui::classifier::view_classifiers),
) )
.route( .route(
"/classifiers/new_rule", "/classifiers/new_rule",
get(routes::ui::classifier::rules_new_view), get(routes::ui::classifier::rules_new_view)
) .post(routes::ui::classifier::rules_new_action),
.route(
"/classifiers/new_rule",
post(routes::ui::classifier::rules_new_action),
) )
.route( .route(
"/classifiers/new_category", "/classifiers/new_category",
get(routes::ui::classifier::category_new_view), get(routes::ui::classifier::category_new_view)
) .post(routes::ui::classifier::category_new_action),
.route(
"/classifiers/new_category",
post(routes::ui::classifier::category_new_action),
) )
.nest( .nest(
"/static", "/static",
@ -115,6 +108,7 @@ async fn main() {
web_view::builder() web_view::builder()
.title("Test") .title("Test")
.content(web_view::Content::Url("http://localhost:3000")) .content(web_view::Content::Url("http://localhost:3000"))
.size(1024, 600)
.user_data(()) .user_data(())
.invoke_handler(|_wv, _arg| Ok(())) .invoke_handler(|_wv, _arg| Ok(()))
.run() .run()

View file

@ -1,7 +1,7 @@
use std::{borrow::BorrowMut, collections::HashMap, sync::Arc}; use std::{borrow::BorrowMut, collections::HashMap, sync::Arc};
use axum::{extract::State, response::IntoResponse}; use axum::{extract::State, response::IntoResponse};
use chrono::{DateTime, Utc}; use chrono::Utc;
use hyper::{header::CONTENT_TYPE, StatusCode}; use hyper::{header::CONTENT_TYPE, StatusCode};
use serde::Serialize; use serde::Serialize;
use sqlx::SqlitePool; use sqlx::SqlitePool;
@ -11,9 +11,7 @@ use crate::users::UserToken;
use accounters::models::{account::Account, categories::Category, transaction::Transaction}; use accounters::models::{account::Account, categories::Category, transaction::Transaction};
pub mod account; pub mod account;
pub mod categories;
pub mod classifier; pub mod classifier;
pub mod rules;
pub mod transaction; pub mod transaction;
#[derive(Serialize)] #[derive(Serialize)]

View file

@ -134,6 +134,14 @@ pub async fn list_transactions(
); );
} }
let categories: HashMap<i32, String> = Category::list(db.as_ref())
.await
.unwrap()
.iter()
.map(|x| (x.category_id, x.name.clone()))
.collect();
ctx.insert("categories", &categories);
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);

View file

@ -1,72 +0,0 @@
use std::sync::Arc;
use accounters::models::categories::Category;
use axum::{
extract::{Form, State},
response::IntoResponse,
};
use hyper::{header::CONTENT_TYPE, StatusCode};
use serde::Deserialize;
use sqlx::SqlitePool;
use tera::{Context, Tera};
use crate::users::UserToken;
pub async fn list(
State(db): State<Arc<SqlitePool>>,
State(tmpl): State<Arc<Tera>>,
uid: UserToken,
) -> impl IntoResponse {
match Category::list(db.as_ref()).await {
Ok(categories) => {
let mut ctx = Context::new();
ctx.insert("categories", &categories);
(
StatusCode::OK,
[(CONTENT_TYPE, "text/html;charset=utf-8")],
tmpl.render("categories_list.html", &ctx).unwrap(),
)
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
[(CONTENT_TYPE, "text/plain;charset=utf-8")],
format!("{e}"),
),
}
}
pub async fn new_view(State(tmpl): State<Arc<Tera>>, uid: UserToken) -> impl IntoResponse {
(
StatusCode::OK,
[(CONTENT_TYPE, "text/html;charset=utf-8")],
tmpl.render("categories_new.html", &Context::new()).unwrap(),
)
}
#[derive(Deserialize)]
pub struct NewRuleParams {
pub name: String,
pub description: String,
}
pub async fn new_action(
State(db): State<Arc<SqlitePool>>,
State(tmpls): State<Arc<Tera>>,
uid: UserToken,
Form(params): Form<NewRuleParams>,
) -> impl IntoResponse {
match Category::new(db.as_ref(), &params.name, &params.description).await {
Ok(_) => (
StatusCode::OK,
[(CONTENT_TYPE, "text/html;charset=utf-8")],
tmpls
.render("rules_new_success.html", &Context::new())
.unwrap(),
),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
[(CONTENT_TYPE, "text/plain;charset=utf-8")],
format!("{e}"),
),
}
}

View file

@ -5,7 +5,10 @@ use axum::{
extract::{Form, State}, extract::{Form, State},
response::IntoResponse, response::IntoResponse,
}; };
use hyper::{header::CONTENT_TYPE, StatusCode}; use hyper::{
header::{CONTENT_TYPE, LOCATION},
StatusCode,
};
use serde::Deserialize; use serde::Deserialize;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tera::{Context, Tera}; use tera::{Context, Tera};
@ -75,17 +78,14 @@ pub struct NewRuleParams {
pub async fn rules_new_action( pub async fn rules_new_action(
State(db): State<Arc<SqlitePool>>, State(db): State<Arc<SqlitePool>>,
State(tmpls): State<Arc<Tera>>,
uid: UserToken, uid: UserToken,
Form(params): Form<NewRuleParams>, Form(params): Form<NewRuleParams>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match Rule::new(db.as_ref(), uid.user_id, params.regex, params.category).await { match Rule::new(db.as_ref(), uid.user_id, params.regex, params.category).await {
Ok(_) => ( Ok(_) => (
StatusCode::OK, StatusCode::MOVED_PERMANENTLY,
[(CONTENT_TYPE, "text/html;charset=utf-8")], [(LOCATION, "/classifiers")],
tmpls String::new(),
.render("rules_new_success.html", &Context::new())
.unwrap(),
), ),
Err(e) => ( Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
@ -95,29 +95,6 @@ pub async fn rules_new_action(
} }
} }
pub async fn category_list(
State(db): State<Arc<SqlitePool>>,
State(tmpl): State<Arc<Tera>>,
uid: UserToken,
) -> impl IntoResponse {
match Category::list(db.as_ref()).await {
Ok(categories) => {
let mut ctx = Context::new();
ctx.insert("categories", &categories);
(
StatusCode::OK,
[(CONTENT_TYPE, "text/html;charset=utf-8")],
tmpl.render("categories_list.html", &ctx).unwrap(),
)
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
[(CONTENT_TYPE, "text/plain;charset=utf-8")],
format!("{e}"),
),
}
}
pub async fn category_new_view(State(tmpl): State<Arc<Tera>>, uid: UserToken) -> impl IntoResponse { pub async fn category_new_view(State(tmpl): State<Arc<Tera>>, uid: UserToken) -> impl IntoResponse {
( (
StatusCode::OK, StatusCode::OK,
@ -134,17 +111,14 @@ pub struct CategoryNewRuleParams {
pub async fn category_new_action( pub async fn category_new_action(
State(db): State<Arc<SqlitePool>>, State(db): State<Arc<SqlitePool>>,
State(tmpls): State<Arc<Tera>>,
uid: UserToken, uid: UserToken,
Form(params): Form<CategoryNewRuleParams>, Form(params): Form<CategoryNewRuleParams>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match Category::new(db.as_ref(), &params.name, &params.description).await { match Category::new(db.as_ref(), &params.name, &params.description).await {
Ok(_) => ( Ok(_) => (
StatusCode::OK, StatusCode::MOVED_PERMANENTLY,
[(CONTENT_TYPE, "text/html;charset=utf-8")], [(LOCATION, "/classifiers")],
tmpls String::new(),
.render("rules_new_success.html", &Context::new())
.unwrap(),
), ),
Err(e) => ( Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,

View file

@ -1,84 +0,0 @@
use std::sync::Arc;
use accounters::models::{categories::Category, rules::Rule};
use axum::{
extract::{Form, State},
response::IntoResponse,
};
use hyper::{header::CONTENT_TYPE, StatusCode};
use serde::Deserialize;
use sqlx::SqlitePool;
use tera::{Context, Tera};
use crate::users::UserToken;
pub async fn list(
State(db): State<Arc<SqlitePool>>,
State(tmpls): State<Arc<Tera>>,
uid: UserToken,
) -> impl IntoResponse {
let rules = match Rule::list_by_user(db.as_ref(), uid.user_id).await {
Ok(r) => r,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
[(CONTENT_TYPE, "text/plain")],
format!("{e:?}"),
);
}
};
let mut ctx = Context::new();
ctx.insert("rules", &rules);
(
StatusCode::OK,
[(CONTENT_TYPE, "text/html;charset=utf-8")],
tmpls.render("rules_list.html", &ctx).unwrap(),
)
}
pub async fn new_view(
State(db): State<Arc<SqlitePool>>,
State(tmpls): State<Arc<Tera>>,
uid: UserToken,
) -> impl IntoResponse {
let categories = Category::list(db.as_ref()).await.unwrap();
let mut ctx = Context::new();
ctx.insert("categories", &categories);
(
StatusCode::OK,
[(CONTENT_TYPE, "text/html;charset=utf-8")],
tmpls.render("rules_new.html", &ctx).unwrap(),
)
}
#[derive(Deserialize)]
pub struct NewRuleParams {
pub description: String,
pub regex: String,
pub category: i32,
}
pub async fn new_action(
State(db): State<Arc<SqlitePool>>,
State(tmpls): State<Arc<Tera>>,
uid: UserToken,
Form(params): Form<NewRuleParams>,
) -> impl IntoResponse {
match Rule::new(db.as_ref(), uid.user_id, params.regex, params.category).await {
Ok(_) => (
StatusCode::OK,
[(CONTENT_TYPE, "text/html;charset=utf-8")],
tmpls
.render("rules_new_success.html", &Context::new())
.unwrap(),
),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
[(CONTENT_TYPE, "text/plain;charset=utf-8")],
format!("{e}"),
),
}
}