receipts/list: Reverse sort and add pagination
dustin/receipts/pipeline/head This commit looks good Details

Now that there are quite a few receipts in the database, scrolling to
the end to see the most recent entries is rather cumbersome.  Let's show
the most recent receipts first, and hide older ones by default by
splitting the list into multiple pages.
master
Dustin 2025-05-18 16:00:26 -05:00
parent ad1c857c97
commit b919bd8f0d
7 changed files with 112 additions and 7 deletions

View File

@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n count(id) AS \"count!\"\nFROM\n receipts\n",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count!",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "34f56cde503100c09bbb378ce656af95abd81949be0c369a5d7225272e6c9c58"
}

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n id, vendor, date, amount, notes, filename\nFROM\n receipts\nORDER BY date\n",
"query": "SELECT\n id, vendor, date, amount, notes, filename\nFROM\n receipts\nORDER BY\n date DESC,\n id DESC\nLIMIT $1\nOFFSET $2\n",
"describe": {
"columns": [
{
@ -35,7 +35,10 @@
}
],
"parameters": {
"Left": []
"Left": [
"Int8",
"Int8"
]
},
"nullable": [
false,
@ -46,5 +49,5 @@
false
]
},
"hash": "71dcdc6a24d99eff2dd7af673a0ebb6fda45b0ebd5244309472921a934e1b829"
"hash": "ed7bf495d2eefe7b479a79cc2fc77de3b5a3db4415cd55ecbd21c28c108274a6"
}

View File

@ -0,0 +1,4 @@
SELECT
count(id) AS "count!"
FROM
receipts

View File

@ -2,4 +2,8 @@ SELECT
id, vendor, date, amount, notes, filename
FROM
receipts
ORDER BY date
ORDER BY
date DESC,
id DESC
LIMIT $1
OFFSET $2

View File

@ -138,12 +138,21 @@ impl ReceiptsRepository {
pub async fn list_receipts(
&mut self,
limit: i64,
offset: i64,
) -> Result<Vec<ReceiptJson>, sqlx::Error> {
sqlx::query_file_as!(ReceiptJson, "sql/receipts/list-receipts.sql")
sqlx::query_file_as!(ReceiptJson, "sql/receipts/list-receipts.sql", limit, offset)
.fetch_all(&mut **self.conn)
.await
}
pub async fn count_receipts(&mut self) -> Result<i64, sqlx::Error> {
Ok(sqlx::query_file!("sql/receipts/count-receipts.sql")
.fetch_one(&mut **self.conn)
.await?
.count)
}
pub async fn add_receipt(
&mut self,
data: &ReceiptPostData,

View File

@ -1,3 +1,5 @@
use std::ops::RangeInclusive;
use rocket::form::Form;
use rocket::http::{ContentType, Header, MediaType, Status};
use rocket::serde::json::Json;
@ -12,18 +14,62 @@ use crate::imaging;
use crate::receipts::*;
use crate::{Context, Database};
#[rocket::get("/")]
fn paginate(total: i64, count: i64, current: i64) -> Vec<String> {
let start = 1;
let end = (total / count).max(1);
let pages = RangeInclusive::new(start, end);
if end < 10 {
pages.map(|p| format!("{}", p)).collect()
} else {
pages
.filter_map(|p| {
if p == start
|| (current - 2 <= p && p <= current + 2)
|| p == end
{
Some(format!("{}", p))
} else if p == current - 3 || p == current + 3 {
Some("...".into())
} else {
None
}
})
.collect()
}
}
#[rocket::get("/?<page>&<count>")]
pub async fn list_receipts(
db: DatabaseConnection<Database>,
page: Option<i64>,
count: Option<i64>,
) -> (Status, Template) {
let mut repo = ReceiptsRepository::new(db);
match repo.list_receipts().await {
let count = count.unwrap_or(25);
let page = page.unwrap_or(1);
let total = match repo.count_receipts().await {
Ok(r) => r,
Err(e) => {
return (
Status::InternalServerError,
Template::render(
"error",
context! {
error: e.to_string(),
},
),
)
},
};
match repo.list_receipts(count, (page - 1) * count).await {
Ok(r) => (
Status::Ok,
Template::render(
"receipt-list",
context! {
receipts: r,
pages: paginate(total, count, page),
count: count,
},
),
),

View File

@ -80,6 +80,18 @@
#confirm-delete dl dd {
margin-left: 0;
}
ul.pagination {
margin: 0;
padding: 0;
list-style-type: none;
text-align: center;
}
ul.pagination li {
display: inline-block;
margin: 1em;
}
</style>
{% endblock %} {% block main %}
<h1>Receipts</h1>
@ -114,6 +126,13 @@
</sl-card>
{% endfor %}
</article>
<ul class="pagination">
{%- for page in pages %}
<li>{% if page == "..." %}...{% else
%}<sl-button href="?page={{ page }}&count={{ count }}">{{ page }}</sl-button>{%
endif %}</li>
{%- endfor %}
</ul>
<sl-dialog id="confirm-delete" label="Delete Receipt">
<p>
Are you sure you want to delete receipt <span class="receipt-id"></span>?