diff options
| author | Joris Guyonvarch | 2026-04-18 11:04:47 +0200 |
|---|---|---|
| committer | Joris Guyonvarch | 2026-04-18 11:05:17 +0200 |
| commit | 6d1300640051baa23360846197b54e1e69ae32e3 (patch) | |
| tree | 46219dcf5b5c9e5da0920ffd966d49ba80947a9b | |
| parent | b35589eb90f2e5ee5521964e64eb578e9eb99032 (diff) | |
Add balancing capabilities
If payment are too unbalanced, it’s easier to make a transfer.
| -rw-r--r-- | assets/main.js | 9 | ||||
| -rw-r--r-- | src/controller/balance.rs | 20 | ||||
| -rw-r--r-- | src/controller/balancing.rs | 189 | ||||
| -rw-r--r-- | src/controller/mod.rs | 1 | ||||
| -rw-r--r-- | src/db/balancing.rs | 260 | ||||
| -rw-r--r-- | src/db/migrations/07-create-balancing-table.sql | 9 | ||||
| -rw-r--r-- | src/db/mod.rs | 2 | ||||
| -rw-r--r-- | src/model/balancing.rs | 29 | ||||
| -rw-r--r-- | src/model/mod.rs | 1 | ||||
| -rw-r--r-- | src/queries.rs | 6 | ||||
| -rw-r--r-- | src/routes.rs | 47 | ||||
| -rw-r--r-- | src/templates.rs | 1 | ||||
| -rw-r--r-- | src/validation/balancing.rs | 34 | ||||
| -rw-r--r-- | src/validation/mod.rs | 1 | ||||
| -rw-r--r-- | templates/balancing/create.html | 79 | ||||
| -rw-r--r-- | templates/balancing/table.html | 56 | ||||
| -rw-r--r-- | templates/balancing/update.html | 105 | ||||
| -rw-r--r-- | templates/base.html | 10 |
18 files changed, 858 insertions, 1 deletions
diff --git a/assets/main.js b/assets/main.js index c83e4fc..7ca4306 100644 --- a/assets/main.js +++ b/assets/main.js @@ -41,6 +41,15 @@ if (path == '/login') { trim_inputs_on_blur() control_remove_button() +} else if (path == '/balancing') { // Balancing creation + + trim_inputs_on_blur() + +} else if (path.startsWith('/balancing/')) { // Balancing modification + + trim_inputs_on_blur() + control_remove_button() + } else if (path == '/balance') { } else if (path == '/statistics') { diff --git a/src/controller/balance.rs b/src/controller/balance.rs index 309f15c..6cb937b 100644 --- a/src/controller/balance.rs +++ b/src/controller/balance.rs @@ -7,6 +7,7 @@ use crate::controller::utils; use crate::controller::wallet::Wallet; use crate::db; use crate::model::user::User; +use crate::model::balancing::Balancing; use crate::payer; use crate::templates; @@ -22,7 +23,10 @@ pub async fn get(wallet: &Wallet) -> Response<Full<Bytes>> { get_template_user_incomes(&users, &user_incomes); let total_income: i64 = user_incomes.values().sum(); - let user_payments = db::payments::repartition(&wallet.db_conn).await; + let user_payments = with_balancing( + db::payments::repartition(&wallet.db_conn).await, + db::balancing::list(&wallet.db_conn).await + ); let template_user_payments = get_template_user_payments(&users, &user_payments); let total_payments: i64 = user_payments.iter().map(|p| p.1).sum(); @@ -67,3 +71,17 @@ fn get_template_user_incomes( user_incomes.sort_by_key(|i| i.1); user_incomes } + +fn with_balancing( + user_payments: HashMap<i64, i64>, + balancings: Vec<Balancing> +) -> HashMap<i64, i64> { + let mut user_payments = user_payments; + for balancing in balancings { + let src = user_payments.entry(balancing.source).or_insert(0); + *src += balancing.amount; + let dest = user_payments.entry(balancing.destination).or_insert(0); + *dest -= balancing.amount; + } + user_payments +} diff --git a/src/controller/balancing.rs b/src/controller/balancing.rs new file mode 100644 index 0000000..718358c --- /dev/null +++ b/src/controller/balancing.rs @@ -0,0 +1,189 @@ +use http_body_util::Full; +use hyper::Response; +use hyper::body::Bytes; +use std::collections::HashMap; + +use crate::controller::utils; +use crate::controller::wallet::Wallet; +use crate::db; +use crate::queries; +use crate::templates; +use crate::validation; + +static PER_PAGE: i64 = 10; + +pub async fn table( + wallet: &Wallet, + query: queries::Balancing, +) -> Response<Full<Bytes>> { + let page = query.page.unwrap_or(1); + let count = db::balancing::count(&wallet.db_conn).await; + let balancings = db::balancing::list_for_table(&wallet.db_conn, page, PER_PAGE).await; + let max_page = (count as f32 / PER_PAGE as f32).ceil() as i64; + + let context = minijinja::context!( + header => templates::Header::Balancing, + connected_user => wallet.user, + balancings => balancings, + page => page, + max_page => max_page, + highlight => query.highlight + ); + + utils::template( + &wallet.assets, + &wallet.templates, + "balancing/table.html", + context, + ) +} + +pub async fn create_form( + wallet: &Wallet, + query: queries::Balancing, +) -> Response<Full<Bytes>> { + create_form_feedback(wallet, query, HashMap::new(), None).await +} + +async fn create_form_feedback( + wallet: &Wallet, + query: queries::Balancing, + form: HashMap<String, String>, + error: Option<String>, +) -> Response<Full<Bytes>> { + let users = db::users::list(&wallet.db_conn).await; + + let context = minijinja::context!( + header => templates::Header::Balancing, + connected_user => wallet.user, + users => users, + query => query, + form => form, + error => error, + ); + + utils::template( + &wallet.assets, + &wallet.templates, + "balancing/create.html", + context, + ) +} + + +pub async fn create( + wallet: &Wallet, + query: queries::Balancing, + form: HashMap<String, String>, +) -> Response<Full<Bytes>> { + let error = |e: &str| { + create_form_feedback(wallet, query, form.clone(), Some(e.to_string())) + }; + + match validation::balancing::create(&form) { + Some(balancing) => { + match db::balancing::create(&wallet.db_conn, balancing).await { + Some(id) => { + let row = + db::balancing::get_row(&wallet.db_conn, id).await; + let page = (row - 1) / PER_PAGE + 1; + utils::redirect(&format!( + "/balancings?page={}&highlight={}", + page, id + )) + } + None => error("Erreur serveur").await, + } + } + None => error("Erreur lors de la validation du formulaire.").await, + } +} + +pub async fn update_form( + id: i64, + wallet: &Wallet, + query: queries::Balancing, +) -> Response<Full<Bytes>> { + update_form_feedback(id, wallet, query, HashMap::new(), None).await +} + +async fn update_form_feedback( + id: i64, + wallet: &Wallet, + query: queries::Balancing, + form: HashMap<String, String>, + error: Option<String>, +) -> Response<Full<Bytes>> { + let users = db::users::list(&wallet.db_conn).await; + let balancing = db::balancing::get(&wallet.db_conn, id).await; + + let context = minijinja::context!( + header => &templates::Header::Balancing, + connected_user => &wallet.user, + users => &users, + id => &id, + balancing => &balancing, + query => &query, + form => &form, + error => &error + ); + + utils::template( + &wallet.assets, + &wallet.templates, + "balancing/update.html", + context, + ) +} + +pub async fn update( + id: i64, + wallet: &Wallet, + query: queries::Balancing, + form: HashMap<String, String>, +) -> Response<Full<Bytes>> { + let error = |e: &str| { + update_form_feedback( + id, + wallet, + query, + form.clone(), + Some(e.to_string()), + ) + }; + + match validation::balancing::update(&form) { + Some(balancing) => { + if db::balancing::update(&wallet.db_conn, id, balancing).await { + let row = db::balancing::get_row(&wallet.db_conn, id).await; + let page = (row - 1) / PER_PAGE + 1; + utils::redirect(&format!( + "/balancings?page={}&highlight={}", + page, id + )) + } else { + error("Erreur serveur").await + } + } + None => error("Erreur lors de la validation du formulaire.").await, + } +} + +pub async fn delete( + id: i64, + wallet: &Wallet, + query: queries::Balancing, +) -> Response<Full<Bytes>> { + if db::balancing::delete(&wallet.db_conn, id).await { + utils::redirect(&format!("/balancings?page={}", query.page.unwrap_or(1))) + } else { + update_form_feedback( + id, + wallet, + query, + HashMap::new(), + Some("Erreur serveur".to_string()), + ) + .await + } +} diff --git a/src/controller/mod.rs b/src/controller/mod.rs index e2ef561..bbead68 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -1,4 +1,5 @@ pub mod balance; +pub mod balancing; pub mod categories; pub mod error; pub mod incomes; diff --git a/src/db/balancing.rs b/src/db/balancing.rs new file mode 100644 index 0000000..1641b97 --- /dev/null +++ b/src/db/balancing.rs @@ -0,0 +1,260 @@ +use tokio_rusqlite::{Connection, Row, named_params}; + +use crate::db::utils; +use crate::model::balancing::{Balancing, Create, Update, TableRow}; + +fn row_to_balancing(row: &Row) -> Result<Balancing, rusqlite::Error> { + Ok(Balancing { + id: row.get(0)?, + source: row.get(1)?, + destination: row.get(2)?, + amount: row.get(3)?, + }) +} + +fn row_to_table_row(row: &Row) -> Result<TableRow, rusqlite::Error> { + Ok(TableRow { + id: row.get(0)?, + source: row.get(1)?, + destination: row.get(2)?, + amount: row.get(3)?, + }) +} + +pub async fn count(conn: &Connection) -> i64 { + let query = r#" + SELECT COUNT(*) + FROM balancing + WHERE balancing.deleted_at IS NULL + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = stmt.query_map([], |row| row.get(0))?; + utils::one::<i64, _>(&mut iter) + }) + .await; + + match res { + Ok(count) => count, + Err(err) => { + log::error!("Error counting balancing: {:?}", err); + 0 + } + } +} + +pub async fn list_for_table(conn: &Connection, page: i64, per_page: i64) -> Vec<TableRow> { + let query = r#" + SELECT + balancing.id, + users_src.name, + users_dest.name, + balancing.amount + FROM balancing + INNER JOIN users AS users_src ON users_src.id = balancing.source + INNER JOIN users AS users_dest ON users_dest.id = balancing.destination + WHERE balancing.deleted_at IS NULL + ORDER BY balancing.created_at DESC + LIMIT :limit + OFFSET :offset + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + + stmt.query_map( + named_params![":limit": per_page, ":offset": (page - 1) * per_page], + row_to_table_row + )? + .collect::<Result<Vec<TableRow>, _>>() + }) + .await; + + match res { + Ok(xs) => xs, + Err(err) => { + log::error!("Error listing balancing: {:?}", err); + vec![] + } + } +} +pub async fn list(conn: &Connection) -> Vec<Balancing> { + let query = r#" + SELECT + id, + source, + destination, + amount + FROM balancing + WHERE deleted_at IS NULL + ORDER BY created_at DESC + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + + stmt.query_map([], row_to_balancing)? + .collect::<Result<Vec<Balancing>, _>>() + }) + .await; + + match res { + Ok(xs) => xs, + Err(err) => { + log::error!("Error listing balancing: {:?}", err); + vec![] + } + } +} + +pub async fn get_row(conn: &Connection, id: i64) -> i64 { + let query = r#" + SELECT row + FROM ( + SELECT + ROW_NUMBER () OVER (ORDER BY created_at DESC) AS row, + id + FROM balancing + WHERE deleted_at IS NULL + ) + WHERE id = :id + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = + stmt.query_map(named_params![":id": id], |row| row.get(0))?; + utils::one::<i64, _>(&mut iter) + }) + .await; + + match res { + Ok(row) => row, + Err(err) => { + log::error!("Error getting balancing row: {:?}", err); + 1 + } + } +} + +pub async fn get(conn: &Connection, id: i64) -> Option<Balancing> { + let query = r#" + SELECT + id, + source, + destination, + amount + FROM balancing + WHERE + id = :id + AND deleted_at IS NULL + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = + stmt.query_map(named_params![":id": id], row_to_balancing)?; + utils::one(&mut iter) + }) + .await; + + match res { + Ok(balancing) => Some(balancing), + Err(err) => { + log::error!("Error looking for balancing {}: {:?}", id, err); + None + } + } +} + +pub async fn create(conn: &Connection, c: Create) -> Option<i64> { + let query = r#"INSERT INTO balancing(source, destination, amount) VALUES (:source, :destination, :amount)"#; + + let res: Result<_, tokio_rusqlite::Error<rusqlite::Error>> = conn + .call(move |conn| { + conn.execute( + query, + named_params![ + ":source": c.source, + ":destination": c.destination, + ":amount": c.amount, + ], + )?; + Ok(conn.last_insert_rowid()) + }) + .await; + + match res { + Ok(balancing_id) => Some(balancing_id), + Err(err) => { + log::error!("Error creating balancing: {:?}", err); + None + } + } +} + +pub async fn update(conn: &Connection, id: i64, c: Update) -> bool { + let query = r#" + UPDATE balancing + SET + source = :source, + destination = :destination, + amount = :amount, + updated_at = datetime() + WHERE id = :id + "#; + + let res = conn + .call(move |conn| { + conn.execute( + query, + named_params![ + ":source": c.source, + ":destination": c.destination, + ":amount": c.amount, + ":id": id + ], + ) + }) + .await; + + match res { + Ok(_) => true, + Err(err) => { + log::error!("Error updating balancing {}: {:?}", id, err); + false + } + } +} + +pub async fn delete(conn: &Connection, id: i64) -> bool { + let res = conn + .call(move |conn| { + conn.execute( + r#" + UPDATE + balancing + SET + deleted_at = datetime() + WHERE + id = :id + "#, + named_params![":id": id], + ) + }) + .await; + + match res { + Ok(_) => true, + Err(err) => { + log::error!("Error deleting balancing {}: {:?}", id, err); + false + } + } +} diff --git a/src/db/migrations/07-create-balancing-table.sql b/src/db/migrations/07-create-balancing-table.sql new file mode 100644 index 0000000..148657e --- /dev/null +++ b/src/db/migrations/07-create-balancing-table.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS "balancing"( + "id" INTEGER PRIMARY KEY, + "source" INTEGER NOT NULL REFERENCES "users", + "destination" INTEGER NOT NULL REFERENCES "users", + "amount" INTEGER NOT NULL, + "created_at" TEXT NULL DEFAULT (datetime('now')), + "updated_at" TEXT NULL, + "deleted_at" TEXT NULL +) STRICT; diff --git a/src/db/mod.rs b/src/db/mod.rs index c444995..d257bf1 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -2,6 +2,7 @@ use anyhow::{Error, Result}; use rusqlite_migration::{M, Migrations}; use tokio_rusqlite::Connection; +pub mod balancing; pub mod categories; pub mod incomes; pub mod jobs; @@ -28,6 +29,7 @@ async fn apply_migrations(conn: &Connection) -> Result<()> { M::up(include_str!("migrations/04-plural-naming.sql")), M::up(include_str!("migrations/05-strict-tables.sql")), M::up(include_str!("migrations/06-remove-weekly-report-job.sql")), + M::up(include_str!("migrations/07-create-balancing-table.sql")), ]); Ok(conn.call(move |conn| migrations.to_latest(conn)).await?) diff --git a/src/model/balancing.rs b/src/model/balancing.rs new file mode 100644 index 0000000..fe480f2 --- /dev/null +++ b/src/model/balancing.rs @@ -0,0 +1,29 @@ +#[derive(Debug, serde::Serialize)] +pub struct TableRow { + pub id: i64, + pub source: String, + pub destination: String, + pub amount: i64, +} + +#[derive(serde::Serialize, Clone)] +pub struct Balancing { + pub id: i64, + pub source: i64, + pub destination: i64, + pub amount: i64, +} + +#[derive(Debug)] +pub struct Create { + pub source: i64, + pub destination: i64, + pub amount: i64, +} + +#[derive(Debug)] +pub struct Update { + pub source: i64, + pub destination: i64, + pub amount: i64, +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 55adadd..9381103 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,3 +1,4 @@ +pub mod balancing; pub mod category; pub mod config; pub mod frequency; diff --git a/src/queries.rs b/src/queries.rs index 86a8520..519b00e 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -79,6 +79,12 @@ pub struct Categories { pub highlight: Option<i64>, } +#[derive(serde::Serialize, serde::Deserialize, Clone)] +pub struct Balancing { + pub page: Option<i64>, + pub highlight: Option<i64>, +} + #[derive(serde::Serialize, serde::Deserialize)] pub struct PaymentCategory { pub payment_name: String, diff --git a/src/routes.rs b/src/routes.rs index 8abe1b4..3afc822 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -86,6 +86,7 @@ async fn authenticated_routes( let query = uri.query(); match (method, path) { + // PAYMENTS (&Method::GET, [""]) => { controller::payments::table(&wallet, parse_query(query)).await } @@ -129,6 +130,7 @@ async fn authenticated_routes( ) .await } + // INCOMES (&Method::GET, ["incomes"]) => { controller::incomes::table(&wallet, parse_query(query)).await } @@ -168,6 +170,7 @@ async fn authenticated_routes( ) .await } + // CATEGORIES (&Method::GET, ["categories"]) => { controller::categories::table(&wallet, parse_query(query)).await } @@ -192,13 +195,57 @@ async fn authenticated_routes( (&Method::POST, ["category", id, "delete"]) => { controller::categories::delete(parse_id(id), &wallet).await } + // BALANCING + (&Method::GET, ["balancings"]) => { + controller::balancing::table(&wallet, parse_query(query)).await + } + (&Method::GET, ["balancing"]) => { + controller::balancing::create_form(&wallet, parse_query(query)).await + } + (&Method::POST, ["balancing", "create"]) => { + controller::balancing::create( + &wallet, + parse_query(query), + body_form(request).await, + ) + .await + } + (&Method::GET, ["balancing", id]) => { + controller::balancing::update_form( + parse_id(id), + &wallet, + parse_query(query), + ) + .await + } + (&Method::POST, ["balancing", id, "update"]) => { + controller::balancing::update( + parse_id(id), + &wallet, + parse_query(query), + body_form(request).await, + ) + .await + } + (&Method::POST, ["balancing", id, "delete"]) => { + controller::balancing::delete( + parse_id(id), + &wallet, + parse_query(query), + ) + .await + } + // BALANCE (&Method::GET, ["balance"]) => controller::balance::get(&wallet).await, + // STATS (&Method::GET, ["statistics"]) => { controller::statistics::get(&wallet).await } + // LOGOUT (&Method::POST, ["logout"]) => { controller::login::logout(config, &wallet).await } + // ERROR _ => controller::error::error( &wallet, "Page introuvable", diff --git a/src/templates.rs b/src/templates.rs index 5ea91b4..2c65ddb 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -8,6 +8,7 @@ pub enum Header { Payments, Categories, Incomes, + Balancing, Balance, Statistics, } diff --git a/src/validation/balancing.rs b/src/validation/balancing.rs new file mode 100644 index 0000000..8892bca --- /dev/null +++ b/src/validation/balancing.rs @@ -0,0 +1,34 @@ +use std::collections::HashMap; + +use crate::model::balancing::{Create, Update}; +use crate::validation::utils::*; + +pub fn create(form: &HashMap<String, String>) -> Option<Create> { + let source = parse::<i64>(form, "source")?; + let destination = parse::<i64>(form, "destination")?; + + if source == destination { + None + } else { + Some(Create { + source, + destination, + amount: parse::<i64>(form, "amount")? + }) + } +} + +pub fn update(form: &HashMap<String, String>) -> Option<Update> { + let source = parse::<i64>(form, "source")?; + let destination = parse::<i64>(form, "destination")?; + + if source == destination { + None + } else { + Some(Update { + source, + destination, + amount: parse::<i64>(form, "amount")? + }) + } +} diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 181abc7..291eb4f 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -1,3 +1,4 @@ +pub mod balancing; pub mod category; pub mod income; pub mod login; diff --git a/templates/balancing/create.html b/templates/balancing/create.html new file mode 100644 index 0000000..c9bfdba --- /dev/null +++ b/templates/balancing/create.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} + +{% block title %} + Nouvel équilibrage +{% endblock title %} + +{% block main %} + + <section class="g-Section"> + <p class="g-Paragraph"> + <a + class="g-Link g-Media__Large" + href="/balancings?page={{ query.page or 1 }}" + > + Retour aux équilibrages + </a> + </p> + + <form + class="g-Form" + action="/balancing/create?page={{ query.page or 1 }}" + method="POST" + > + <h1 class="g-H1"> + Nouvel équilibrage + </h1> + + {% if error %} + <div class="g-Form__Error">{{ error }}</div> + {% endif %} + + <label class="g-Form__Label"> + Montant + <input + name="amount" + type="number" + class="g-Form__Input" + value="{{ form.amount or "" }}" + min=1 + required + {% if not form %} autofocus {% endif %} + /> + </label> + + {% set user_id = form.user_id or connected_user.id %} + + <label class="g-Form__Label"> + De + <select name="source" class="g-Form__Select" required> + {% for user in users %} + <option + value="{{ user.id }}" + {% if "" ~ user.id == "" ~ user_id %} selected {% endif %} + > + {{ user.name }} + </option> + {% endfor %} + </select> + </label> + + <label class="g-Form__Label"> + Vers + <select name="destination" class="g-Form__Select" required> + <option value="" disabled selected></option> + {% for user in users %} + <option value="{{ user.id }}"> + {{ user.name }} + </option> + {% endfor %} + </select> + </label> + + <div> + <input class="g-Button__Validate" type="submit" value="Créer" /> + </div> + </form> + </section> + +{% endblock main %} diff --git a/templates/balancing/table.html b/templates/balancing/table.html new file mode 100644 index 0000000..72f3b37 --- /dev/null +++ b/templates/balancing/table.html @@ -0,0 +1,56 @@ +{% import "macros/paging.html" as paging %} + +{% extends "base.html" %} + +{% block title %} + Équilibrages +{% endblock title %} + +{% block main %} + + <section class="g-Section"> + + {% if not balancings %} + <div class="g-Table__NoResults"> + Il n’y a aucun équilibrage. + </div> + {% endif %} + + <a + class="g-Paragraph g-Button__Validate" + href="/balancing?page={{ page or 1 }}" + > + Ajouter un équilibrage + </a> + + {% if balancings %} + <div class="g-Table"> + <div class="g-Table__Row g-Table__Row--Header"> + <span class="g-Table__Cell">Montant</span> + <span class="g-Table__Cell">De</span> + <span class="g-Table__Cell">Vers</span> + </div> + {% for balancing in balancings %} + <a + class="g-Table__Row {% if highlight == balancing.id %} g-Table__Row--Highlight {% endif %}" + href="/balancing/{{ balancing.id }}?page={{ page or 1 }}" + > + <span class="g-Table__Cell g-Table__NumericCell"> + {{ balancing.amount | euros() }} + </span> + <span class="g-Table__Cell">{{ balancing.source }}</span> + <span class="g-Table__Cell">{{ balancing.destination }}</span> + </a> + {% endfor %} + </div> + + {{ paging.view( + url="/balancings", + page=page, + max_page=max_page + ) }} + {% endif %} + + </section> + +{% endblock main %} diff --git a/templates/balancing/update.html b/templates/balancing/update.html new file mode 100644 index 0000000..9c98e93 --- /dev/null +++ b/templates/balancing/update.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} + +{% block title %} + Équilibrage {{ id }} +{% endblock title %} + +{% block main %} + + <section class="g-Section"> + <p class="g-Paragraph"> + <a + class="g-Link g-Media__Large" + href="/balancings?page={{ query.page or 1 }}" + > + Retour aux équilibrages + </a> + </p> + + {% if error %} + <div class="g-Form__Error">{{ error }}</div> + {% endif %} + + {% if not balancing %} + + L’équilibrage n’a pas été trouvé. + + {% else %} + + <form + class="g-Form" + action="/balancing/{{ balancing.id }}/update" + method="POST" + > + <h1 class="g-H1">Modification</h1> + + <label class="g-Form__Label"> + Montant + <input + name="amount" + type="number" + class="g-Form__Input" + value="{{ form.amount or balancing.amount }}" + min=1 + required + /> + </label> + + {% set source = form.source or balancing.source %} + + <label class="g-Form__Label"> + De + <select name="source" class="g-Form__Select" required> + {% for user in users %} + <option + value="{{ user.id }}" + {% if "" ~ user.id == "" ~ source %} selected {% endif %} + > + {{ user.name }} + </option> + {% endfor %} + </select> + </label> + + {% set destination = form.destination or balancing.destination %} + + <label class="g-Form__Label"> + Vers + <select name="destination" class="g-Form__Select" required> + {% for user in users %} + <option + value="{{ user.id }}" + {% if "" ~ user.id == "" ~ destination %} selected {% endif %} + > + {{ user.name }} + </option> + {% endfor %} + </select> + </label> + + <div> + <input class="g-Button__Validate" type="submit" value="Modifier" /> + </div> + </form> + + <form + class="g-Form" + action="/balancing/{{ balancing.id }}/delete" + method="POST" + > + <h1 class="g-H1">Suppression</h1> + + <label class="g-Form__Label"> + Veuillez recopier le montant de l’équilibrage : « {{ balancing.amount }} ». + <input name="remove-input" class="g-Form__Input" data-name="{{ balancing.amount }}" /> + </label> + + <div> + <input id="remove-button" class="g-Button__Danger" type="submit" value="Supprimer" disabled /> + </div> + </form> + + {% endif %} + </section> + +{% endblock main %} diff --git a/templates/base.html b/templates/base.html index 9865e16..e6f38aa 100644 --- a/templates/base.html +++ b/templates/base.html @@ -52,6 +52,16 @@ </a> <a + href="/balancings" + class=" + g-Nav__Link + {% if header == "Balancing" %} g-Nav__Link--Current {% endif %} + " + > + Équilibrage + </a> + + <a href="/balance" class=" g-Nav__Link |
