Implement basic page navigation w/ mock data
Obviously, we'll replace the mock `Database` with a Firefly III API client, but this is here for now to support the UI interactions.bugfix/ci-buildah
parent
837caecc3a
commit
b55fb893e2
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.html.tera",
|
||||
"options": {
|
||||
"parser": "html"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -10,3 +10,6 @@ license = "MIT OR Apache-2.0"
|
|||
keywords = ["personal-finance", "receipts"]
|
||||
|
||||
[dependencies]
|
||||
rocket = { version = "0.5.1", default-features = false, features = ["json"] }
|
||||
rocket_dyn_templates = { version = "0.2.0", features = ["tera"] }
|
||||
serde = { version = "1.0.218", default-features = false, features = ["derive"] }
|
||||
|
|
87
src/main.rs
87
src/main.rs
|
@ -1 +1,86 @@
|
|||
fn main() {}
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use rocket::form::Form;
|
||||
use rocket::fs::{FileServer, TempFile};
|
||||
use rocket::http::Status;
|
||||
use rocket::response::Redirect;
|
||||
use rocket::serde::Serialize;
|
||||
use rocket_dyn_templates::{context, Template};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Transaction {
|
||||
id: u32,
|
||||
amount: f64,
|
||||
description: String,
|
||||
date: String,
|
||||
}
|
||||
|
||||
struct Database {
|
||||
transactions: HashMap<i32, Transaction>,
|
||||
}
|
||||
|
||||
#[derive(rocket::FromForm)]
|
||||
struct TransactionPostData<'r> {
|
||||
amount: f32,
|
||||
notes: String,
|
||||
photo: Vec<TempFile<'r>>,
|
||||
}
|
||||
|
||||
static DB: LazyLock<Database> = LazyLock::new(|| {
|
||||
let mut transactions = HashMap::new();
|
||||
transactions.insert(
|
||||
5411,
|
||||
Transaction {
|
||||
id: 5411,
|
||||
amount: 140.38,
|
||||
description: "THE HOME DEPOT #2218".into(),
|
||||
date: "March 2nd, 2025".into(),
|
||||
},
|
||||
);
|
||||
Database { transactions }
|
||||
});
|
||||
|
||||
#[rocket::get("/")]
|
||||
async fn index() -> Redirect {
|
||||
Redirect::to(rocket::uri!(transaction_list()))
|
||||
}
|
||||
|
||||
#[rocket::get("/transactions")]
|
||||
async fn transaction_list() -> Template {
|
||||
let transactions: Vec<_> = DB.transactions.values().collect();
|
||||
Template::render("transaction-list", context! {
|
||||
transactions: transactions,
|
||||
})
|
||||
}
|
||||
|
||||
#[rocket::get("/transactions/<id>")]
|
||||
async fn get_transaction(id: i32) -> Option<Template> {
|
||||
let txn = DB.transactions.get(&id)?;
|
||||
Some(Template::render("transaction", txn))
|
||||
}
|
||||
|
||||
#[rocket::post("/transactions/<id>", data = "<form>")]
|
||||
async fn update_transaction(
|
||||
id: f32,
|
||||
form: Form<TransactionPostData<'_>>,
|
||||
) -> (Status, &'static str) {
|
||||
println!("{} {} {}", id, form.amount, form.photo.len());
|
||||
(Status::ImATeapot, "")
|
||||
}
|
||||
|
||||
#[rocket::launch]
|
||||
async fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.mount(
|
||||
"/",
|
||||
rocket::routes![
|
||||
index,
|
||||
transaction_list,
|
||||
get_transaction,
|
||||
update_transaction
|
||||
],
|
||||
)
|
||||
.mount("/static", FileServer::from("js/dist"))
|
||||
.attach(Template::fairing())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charset="utf-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
href="/static/icons/icon-192.png"
|
||||
sizes="192x192"
|
||||
type="image/png"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png" />
|
||||
<link rel="stylesheet" href="/static/common.css" />
|
||||
<script src="/static/common.js"></script>
|
||||
{% block head %}{% endblock -%}
|
||||
</head>
|
||||
<body>
|
||||
<div id="page-loading">
|
||||
<div>Loading ...</div>
|
||||
</div>
|
||||
<main class="container">{% block main %}{% endblock %}</main>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
{% extends "base" %}
|
||||
{% block head %}
|
||||
<title>Transactions</title>
|
||||
{% endblock %}
|
||||
{% block main %}
|
||||
<h1>Transactions</h1>
|
||||
<p>
|
||||
These transactions have not been reviewed and do not have attached receipts.
|
||||
</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="col">Description</th>
|
||||
<th scope="col">Date</th>
|
||||
<th scope="col">Amount</th>
|
||||
</tr>
|
||||
{% for txn in transactions -%}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/transactions/{{ txn.id }}">{{ txn.description }}</a>
|
||||
</td>
|
||||
<td>{{ txn.date }}</td>
|
||||
<td>${{ txn.amount }}</td>
|
||||
</tr>
|
||||
{% endfor -%}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
|
@ -0,0 +1,68 @@
|
|||
{% extends "base" %}
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="/static/transaction.css" />
|
||||
<title>Update Transaction: {{ description }}</title>
|
||||
{% endblock %}
|
||||
{% block main %}
|
||||
<h1>Update Transaction</h1>
|
||||
<nav>
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item href="/">Transactions</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>{{ description }}</sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
</nav>
|
||||
<form name="transaction">
|
||||
<p>
|
||||
<sl-input label="Date" value="March 2nd, 2025" readonly></sl-input>
|
||||
</p>
|
||||
<p>
|
||||
<sl-input label="Description" value="{{ description }}" readonly></sl-input>
|
||||
</p>
|
||||
<p>
|
||||
<sl-input
|
||||
type="number"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
label="Amount"
|
||||
name="amount"
|
||||
value="{{ amount }}"
|
||||
></sl-input>
|
||||
</p>
|
||||
<p><sl-textarea label="Notes" name="notes"></sl-textarea></p>
|
||||
<sl-details summary="Take Photo" id="photo-box">
|
||||
<p class="fallback">Your browser does not support taking photos.</p>
|
||||
<div id="photo-view" class="invisible">
|
||||
<div class="workspace">
|
||||
<video class="invisible"></video>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div>
|
||||
<sl-tooltip content="Take Photo" placement="left">
|
||||
<sl-icon-button name="camera" label="Take Photo"></sl-icon-button>
|
||||
</sl-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<sl-tooltip content="Crop" placement="left">
|
||||
<sl-icon-button name="crop" label="Crop"></sl-icon-button>
|
||||
</sl-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<sl-tooltip content="Start Over" placement="left">
|
||||
<sl-icon-button
|
||||
name="trash"
|
||||
label="Start Over"
|
||||
></sl-icon-button>
|
||||
</sl-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</sl-details>
|
||||
<footer>
|
||||
<sl-button type="reset" variant="secondary">Reset</sl-button
|
||||
><sl-button type="submit" variant="primary">Submit</sl-button>
|
||||
</footer>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="/static/transaction.js"></script>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue