some htmx and alpine
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use maud::{html, Markup};
|
||||
use maud::{html, Markup, PreEscaped};
|
||||
|
||||
use crate::models::*;
|
||||
use crate::ClientState;
|
||||
@@ -312,12 +312,154 @@ impl InventoryItemList {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InventoryNewItemFormName;
|
||||
|
||||
impl InventoryNewItemFormName {
|
||||
pub fn build(value: Option<&str>, error: bool) -> Markup {
|
||||
html!(
|
||||
div
|
||||
."grid"
|
||||
."grid-cols-[2fr,3fr]"
|
||||
."justify-items-center"
|
||||
."items-center"
|
||||
hx-post="/inventory/item/name/validate"
|
||||
hx-trigger="input delay:1s, every 5s"
|
||||
hx-params="new-item-name"
|
||||
hx-swap="outerHTML"
|
||||
{
|
||||
label for="name" .font-bold { "Name" }
|
||||
input
|
||||
type="text"
|
||||
id="new-item-name"
|
||||
name="new-item-name"
|
||||
x-on:input="(e) => {save_active = inventory_new_item_check_input()}"
|
||||
."block"
|
||||
."w-full"
|
||||
."p-2"
|
||||
."bg-gray-50"
|
||||
."border-2"
|
||||
."border-red-500"[error]
|
||||
."border-gray-300"[!error]
|
||||
."rounded"
|
||||
."focus:outline-none"
|
||||
."focus:bg-white"
|
||||
."focus:border-purple-500"[!error]
|
||||
value=[value]
|
||||
;
|
||||
@if error {
|
||||
div
|
||||
."col-start-2"
|
||||
."text-sm"
|
||||
."text-red-500"
|
||||
{ "name already exists" }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InventoryNewItemFormWeight;
|
||||
|
||||
impl InventoryNewItemFormWeight {
|
||||
pub fn build() -> Markup {
|
||||
html!(
|
||||
div
|
||||
."grid"
|
||||
."grid-cols-[2fr,3fr]"
|
||||
."justify-items-center"
|
||||
."items-center"
|
||||
{
|
||||
label for="weight" .font-bold { "Weight" }
|
||||
input
|
||||
type="number"
|
||||
id="new-item-weight"
|
||||
name="new-item-weight"
|
||||
min="0"
|
||||
x-on:input="(e) => {
|
||||
save_active = inventory_new_item_check_input();
|
||||
weight_error = !check_weight();
|
||||
}"
|
||||
x-bind:class="weight_error && 'border-red-500' || 'border-gray-300 focus:border-purple-500'"
|
||||
."block"
|
||||
."w-full"
|
||||
."p-2"
|
||||
."bg-gray-50"
|
||||
."border-2"
|
||||
."rounded"
|
||||
."focus:outline-none"
|
||||
."focus:bg-white"
|
||||
{}
|
||||
span
|
||||
// x-on produces some errors, this works as well
|
||||
x-bind:class="!weight_error && 'hidden'"
|
||||
."col-start-2"
|
||||
."text-sm"
|
||||
."text-red-500"
|
||||
{ "invalid input" }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InventoryNewItemFormCategory;
|
||||
|
||||
impl InventoryNewItemFormCategory {
|
||||
pub fn build(state: &ClientState, categories: &Vec<Category>) -> Markup {
|
||||
html!(
|
||||
div
|
||||
."grid"
|
||||
."grid-cols-[2fr,3fr]"
|
||||
."justify-items-center"
|
||||
."items-center"
|
||||
{
|
||||
label for="item-category" .font-bold ."w-1/2" .text-center { "Category" }
|
||||
select
|
||||
id="new-item-category-id"
|
||||
name="new-item-category-id"
|
||||
."block"
|
||||
."w-full"
|
||||
."p-2"
|
||||
."bg-gray-50"
|
||||
."border-2"
|
||||
."border-gray-300"
|
||||
."rounded"
|
||||
."focus:outline-none"
|
||||
."focus:bg-white"
|
||||
."focus:border-purple-500"
|
||||
autocomplete="off" // https://stackoverflow.com/a/10096033
|
||||
{
|
||||
@for category in categories {
|
||||
option value=(category.id) selected[state.active_category_id.map_or(false, |id| id == category.id)] {
|
||||
(category.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InventoryNewItemForm;
|
||||
|
||||
impl InventoryNewItemForm {
|
||||
pub fn build(state: &ClientState, categories: &Vec<Category>) -> Markup {
|
||||
html!(
|
||||
script {
|
||||
(PreEscaped("
|
||||
function inventory_new_item_check_input() {
|
||||
return document.getElementById('new-item-name').value.length != 0
|
||||
&& is_positive_integer(document.getElementById('new-item-weight').value)
|
||||
}
|
||||
function check_weight() {
|
||||
return document.getElementById('new-item-weight').validity.valid;
|
||||
}
|
||||
"))
|
||||
}
|
||||
form
|
||||
x-data="{
|
||||
save_active: inventory_new_item_check_input(),
|
||||
weight_error: !check_weight(),
|
||||
}"
|
||||
name="new-item"
|
||||
id="new-item"
|
||||
action="/inventory/item/"
|
||||
@@ -328,74 +470,14 @@ impl InventoryNewItemForm {
|
||||
span ."mdi" ."mdi-playlist-plus" ."text-2xl" ."mr-4" {}
|
||||
p ."inline" ."text-xl" { "Add new item" }
|
||||
}
|
||||
div ."w-11/12" ."mx-auto" {
|
||||
div ."pb-8" {
|
||||
div ."flex" ."flex-row" ."justify-center" ."items-start"{
|
||||
label for="name" .font-bold ."w-1/2" ."p-2" ."text-center" { "Name" }
|
||||
span ."w-1/2" {
|
||||
input type="text" id="new-item-name" name="new-item-name"
|
||||
."block"
|
||||
."w-full"
|
||||
."p-2"
|
||||
."bg-gray-50"
|
||||
."border-2"
|
||||
."rounded"
|
||||
."focus:outline-none"
|
||||
."focus:bg-white"
|
||||
."focus:border-purple-500"
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div ."flex" ."flex-row" ."justify-center" ."items-center" ."pb-8" {
|
||||
label for="weight" .font-bold ."w-1/2" .text-center { "Weight" }
|
||||
span ."w-1/2" {
|
||||
input
|
||||
type="text"
|
||||
id="new-item-weight"
|
||||
name="new-item-weight"
|
||||
."block"
|
||||
."w-full"
|
||||
."p-2"
|
||||
."bg-gray-50"
|
||||
."border-2"
|
||||
."border-gray-300"
|
||||
."rounded"
|
||||
."focus:outline-none"
|
||||
."focus:bg-white"
|
||||
."focus:border-purple-500"
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
div ."flex" ."flex-row" ."justify-center" ."items-center" ."pb-8" {
|
||||
label for="item-category" .font-bold ."w-1/2" .text-center { "Category" }
|
||||
span ."w-1/2" {
|
||||
select
|
||||
id="new-item-category-id"
|
||||
name="new-item-category-id"
|
||||
."block"
|
||||
."w-full"
|
||||
."p-2"
|
||||
."bg-gray-50"
|
||||
."border-2"
|
||||
."border-gray-300"
|
||||
."rounded"
|
||||
."focus:outline-none"
|
||||
."focus:bg-white"
|
||||
."focus:border-purple-500"
|
||||
autocomplete="off" // https://stackoverflow.com/a/10096033
|
||||
{
|
||||
@for category in categories {
|
||||
option value=(category.id) selected[state.active_category_id.map_or(false, |id| id == category.id)] {
|
||||
(category.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div ."w-11/12" ."mx-auto" ."flex" ."flex-col" ."gap-8" {
|
||||
(InventoryNewItemFormName::build(None, false))
|
||||
(InventoryNewItemFormWeight::build())
|
||||
(InventoryNewItemFormCategory::build(&state, categories))
|
||||
input type="submit" value="Add"
|
||||
x-bind:disabled="!save_active"
|
||||
."enabled:cursor-pointer"
|
||||
."disabled:opacity-50"
|
||||
."py-2"
|
||||
."border-2"
|
||||
."rounded"
|
||||
@@ -415,6 +497,7 @@ impl InventoryNewCategoryForm {
|
||||
pub fn build() -> Markup {
|
||||
html!(
|
||||
form
|
||||
x-data="{ save_active: document.getElementById('new-category-name').value.length != 0 }"
|
||||
name="new-category"
|
||||
id="new-category"
|
||||
action="/inventory/category/"
|
||||
@@ -431,11 +514,13 @@ impl InventoryNewCategoryForm {
|
||||
label for="name" .font-bold ."w-1/2" ."p-2" ."text-center" { "Name" }
|
||||
span ."w-1/2" {
|
||||
input type="text" id="new-category-name" name="new-category-name"
|
||||
x-on:input="(e) => {save_active = e.target.value.length != 0 }"
|
||||
."block"
|
||||
."w-full"
|
||||
."p-2"
|
||||
."bg-gray-50"
|
||||
."border-2"
|
||||
."border-gray-300"
|
||||
."rounded"
|
||||
."focus:outline-none"
|
||||
."focus:bg-white"
|
||||
@@ -446,6 +531,9 @@ impl InventoryNewCategoryForm {
|
||||
}
|
||||
}
|
||||
input type="submit" value="Add"
|
||||
x-bind:disabled="!save_active"
|
||||
."enabled:cursor-pointer"
|
||||
."disabled:opacity-50"
|
||||
."py-2"
|
||||
."border-2"
|
||||
."rounded"
|
||||
|
||||
@@ -24,6 +24,7 @@ impl Root {
|
||||
head {
|
||||
title { "Packager" }
|
||||
script src="https://unpkg.com/htmx.org@1.9.2" {}
|
||||
script src="https://unpkg.com/alpinejs@3.12.1" defer {}
|
||||
script src="https://cdn.tailwindcss.com" {}
|
||||
script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js" defer {}
|
||||
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css";
|
||||
|
||||
@@ -9,6 +9,9 @@ use serde_variant::to_variant_name;
|
||||
use crate::ClientState;
|
||||
pub struct TripManager;
|
||||
|
||||
pub mod types;
|
||||
pub use types::*;
|
||||
|
||||
impl TripManager {
|
||||
pub fn build(trips: Vec<models::Trip>) -> Markup {
|
||||
html!(
|
||||
@@ -557,7 +560,7 @@ impl TripInfo {
|
||||
."gap-1"
|
||||
{
|
||||
span { (triptype.name) }
|
||||
span ."mdi" ."mdi-delete" ."text-sm" {}
|
||||
span ."mdi" ."mdi-close" ."text-sm" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -584,6 +587,7 @@ impl TripInfo {
|
||||
."flex-column"
|
||||
."items-center"
|
||||
."hover:bg-green-200"
|
||||
."hover:opacity-100"
|
||||
."gap-1"
|
||||
."opacity-60"
|
||||
{
|
||||
@@ -623,7 +627,9 @@ pub struct TripComment;
|
||||
impl TripComment {
|
||||
pub fn build(trip: &models::Trip) -> Markup {
|
||||
html!(
|
||||
div {
|
||||
div
|
||||
x-data="{ save_active: false }"
|
||||
{
|
||||
h1 ."text-xl" ."mb-5" { "Comments" }
|
||||
|
||||
form
|
||||
@@ -636,6 +642,7 @@ impl TripComment {
|
||||
// https://stackoverflow.com/a/48460773
|
||||
textarea
|
||||
#"comment"
|
||||
x-on:input="save_active=true"
|
||||
."border" ."w-full" ."h-48"
|
||||
name="new-comment"
|
||||
form="edit-comment"
|
||||
@@ -647,11 +654,14 @@ impl TripComment {
|
||||
button
|
||||
type="submit"
|
||||
form="edit-comment"
|
||||
x-bind:disabled="!save_active"
|
||||
."enabled:bg-green-200"
|
||||
."enabled:hover:bg-green-400"
|
||||
."enabled:cursor-pointer"
|
||||
."disabled:opacity-50"
|
||||
."disabled:bg-gray-300"
|
||||
."mt-2"
|
||||
."border"
|
||||
."bg-green-200"
|
||||
."hover:bg-green-400"
|
||||
."cursor-pointer"
|
||||
."flex"
|
||||
."flex-column"
|
||||
."p-2"
|
||||
163
rust/src/components/trip/types.rs
Normal file
163
rust/src/components/trip/types.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use crate::models;
|
||||
use crate::ClientState;
|
||||
use maud::{html, Markup};
|
||||
|
||||
pub struct TypeList;
|
||||
|
||||
impl TypeList {
|
||||
pub fn build(state: &ClientState, trip_types: Vec<models::TripsType>) -> Markup {
|
||||
html!(
|
||||
div ."p-8" ."flex" ."flex-col" ."gap-8" {
|
||||
h1 ."text-2xl" {"Trip Types"}
|
||||
|
||||
ul
|
||||
."flex"
|
||||
."flex-col"
|
||||
."items-stretch"
|
||||
."border-t"
|
||||
."border-l"
|
||||
."h-full"
|
||||
{
|
||||
@for trip_type in trip_types {
|
||||
li
|
||||
."border-b"
|
||||
."border-r"
|
||||
."flex"
|
||||
."flex-row"
|
||||
."justify-between"
|
||||
."items-stretch"
|
||||
{
|
||||
@if state.trip_type_edit.map_or(false, |id| id == trip_type.id) {
|
||||
form
|
||||
."hidden"
|
||||
id="edit-trip-type"
|
||||
action={ (trip_type.id) "/edit/name/submit" }
|
||||
target="_self"
|
||||
method="post"
|
||||
{}
|
||||
div
|
||||
."bg-blue-200"
|
||||
."p-2"
|
||||
."grow"
|
||||
{
|
||||
input
|
||||
."bg-blue-100"
|
||||
."hover:bg-white"
|
||||
."w-full"
|
||||
type="text"
|
||||
name="new-value"
|
||||
form="edit-trip-type"
|
||||
value=(trip_type.name)
|
||||
;
|
||||
}
|
||||
div
|
||||
."flex"
|
||||
."flex-row"
|
||||
{
|
||||
a
|
||||
href="."
|
||||
."bg-red-200"
|
||||
."hover:bg-red-300"
|
||||
."w-8"
|
||||
."flex"
|
||||
{
|
||||
span
|
||||
."mdi"
|
||||
."mdi-cancel"
|
||||
."text-xl"
|
||||
."m-auto"
|
||||
;
|
||||
}
|
||||
button
|
||||
type="submit"
|
||||
form="edit-trip-type"
|
||||
."bg-green-200"
|
||||
."hover:bg-green-300"
|
||||
."w-8"
|
||||
{
|
||||
span
|
||||
."mdi"
|
||||
."mdi-content-save"
|
||||
."text-xl"
|
||||
;
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
span
|
||||
."p-2"
|
||||
{
|
||||
(trip_type.name)
|
||||
}
|
||||
|
||||
div
|
||||
."bg-blue-100"
|
||||
."hover:bg-blue-200"
|
||||
."p-0"
|
||||
."w-8"
|
||||
{
|
||||
a
|
||||
.flex
|
||||
."w-full"
|
||||
."h-full"
|
||||
href={ "?edit=" (trip_type.id) }
|
||||
{
|
||||
span
|
||||
."m-auto"
|
||||
."mdi"
|
||||
."mdi-pencil"
|
||||
."text-xl";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form
|
||||
name="new-trip-type"
|
||||
action="/trips/types/"
|
||||
target="_self"
|
||||
method="post"
|
||||
."mt-8" ."p-5" ."border-2" ."border-gray-200"
|
||||
{
|
||||
div ."mb-5" ."flex" ."flex-row" {
|
||||
span ."mdi" ."mdi-playlist-plus" ."text-2xl" ."mr-4" {}
|
||||
p ."inline" ."text-xl" { "Add new trip type" }
|
||||
}
|
||||
div ."w-11/12" ."m-auto" {
|
||||
div ."mx-auto" ."pb-8" {
|
||||
div ."flex" ."flex-row" ."justify-center" {
|
||||
label for="new-trip-type-name" ."font-bold" ."w-1/2" ."p-2" ."text-center" { "Name" }
|
||||
span ."w-1/2" {
|
||||
input
|
||||
type="text"
|
||||
id="new-trip-type-name"
|
||||
name="new-trip-type-name"
|
||||
."block"
|
||||
."w-full"
|
||||
."p-2"
|
||||
."bg-gray-50"
|
||||
."border-2"
|
||||
."rounded"
|
||||
."focus:outline-none"
|
||||
."focus:bg-white"
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
input
|
||||
type="submit"
|
||||
value="Add"
|
||||
."py-2"
|
||||
."border-2"
|
||||
."rounded"
|
||||
."border-gray-300"
|
||||
."mx-auto"
|
||||
."w-full"
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user