receipts/list: Reverse sort and add pagination
dustin/receipts/pipeline/head This commit looks good
Details
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
parent
ad1c857c97
commit
b919bd8f0d
|
@ -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"
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Left": []
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
false,
|
||||||
|
@ -46,5 +49,5 @@
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "71dcdc6a24d99eff2dd7af673a0ebb6fda45b0ebd5244309472921a934e1b829"
|
"hash": "ed7bf495d2eefe7b479a79cc2fc77de3b5a3db4415cd55ecbd21c28c108274a6"
|
||||||
}
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
SELECT
|
||||||
|
count(id) AS "count!"
|
||||||
|
FROM
|
||||||
|
receipts
|
|
@ -2,4 +2,8 @@ SELECT
|
||||||
id, vendor, date, amount, notes, filename
|
id, vendor, date, amount, notes, filename
|
||||||
FROM
|
FROM
|
||||||
receipts
|
receipts
|
||||||
ORDER BY date
|
ORDER BY
|
||||||
|
date DESC,
|
||||||
|
id DESC
|
||||||
|
LIMIT $1
|
||||||
|
OFFSET $2
|
||||||
|
|
|
@ -138,12 +138,21 @@ impl ReceiptsRepository {
|
||||||
|
|
||||||
pub async fn list_receipts(
|
pub async fn list_receipts(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
limit: i64,
|
||||||
|
offset: i64,
|
||||||
) -> Result<Vec<ReceiptJson>, sqlx::Error> {
|
) -> 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)
|
.fetch_all(&mut **self.conn)
|
||||||
.await
|
.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(
|
pub async fn add_receipt(
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &ReceiptPostData,
|
data: &ReceiptPostData,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
use rocket::http::{ContentType, Header, MediaType, Status};
|
use rocket::http::{ContentType, Header, MediaType, Status};
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
|
@ -12,18 +14,62 @@ use crate::imaging;
|
||||||
use crate::receipts::*;
|
use crate::receipts::*;
|
||||||
use crate::{Context, Database};
|
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(
|
pub async fn list_receipts(
|
||||||
db: DatabaseConnection<Database>,
|
db: DatabaseConnection<Database>,
|
||||||
|
page: Option<i64>,
|
||||||
|
count: Option<i64>,
|
||||||
) -> (Status, Template) {
|
) -> (Status, Template) {
|
||||||
let mut repo = ReceiptsRepository::new(db);
|
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) => (
|
Ok(r) => (
|
||||||
Status::Ok,
|
Status::Ok,
|
||||||
Template::render(
|
Template::render(
|
||||||
"receipt-list",
|
"receipt-list",
|
||||||
context! {
|
context! {
|
||||||
receipts: r,
|
receipts: r,
|
||||||
|
pages: paginate(total, count, page),
|
||||||
|
count: count,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -80,6 +80,18 @@
|
||||||
#confirm-delete dl dd {
|
#confirm-delete dl dd {
|
||||||
margin-left: 0;
|
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>
|
</style>
|
||||||
{% endblock %} {% block main %}
|
{% endblock %} {% block main %}
|
||||||
<h1>Receipts</h1>
|
<h1>Receipts</h1>
|
||||||
|
@ -114,6 +126,13 @@
|
||||||
</sl-card>
|
</sl-card>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</article>
|
</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">
|
<sl-dialog id="confirm-delete" label="Delete Receipt">
|
||||||
<p>
|
<p>
|
||||||
Are you sure you want to delete receipt <span class="receipt-id"></span>?
|
Are you sure you want to delete receipt <span class="receipt-id"></span>?
|
||||||
|
|
Loading…
Reference in New Issue