some optimizations

This commit is contained in:
2023-08-29 21:34:00 +02:00
parent 57886f5007
commit a7038c5e2b
5 changed files with 68 additions and 78 deletions

View File

@@ -7,7 +7,7 @@ use uuid::{uuid, Uuid};
pub struct Inventory; pub struct Inventory;
impl Inventory { impl Inventory {
pub async fn build(state: ClientState, categories: Vec<Category>) -> Result<Markup, Error> { pub fn build(state: ClientState, categories: Vec<Category>) -> Result<Markup, Error> {
let doc = html!( let doc = html!(
div id="pkglist-item-manager" { div id="pkglist-item-manager" {
div ."p-8" ."grid" ."grid-cols-4" ."gap-5" { div ."p-8" ."grid" ."grid-cols-4" ."gap-5" {
@@ -20,7 +20,7 @@ impl Inventory {
h1 ."text-2xl" ."text-center" { "Items" } h1 ."text-2xl" ."text-center" { "Items" }
@if let Some(active_category_id) = state.active_category_id { @if let Some(active_category_id) = state.active_category_id {
(InventoryItemList::build(&state, categories.iter().find(|category| category.id == active_category_id) (InventoryItemList::build(&state, categories.iter().find(|category| category.id == active_category_id)
.ok_or(Error::NotFoundError { description: format!("no category with id {}", active_category_id) })? .ok_or(Error::NotFound{ description: format!("no category with id {}", active_category_id) })?
.items()) .items())
) )
} }
@@ -475,7 +475,7 @@ impl InventoryNewItemForm {
div ."w-11/12" ."mx-auto" ."flex" ."flex-col" ."gap-8" { div ."w-11/12" ."mx-auto" ."flex" ."flex-col" ."gap-8" {
(InventoryNewItemFormName::build(None, false)) (InventoryNewItemFormName::build(None, false))
(InventoryNewItemFormWeight::build()) (InventoryNewItemFormWeight::build())
(InventoryNewItemFormCategory::build(&state, categories)) (InventoryNewItemFormCategory::build(state, categories))
input type="submit" value="Add" input type="submit" value="Add"
x-bind:disabled="!save_active" x-bind:disabled="!save_active"
."enabled:cursor-pointer" ."enabled:cursor-pointer"

View File

@@ -17,7 +17,7 @@ pub enum TopLevelPage {
} }
impl Root { impl Root {
pub fn build(body: Markup, active_page: &TopLevelPage) -> Markup { pub fn build(body: &Markup, active_page: &TopLevelPage) -> Markup {
html!( html!(
(DOCTYPE) (DOCTYPE)
html { html {

View File

@@ -66,8 +66,8 @@ impl TripTable {
@for trip in trips { @for trip in trips {
tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" ."h-full" { tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" ."h-full" {
(TripTableRow::build(trip.id, &trip.name)) (TripTableRow::build(trip.id, &trip.name))
(TripTableRow::build(trip.id, &trip.date_start)) (TripTableRow::build(trip.id, trip.date_start))
(TripTableRow::build(trip.id, &trip.date_end)) (TripTableRow::build(trip.id, trip.date_end))
(TripTableRow::build(trip.id, (trip.date_end - trip.date_start).whole_days())) (TripTableRow::build(trip.id, (trip.date_end - trip.date_start).whole_days()))
(TripTableRow::build(trip.id, trip.state)) (TripTableRow::build(trip.id, trip.state))
} }
@@ -266,9 +266,9 @@ impl Trip {
} }
} }
} }
(TripInfo::build(state, &trip)) (TripInfo::build(state, trip))
(TripComment::build(&trip)) (TripComment::build(trip))
(TripItems::build(state, &trip)?) (TripItems::build(state, trip)?)
} }
)) ))
} }
@@ -280,12 +280,12 @@ impl TripInfoRow {
pub fn build( pub fn build(
name: &str, name: &str,
value: Option<impl std::fmt::Display>, value: Option<impl std::fmt::Display>,
attribute_key: TripAttribute, attribute_key: &TripAttribute,
edit_attribute: Option<&TripAttribute>, edit_attribute: Option<&TripAttribute>,
input_type: InputType, input_type: InputType,
has_two_columns: bool, has_two_columns: bool,
) -> Markup { ) -> Markup {
let edit = edit_attribute.map_or(false, |a| *a == attribute_key); let edit = edit_attribute.map_or(false, |a| a == attribute_key);
html!( html!(
@if edit { @if edit {
form form
@@ -307,7 +307,7 @@ impl TripInfoRow {
id="new-value" id="new-value"
name="new-value" name="new-value"
form="edit-trip" form="edit-trip"
value=(value.map_or("".to_string(), |v| v.to_string())) value=(value.map_or(String::new(), |v| v.to_string()))
; ;
} }
} }
@@ -359,7 +359,7 @@ impl TripInfoRow {
} }
} @else { } @else {
td ."border" ."p-2" { (name) } td ."border" ."p-2" { (name) }
td ."border" ."p-2" { (value.map_or("".to_string(), |v| v.to_string())) } td ."border" ."p-2" { (value.map_or(String::new(), |v| v.to_string())) }
td td
colspan=(if has_two_columns {"2"} else {"1"}) colspan=(if has_two_columns {"2"} else {"1"})
."border-none" ."border-none"
@@ -406,35 +406,35 @@ impl TripInfo {
tbody { tbody {
(TripInfoRow::build("Location", (TripInfoRow::build("Location",
trip.location.as_ref(), trip.location.as_ref(),
TripAttribute::Location, &TripAttribute::Location,
state.trip_edit_attribute.as_ref(), state.trip_edit_attribute.as_ref(),
InputType::Text, InputType::Text,
has_two_columns has_two_columns
)) ))
(TripInfoRow::build("Start date", (TripInfoRow::build("Start date",
Some(trip.date_start), Some(trip.date_start),
TripAttribute::DateStart, &TripAttribute::DateStart,
state.trip_edit_attribute.as_ref(), state.trip_edit_attribute.as_ref(),
InputType::Date, InputType::Date,
has_two_columns has_two_columns
)) ))
(TripInfoRow::build("End date", (TripInfoRow::build("End date",
Some(trip.date_end), Some(trip.date_end),
TripAttribute::DateEnd, &TripAttribute::DateEnd,
state.trip_edit_attribute.as_ref(), state.trip_edit_attribute.as_ref(),
InputType::Date, InputType::Date,
has_two_columns has_two_columns
)) ))
(TripInfoRow::build("Temp (min)", (TripInfoRow::build("Temp (min)",
trip.temp_min, trip.temp_min,
TripAttribute::TempMin, &TripAttribute::TempMin,
state.trip_edit_attribute.as_ref(), state.trip_edit_attribute.as_ref(),
InputType::Number, InputType::Number,
has_two_columns has_two_columns
)) ))
(TripInfoRow::build("Temp (max)", (TripInfoRow::build("Temp (max)",
trip.temp_max, trip.temp_max,
TripAttribute::TempMax, &TripAttribute::TempMax,
state.trip_edit_attribute.as_ref(), state.trip_edit_attribute.as_ref(),
InputType::Number, InputType::Number,
has_two_columns has_two_columns
@@ -648,7 +648,7 @@ impl TripComment {
form="edit-comment" form="edit-comment"
autocomplete="off" autocomplete="off"
oninput=r#"this.style.height = "";this.style.height = this.scrollHeight + 2 + "px""# oninput=r#"this.style.height = "";this.style.height = this.scrollHeight + 2 + "px""#
{ (trip.comment.as_ref().unwrap_or(&"".to_string())) } { (trip.comment.as_ref().unwrap_or(&String::new())) }
script defer { (PreEscaped(r#"e = document.getElementById("comment"); e.style.height = e.scrollHeight + 2 + "px";"#)) } script defer { (PreEscaped(r#"e = document.getElementById("comment"); e.style.height = e.scrollHeight + 2 + "px";"#)) }
button button
@@ -683,23 +683,23 @@ impl TripItems {
Ok(html!( Ok(html!(
div ."grid" ."grid-cols-4" ."gap-3" { div ."grid" ."grid-cols-4" ."gap-3" {
div ."col-span-2" { div ."col-span-2" {
(TripCategoryList::build(state, &trip)) (TripCategoryList::build(state, trip))
} }
div ."col-span-2" { div ."col-span-2" {
h1 ."text-2xl" ."mb-5" ."text-center" { "Items" } h1 ."text-2xl" ."mb-5" ."text-center" { "Items" }
@if let Some(active_category_id) = state.active_category_id { @if let Some(active_category_id) = state.active_category_id {
(TripItemList::build( (TripItemList::build(
&state, state,
&trip, trip,
&trip trip
.categories() .categories()
.iter() .iter()
.find(|category| .find(|category|
category.category.id == active_category_id category.category.id == active_category_id
) )
.ok_or( .ok_or(
Error::NotFoundError { Error::NotFound{
description: format!("no category with id {}", active_category_id) description: format!("no category with id {active_category_id}")
} }
)? )?
.items .items

View File

@@ -153,21 +153,15 @@ async fn main() -> Result<(), sqlx::Error> {
async fn root() -> (StatusCode, Markup) { async fn root() -> (StatusCode, Markup) {
( (
StatusCode::OK, StatusCode::OK,
Root::build(Home::build(), &TopLevelPage::None), Root::build(&Home::build(), &TopLevelPage::None),
) )
} }
#[derive(Deserialize)] #[derive(Deserialize, Default)]
struct InventoryQuery { struct InventoryQuery {
edit_item: Option<Uuid>, edit_item: Option<Uuid>,
} }
impl Default for InventoryQuery {
fn default() -> Self {
Self { edit_item: None }
}
}
async fn inventory_active( async fn inventory_active(
State(mut state): State<AppState>, State(mut state): State<AppState>,
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
@@ -233,17 +227,15 @@ async fn inventory(
Ok(( Ok((
StatusCode::OK, StatusCode::OK,
Root::build( Root::build(
Inventory::build(state.client_state, categories) &Inventory::build(state.client_state, categories).map_err(|e| match e {
.await Error::NotFound { description } => {
.map_err(|e| match e { (StatusCode::NOT_FOUND, ErrorPage::build(&description))
Error::NotFoundError { description } => { }
(StatusCode::NOT_FOUND, ErrorPage::build(&description)) _ => (
} StatusCode::INTERNAL_SERVER_ERROR,
_ => ( ErrorPage::build(&e.to_string()),
StatusCode::INTERNAL_SERVER_ERROR, ),
ErrorPage::build(&e.to_string()), })?,
),
})?,
&TopLevelPage::Inventory, &TopLevelPage::Inventory,
), ),
)) ))
@@ -310,7 +302,7 @@ async fn inventory_item_create(
State(state): State<AppState>, State(state): State<AppState>,
Form(new_item): Form<NewItem>, Form(new_item): Form<NewItem>,
) -> Result<Redirect, (StatusCode, String)> { ) -> Result<Redirect, (StatusCode, String)> {
if new_item.name.len() == 0 { if new_item.name.is_empty() {
return Err(( return Err((
StatusCode::UNPROCESSABLE_ENTITY, StatusCode::UNPROCESSABLE_ENTITY,
"name cannot be empty".to_string(), "name cannot be empty".to_string(),
@@ -505,7 +497,7 @@ async fn inventory_item_edit(
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
Form(edit_item): Form<EditItem>, Form(edit_item): Form<EditItem>,
) -> Result<Redirect, (StatusCode, Markup)> { ) -> Result<Redirect, (StatusCode, Markup)> {
if edit_item.name.len() == 0 { if edit_item.name.is_empty() {
return Err(( return Err((
StatusCode::UNPROCESSABLE_ENTITY, StatusCode::UNPROCESSABLE_ENTITY,
ErrorPage::build("name cannot be empty"), ErrorPage::build("name cannot be empty"),
@@ -570,7 +562,7 @@ async fn trip_create(
State(state): State<AppState>, State(state): State<AppState>,
Form(new_trip): Form<NewTrip>, Form(new_trip): Form<NewTrip>,
) -> Result<Redirect, (StatusCode, String)> { ) -> Result<Redirect, (StatusCode, String)> {
if new_trip.name.len() == 0 { if new_trip.name.is_empty() {
return Err(( return Err((
StatusCode::UNPROCESSABLE_ENTITY, StatusCode::UNPROCESSABLE_ENTITY,
"name cannot be empty".to_string(), "name cannot be empty".to_string(),
@@ -679,7 +671,7 @@ async fn trips(
Ok(( Ok((
StatusCode::OK, StatusCode::OK,
Root::build(TripManager::build(trips), &TopLevelPage::Trips), Root::build(&TripManager::build(trips), &TopLevelPage::Trips),
)) ))
} }
@@ -764,8 +756,8 @@ async fn trip(
Ok(( Ok((
StatusCode::OK, StatusCode::OK,
Root::build( Root::build(
components::Trip::build(&state.client_state, &trip).map_err(|e| match e { &components::Trip::build(&state.client_state, &trip).map_err(|e| match e {
Error::NotFoundError { description } => { Error::NotFound { description } => {
(StatusCode::NOT_FOUND, ErrorPage::build(&description)) (StatusCode::NOT_FOUND, ErrorPage::build(&description))
} }
_ => ( _ => (
@@ -916,7 +908,7 @@ async fn trip_edit_attribute(
Form(trip_update): Form<TripUpdate>, Form(trip_update): Form<TripUpdate>,
) -> Result<Redirect, (StatusCode, Markup)> { ) -> Result<Redirect, (StatusCode, Markup)> {
if let TripAttribute::Name = attribute { if let TripAttribute::Name = attribute {
if trip_update.new_value.len() == 0 { if trip_update.new_value.is_empty() {
return Err(( return Err((
StatusCode::UNPROCESSABLE_ENTITY, StatusCode::UNPROCESSABLE_ENTITY,
ErrorPage::build("name cannot be empty"), ErrorPage::build("name cannot be empty"),
@@ -1092,7 +1084,7 @@ async fn inventory_category_create(
State(state): State<AppState>, State(state): State<AppState>,
Form(new_category): Form<NewCategory>, Form(new_category): Form<NewCategory>,
) -> Result<Redirect, (StatusCode, Markup)> { ) -> Result<Redirect, (StatusCode, Markup)> {
if new_category.name.len() == 0 { if new_category.name.is_empty() {
return Err(( return Err((
StatusCode::UNPROCESSABLE_ENTITY, StatusCode::UNPROCESSABLE_ENTITY,
ErrorPage::build("name cannot be empty"), ErrorPage::build("name cannot be empty"),
@@ -1223,7 +1215,7 @@ async fn trips_types(
Ok(( Ok((
StatusCode::OK, StatusCode::OK,
Root::build( Root::build(
components::trip::TypeList::build(&state.client_state, trip_types), &components::trip::TypeList::build(&state.client_state, trip_types),
&TopLevelPage::Trips, &TopLevelPage::Trips,
), ),
)) ))
@@ -1239,7 +1231,7 @@ async fn trip_type_create(
State(state): State<AppState>, State(state): State<AppState>,
Form(new_trip_type): Form<NewTripType>, Form(new_trip_type): Form<NewTripType>,
) -> Result<Redirect, (StatusCode, String)> { ) -> Result<Redirect, (StatusCode, String)> {
if new_trip_type.name.len() == 0 { if new_trip_type.name.is_empty() {
return Err(( return Err((
StatusCode::UNPROCESSABLE_ENTITY, StatusCode::UNPROCESSABLE_ENTITY,
"name cannot be empty".to_string(), "name cannot be empty".to_string(),
@@ -1305,7 +1297,7 @@ async fn trips_types_edit_name(
Path(trip_type_id): Path<Uuid>, Path(trip_type_id): Path<Uuid>,
Form(trip_update): Form<TripTypeUpdate>, Form(trip_update): Form<TripTypeUpdate>,
) -> Result<Redirect, (StatusCode, Markup)> { ) -> Result<Redirect, (StatusCode, Markup)> {
if trip_update.new_value.len() == 0 { if trip_update.new_value.is_empty() {
return Err(( return Err((
StatusCode::UNPROCESSABLE_ENTITY, StatusCode::UNPROCESSABLE_ENTITY,
ErrorPage::build("name cannot be empty"), ErrorPage::build("name cannot be empty"),

View File

@@ -17,40 +17,38 @@ use sqlx::sqlite::SqlitePoolOptions;
use futures::TryFutureExt; use futures::TryFutureExt;
use futures::TryStreamExt; use futures::TryStreamExt;
use time::{ use time::{error::Parse as TimeParse, format_description::FormatItem, macros::format_description};
error::Parse as TimeParseError, format_description::FormatItem, macros::format_description,
};
pub const DATE_FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day]"); pub const DATE_FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day]");
pub enum Error { pub enum Error {
SqlError { description: String }, Sql { description: String },
UuidError { description: String }, Uuid { description: String },
EnumError { description: String }, Enum { description: String },
NotFoundError { description: String }, NotFound { description: String },
IntError { description: String }, Int { description: String },
TimeParseError { description: String }, TimeParse { description: String },
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::SqlError { description } => { Self::Sql { description } => {
write!(f, "SQL error: {description}") write!(f, "SQL error: {description}")
} }
Self::UuidError { description } => { Self::Uuid { description } => {
write!(f, "UUID error: {description}") write!(f, "UUID error: {description}")
} }
Self::NotFoundError { description } => { Self::NotFound { description } => {
write!(f, "Not found: {description}") write!(f, "Not found: {description}")
} }
Self::IntError { description } => { Self::Int { description } => {
write!(f, "Integer error: {description}") write!(f, "Integer error: {description}")
} }
Self::EnumError { description } => { Self::Enum { description } => {
write!(f, "Enum error: {description}") write!(f, "Enum error: {description}")
} }
Self::TimeParseError { description } => { Self::TimeParse { description } => {
write!(f, "Date parse error: {description}") write!(f, "Date parse error: {description}")
} }
} }
@@ -66,7 +64,7 @@ impl fmt::Debug for Error {
impl convert::From<uuid::Error> for Error { impl convert::From<uuid::Error> for Error {
fn from(value: uuid::Error) -> Self { fn from(value: uuid::Error) -> Self {
Error::UuidError { Error::Uuid {
description: value.to_string(), description: value.to_string(),
} }
} }
@@ -74,7 +72,7 @@ impl convert::From<uuid::Error> for Error {
impl convert::From<sqlx::Error> for Error { impl convert::From<sqlx::Error> for Error {
fn from(value: sqlx::Error) -> Self { fn from(value: sqlx::Error) -> Self {
Error::SqlError { Error::Sql {
description: value.to_string(), description: value.to_string(),
} }
} }
@@ -82,15 +80,15 @@ impl convert::From<sqlx::Error> for Error {
impl convert::From<TryFromIntError> for Error { impl convert::From<TryFromIntError> for Error {
fn from(value: TryFromIntError) -> Self { fn from(value: TryFromIntError) -> Self {
Error::IntError { Error::Int {
description: value.to_string(), description: value.to_string(),
} }
} }
} }
impl convert::From<TimeParseError> for Error { impl convert::From<TimeParse> for Error {
fn from(value: TimeParseError) -> Self { fn from(value: TimeParse) -> Self {
Error::TimeParseError { Error::TimeParse {
description: value.to_string(), description: value.to_string(),
} }
} }
@@ -173,8 +171,8 @@ impl std::convert::TryFrom<&str> for TripState {
"Review" => Self::Review, "Review" => Self::Review,
"Done" => Self::Done, "Done" => Self::Done,
_ => { _ => {
return Err(Error::EnumError { return Err(Error::Enum {
description: format!("{} is not a valid value for TripState", value), description: format!("{value} is not a valid value for TripState"),
}) })
} }
}) })