Add base of edit item form

main
Wes Holland 10 months ago
parent 9e6b17b154
commit 123a95277d

@ -0,0 +1,14 @@
use axum::debug_handler;
use axum::extract::{Path, State};
use sqlx::SqlitePool;
use askama_axum::{IntoResponse, Response};
use crate::db::inventory_item::delete_inventory_item;
use crate::error::AppError;
#[debug_handler]
pub async fn delete_item(State(db): State<SqlitePool>, Path(id): Path<i64>) -> Result<Response, AppError> {
delete_inventory_item(&db, id).await?;
let response = format!("Item {} Deleted", id);
Ok(response.into_response())
}

@ -0,0 +1,184 @@
use crate::error::AppError;
use crate::session::SessionUser;
use askama::Template;
use askama_axum::{IntoResponse, Response};
use axum::extract::{Path, State};
use axum::{async_trait, debug_handler};
use axum_htmx::{HxEvent, HxResponseTrigger};
use serde::Deserialize;
use crate::app::routes::AppState;
use crate::db::display_unit::DbDisplayUnit;
use crate::db;
use crate::db::inventory_item::{DbInventoryItem, DbInventoryItemEditableFields};
use crate::util::form::validate_form::{ValidateForm, ValidateFormError};
use crate::util::form::helpers::deserialize_form_checkbox;
use crate::util::form::base_response::BaseResponse;
use crate::util::form::edit_field_value::{EditBoolField, EditFloatField, EditStringField};
use crate::util::form::extractors::{FormBase, FormWithPathVars, ValidForm};
use crate::util::form::field_error::FieldError;
#[derive(Template, Debug, Default)]
#[template(path = "item/item-edit-form.html")]
pub struct EditItemFormTemplate {
pub item_id: i64,
pub display_units: Vec<DbDisplayUnit>,
pub name: EditStringField,
pub display_unit_value: EditStringField,
pub reorder_point: EditStringField,
pub pims_id: EditStringField,
pub vetcove_id: EditStringField,
pub allow_fractional_units: EditBoolField,
}
impl EditItemFormTemplate {
pub fn base(item: DbInventoryItemEditableFields, display_units: Vec<DbDisplayUnit>) -> Self {
let name = EditStringField::new(item.name);
let pims_id = EditStringField::from(item.pims_id);
let vetcove_id = EditStringField::from(item.vetcove_id);
let allow_fractional_units = EditBoolField::new(item.allow_fractional_units);
let display_unit_value = EditStringField::new(item.display_unit_abbreviation);
let precision = if item.allow_fractional_units { 2 } else { 0 };
let reorder_point= format!("{:.*}", precision, item.reorder_point);
let reorder_point = EditStringField::new(reorder_point);
Self {
item_id: item.id,
name,
reorder_point,
pims_id,
vetcove_id,
allow_fractional_units,
display_unit_value,
display_units,
}
}
}
#[derive(Deserialize, Debug)]
pub struct EditItemFormData {
name: String,
display_unit: String,
reorder_point: f64,
pims_id: Option<String>,
vetcove_id: Option<String>,
#[serde(default, deserialize_with = "deserialize_form_checkbox")]
allow_fractional_units: bool,
}
#[async_trait]
impl ValidateForm for FormWithPathVars<i64,EditItemFormData> {
type ValidationErrorResponse = EditItemFormTemplate;
async fn validate(self, state: &AppState) -> Result<Self, ValidateFormError<Self::ValidationErrorResponse>> {
let item_id = self.path_data;
let display_units = db::display_unit::get_display_units(&state.db).await?;
let current_values = db::inventory_item::inventory_item_get_by_id_editable_fields(&state.db, item_id).await?;
let name = EditStringField::new_with_base(
self.form_data.name.clone(),
current_values.name.clone(),
)
.invalid_if(|v| v.is_empty(), FieldError::Required);
let display_unit_value = EditStringField::new_with_base(
self.form_data.display_unit.clone(),
current_values.display_unit_abbreviation.clone(),
)
.invalid_if(|v| v.is_empty(), FieldError::Required)
.invalid_if(|v| !display_units.iter().any(
|x| x.abbreviation.eq(&self.form_data.display_unit)),
FieldError::SelectOption)
.reset_if_error();
let reorder_point = EditFloatField::new_with_base(
self.form_data.reorder_point.clone(),
current_values.reorder_point.clone(),
)
.invalid_if(|v| v.is_nan() || v.is_infinite() || v.is_sign_negative(), FieldError::PositiveNumber)
.invalid_if(|v| !(self.form_data.allow_fractional_units || v.fract() == 0.0), FieldError::WholeNumber)
.map(|v, b| (format!("{:.2}", v), format!("{:.2}", b)));
let pims_id = EditStringField::new_with_base(
self.form_data.pims_id.clone().unwrap_or_default(),
current_values.pims_id.unwrap_or_default(),
)
.invalid_if(|v| v.chars().any(char::is_whitespace), FieldError::ValidIdentifier);
let vetcove_id = EditStringField::new_with_base(
self.form_data.vetcove_id.clone().unwrap_or_default(),
current_values.vetcove_id.unwrap_or_default(),
)
.invalid_if(|v| !v.chars().all(|c| c.is_ascii_digit()), FieldError::ValidIdentifier);
let allow_fractional_units = EditBoolField::new_with_base(
self.form_data.allow_fractional_units,
current_values.allow_fractional_units,
);
if name.is_error() ||
display_unit_value.is_error() ||
reorder_point.is_error() ||
pims_id.is_error() ||
vetcove_id.is_error() {
return Err(ValidateFormError::ValidationError(
Self::ValidationErrorResponse {
item_id,
display_units,
name,
display_unit_value,
reorder_point,
pims_id,
vetcove_id,
allow_fractional_units,
}));
}
Ok(self)
}
}
pub async fn edit_item_form_post(
State(state): State<AppState>,
user: SessionUser,
Path(id): Path<i64>,
form: ValidForm<FormWithPathVars<i64,EditItemFormData>>,
) -> Result<Response, AppError> {
let form_data = &form.form_data;
db::inventory_item::update_inventory_item(&state.db, id, &form_data.name, form_data.reorder_point,
form_data.allow_fractional_units, &form_data.display_unit,
&form_data.pims_id, &form_data.vetcove_id, true,
).await?;
let new_base = db::inventory_item::inventory_item_get_by_id_editable_fields(&state.db, id).await?;
let display_units = db::display_unit::get_display_units(&state.db).await?;
let fresh_form = EditItemFormTemplate::base(new_base, display_units);
let events = vec![
HxEvent::from("form-submit-success"),
HxEvent::from("item-updated"),
];
Ok( (
HxResponseTrigger::normal(events),
fresh_form.into_response()
).into_response() )
}
#[debug_handler]
pub async fn edit_item_form_get(
Path(id): Path<i64>,
State(state): State<AppState>,
) -> Result<Response, AppError> {
let item_vars = db::inventory_item::inventory_item_get_by_id_editable_fields(&state.db, id).await?;
let display_units = db::display_unit::get_display_units(&state.db).await?;
let template = EditItemFormTemplate::base(item_vars, display_units);
Ok(template.into_response())
}

@ -1,4 +1,4 @@
use crate::db::inventory_item::{inventory_item_get_by_id_with_unit, DbInventoryItemWithCount}; use crate::db::inventory_item::{inventory_item_get_by_id_with_unit_and_count, DbInventoryItemWithCount};
use crate::error::AppError; use crate::error::AppError;
use askama::Template; use askama::Template;
use askama_axum::{IntoResponse, Response}; use askama_axum::{IntoResponse, Response};
@ -15,7 +15,7 @@ struct ItemTemplate {
#[debug_handler] #[debug_handler]
pub async fn item(State(db): State<SqlitePool>, Path(id): Path<i64>) -> Result<Response, AppError> { pub async fn item(State(db): State<SqlitePool>, Path(id): Path<i64>) -> Result<Response, AppError> {
let item: ItemDisplayItem = inventory_item_get_by_id_with_unit(&db, id).await?.into(); let item: ItemDisplayItem = inventory_item_get_by_id_with_unit_and_count(&db, id).await?.into();
Ok(ItemTemplate { item_id: id, item }.into_response()) Ok(ItemTemplate { item_id: id, item }.into_response())
} }

@ -3,3 +3,5 @@ pub mod item;
pub mod stats; pub mod stats;
pub mod count; pub mod count;
pub mod create; pub mod create;
pub mod delete;
pub mod edit;

@ -6,7 +6,7 @@ use anyhow::anyhow;
use axum::debug_handler; use axum::debug_handler;
use crate::app::item::item::ItemDisplayItem; use crate::app::item::item::ItemDisplayItem;
use crate::db::adjustment::get_item_adjustment_valuation_weighted_mean; use crate::db::adjustment::get_item_adjustment_valuation_weighted_mean;
use crate::db::inventory_item::inventory_item_get_by_id_with_unit; use crate::db::inventory_item::inventory_item_get_by_id_with_unit_and_count;
use crate::error::AppError; use crate::error::AppError;
use crate::util::currency::int_cents_to_dollars_string; use crate::util::currency::int_cents_to_dollars_string;
@ -22,7 +22,7 @@ struct ItemStatsTemplate {
pub async fn item_stats(State(db): State<SqlitePool>, Path(id): Path<i64>) -> Result<Response, AppError> { pub async fn item_stats(State(db): State<SqlitePool>, Path(id): Path<i64>) -> Result<Response, AppError> {
//TODO This is pretty chatty with the database. Might could cut down the //TODO This is pretty chatty with the database. Might could cut down the
// number of queries // number of queries
let item: ItemDisplayItem = inventory_item_get_by_id_with_unit(&db, id).await?.into(); let item: ItemDisplayItem = inventory_item_get_by_id_with_unit_and_count(&db, id).await?.into();
let value = get_item_adjustment_valuation_weighted_mean(&db, id) let value = get_item_adjustment_valuation_weighted_mean(&db, id)
.await? .await?

@ -2,7 +2,7 @@ use crate::app::{catalog, history, home, item, overview, reports, upload};
use crate::session::SessionUser; use crate::session::SessionUser;
use axum::extract::FromRef; use axum::extract::FromRef;
use axum::middleware::from_extractor; use axum::middleware::from_extractor;
use axum::routing::{get, post}; use axum::routing::{get, post, delete};
use axum::Router; use axum::Router;
use axum_htmx::AutoVaryLayer; use axum_htmx::AutoVaryLayer;
use oauth2::basic::BasicClient; use oauth2::basic::BasicClient;
@ -16,27 +16,34 @@ pub fn routes() -> Router<AppState> {
.route("/home/search", get(home::home)) .route("/home/search", get(home::home))
.route("/catalog", get(catalog::catalog)) .route("/catalog", get(catalog::catalog))
.route("/catalog/", get(catalog::catalog)) .route("/catalog/", get(catalog::catalog))
.route("/item/:item_id", get(item::item::item)) .route("/item/:item_id",
.route("/item/:item_id/", get(item::item::item)) get(item::item::item).
delete(item::delete::delete_item)
)
.route("/item/:item_id/",
get(item::item::item).
delete(item::delete::delete_item)
)
.route("/item/:item_id/count", get(item::count::item_count)) .route("/item/:item_id/count", get(item::count::item_count))
.route("/item/:item_id/stats", get(item::stats::item_stats)) .route("/item/:item_id/stats", get(item::stats::item_stats))
.route("/item/:item_id/edit",
get(item::edit::edit_item_form_get).
post(item::edit::edit_item_form_post)
)
.route("/item/create", .route("/item/create",
get(item::create::create_item_form_get) get(item::create::create_item_form_get).
.post(item::create::create_item_form_post) post(item::create::create_item_form_post)
) )
.route( .route("/item/:item_id/adjustments",
"/item/:item_id/adjustments",
get(item::adjustment::table::get_adjustments_table), get(item::adjustment::table::get_adjustments_table),
) )
.route( .route("/item/:item_id/adjustment/negative",
"/item/:item_id/adjustment/negative", get(item::adjustment::negative::negative_adjustment_form_get).
get(item::adjustment::negative::negative_adjustment_form_get) post(item::adjustment::negative::negative_adjustment_form_post),
.post(item::adjustment::negative::negative_adjustment_form_post),
) )
.route( .route("/item/:item_id/adjustment/positive",
"/item/:item_id/adjustment/positive", get(item::adjustment::positive::positive_adjustment_form_get).
get(item::adjustment::positive::positive_adjustment_form_get) post(item::adjustment::positive::positive_adjustment_form_post),
.post(item::adjustment::positive::positive_adjustment_form_post),
) )
.route("/upload", get(upload::index::upload_index_handler)) .route("/upload", get(upload::index::upload_index_handler))
.route("/upload/catalog", post(upload::catalog::catalog_import)) .route("/upload/catalog", post(upload::catalog::catalog_import))

@ -1,6 +1,7 @@
use serde::Serialize; use serde::Serialize;
use anyhow::Result; use anyhow::Result;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tracing::info;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
@ -119,6 +120,39 @@ pub async fn add_inventory_item(db: &SqlitePool, name: &str, reorder_point: f64,
Ok(new_id) Ok(new_id)
} }
pub async fn delete_inventory_item(db: &SqlitePool, id: i64) -> Result<()> {
let mut tx = db.begin().await?;
let adjustments = sqlx::query!(
r#"
DELETE FROM Adjustment WHERE item = ?
"#,
id
).execute(&mut *tx)
.await?
.rows_affected();
let items = sqlx::query!(
r#"
DELETE FROM InventoryItem WHERE id = ?
"#,
id
).execute(&mut *tx)
.await?
.rows_affected();
tx.commit().await?;
if items == 0 {
info!("Delete requested for {}, but no such item exists", id);
}
else {
info!("Item {} deleted. {} adjustments", id, adjustments);
}
Ok(())
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
pub struct DbInventoryItemWithCount { pub struct DbInventoryItemWithCount {
@ -133,7 +167,7 @@ pub struct DbInventoryItemWithCount {
pub active: bool, pub active: bool,
} }
pub async fn inventory_item_get_by_id_with_unit(db: &SqlitePool, id: i64) -> Result<DbInventoryItemWithCount> { pub async fn inventory_item_get_by_id_with_unit_and_count(db: &SqlitePool, id: i64) -> Result<DbInventoryItemWithCount> {
sqlx::query_as!( sqlx::query_as!(
DbInventoryItemWithCount, DbInventoryItemWithCount,
r#" r#"
@ -174,3 +208,75 @@ pub async fn does_inventory_item_allow_fractional_units(db: &SqlitePool, id: i64
Ok(res.allow_fractional_units) Ok(res.allow_fractional_units)
} }
#[derive(Debug, Serialize)]
#[derive(sqlx::FromRow)]
pub struct DbInventoryItemEditableFields {
pub id: i64,
pub name: String,
pub reorder_point: f64,
pub allow_fractional_units: bool,
pub display_unit: i64,
pub display_unit_str: String,
pub display_unit_abbreviation: String,
pub active: bool,
pub pims_id: Option<String>,
pub vetcove_id: Option<String>,
}
pub async fn inventory_item_get_by_id_editable_fields(db: &SqlitePool, id: i64) -> Result<DbInventoryItemEditableFields> {
sqlx::query_as!(
DbInventoryItemEditableFields,
r#"
SELECT
item.id as id,
item.name as name,
item.reorder_point as reorder_point,
item.allow_fractional_units as allow_fractional_units,
item.display_unit as display_unit,
display_unit.unit as display_unit_str,
display_unit.abbreviation as display_unit_abbreviation,
item.active as active,
item.pims_id as pims_id,
item.vetcove_id as vetcove_id
FROM
InventoryItem as item
JOIN DisplayUnit as display_unit ON item.display_unit = display_unit.id
WHERE item.id = ?
"#,
id
)
.fetch_one(db).await
.map_err(From::from)
}
pub async fn update_inventory_item(db: &SqlitePool, id: i64, name: &str, reorder_point: f64,
allow_fractional_units: bool, display_unit_abbreviation: &str,
pims_id: &Option<String>, vetcove_id: &Option<String>, active: bool
) -> Result<()> {
let affected = sqlx::query!(
r#"
UPDATE InventoryItem SET
name = ?,
reorder_point = ?,
allow_fractional_units = ?,
display_unit = (SELECT id from DisplayUnit WHERE abbreviation = ? ),
active = ?,
pims_id = ?,
vetcove_id = ?
WHERE id = ?
"#,
name,
reorder_point,
allow_fractional_units,
display_unit_abbreviation,
active,
pims_id,
vetcove_id,
id
).execute(db).await?.rows_affected();
assert_eq!(affected, 1);
Ok(())
}

@ -0,0 +1,132 @@
use std::fmt::{Debug, Display};
use crate::util::form::field_error::FieldError;
/**
A sum type for use with forms and form validation. Similar to a basic field_value,
but contains a base value that can be compared to and reverted to. The base value is
assumed to be "good" in terms of validation, meaning the Error refers to the current
value rather than the base value.
*/
pub struct EditFieldValue<Value, Error> {
pub value: Value,
pub base: Value,
pub error: Option<Error>,
}
pub type EditStringField = EditFieldValue<String, FieldError>;
pub type EditBoolField = EditFieldValue<bool, FieldError>;
pub type EditFloatField = EditFieldValue<f64, FieldError>;
impl<Value, Error> EditFieldValue<Value, Error>
where Value: Default + Clone + PartialEq, Error: ToString
{
pub fn new(value: Value) -> Self {
let base = value.clone();
Self { value, base, error: None }
}
pub fn new_with_base(value: Value, base: Value) -> Self {
Self { value, base, error: None }
}
pub fn set_error(&mut self, error: Error) {
self.error = Some(error);
}
pub fn is_error(&self) -> bool {
self.error.is_some()
}
pub fn invalid_if<I>(mut self, is_invalid: I, err: Error ) -> Self
where I: FnOnce(&Value) -> bool {
if is_invalid(&self.value) {
self.set_error(err);
}
self
}
pub fn invalid_if_then<I, E>(mut self, is_invalid: I, err_if: E ) -> Self
where I: FnOnce(&Value) -> bool, E: FnOnce() -> Error {
if is_invalid(&self.value) {
self.set_error(err_if());
}
self
}
pub fn with_base(mut self, base: Value) -> Self {
self.base = base;
self
}
pub fn is_changed(&self) -> bool {
self.value != self.base
}
pub fn reset(mut self) -> Self {
self.value = self.base.clone();
self
}
pub fn reset_if_error(mut self) -> Self {
if self.is_error() { self.reset() } else { self }
}
pub fn map<F, V>(self, f: F) -> EditFieldValue<V, Error>
where F: FnOnce(Value, Value) -> (V, V) {
let (value, base) = f(self.value, self.base);
EditFieldValue::<V, Error> {
value,
base,
error: self.error,
}
}
pub fn with_error(mut self, error: Error) -> Self {
self.set_error(error);
self
}
pub fn error_string(&self) -> String {
match self.error {
Some(ref error) => error.to_string(),
None => "Unknown".to_string(),
}
}
}
impl<Value, Error> Default for EditFieldValue<Value, Error>
where Value: Default + Clone + PartialEq, Error: ToString
{
fn default() -> Self {
Self::new(Value::default())
}
}
impl<Value, Error> Debug for EditFieldValue<Value, Error>
where Value: Debug,
Error: Debug {
fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
fmt.debug_struct("EditFieldValue")
.field("value", &self.value)
.field("error", &self.error)
.finish()
}
}
impl<Value, Error> From<Option<Value>> for EditFieldValue<Value, Error>
where Value: Default + Clone + PartialEq, Error: ToString {
fn from(value: Option<Value>) -> Self {
Self::new(value.unwrap_or_default())
}
}
impl<Value, Error> From<Result<Value, Error>> for EditFieldValue<Value, Error>
where Value: Default + Clone + PartialEq, Error: ToString {
fn from(value: Result<Value, Error>) -> Self {
match value {
Ok(value) => Self::new(value),
Err(error) => Self::default().with_error(error),
}
}
}

@ -4,3 +4,4 @@ pub mod validate_form;
pub mod extractors; pub mod extractors;
pub mod helpers; pub mod helpers;
pub mod base_response; pub mod base_response;
pub mod edit_field_value;

@ -0,0 +1,148 @@
<form
id="item-create"
hx-post="/item/{{item_id}}/edit"
hx-target="this"
hx-swap="outerHTML"
x-on:htmx:response-error="$dispatch('notice', {type: 'error', text: 'Unknown error'})"
x-on:form-submit-success="$dispatch('notice', {type: 'info', text: 'Changes saved'})"
>
<div class="mb-5 grid grid-cols-6 gap-4 p-2">
<div class="col-span-6">
<label for="name" class="mb-2 block text-sm font-medium">Name</label>
<input
type="text"
id="name"
name="name"
class="block w-11/12 p-2 rounded-lg border border-neutral-900 text-sm text-neutral-900 focus:border-paynes-gray focus:ring-paynes-gray dark:text-slate-100"
aria-label="name"
value="{{ name.value }}"
required
{% if name.is_error() -%}
aria-invalid="true"
aria-describedby="invalid-name"
{% endif -%}
/>
{% if name.is_error() -%}
<small id="invalid-name" class="block mt-2 text-sm text-cerise">
{{ name.error_string() }}
</small>
{% endif -%}
</div>
<div class="col-span-2">
<label for="reorder_point" class="mb-2 block text-sm font-medium">Reorder Point</label>
<input
type="number"
id="reorder_point"
name="reorder_point"
step="0.01"
class="block w-3/4 p-2 rounded-lg border border-neutral-900 text-sm text-neutral-900 focus:border-paynes-gray focus:ring-paynes-gray dark:text-slate-100"
aria-label="reorder point"
value="{{ reorder_point.value }}"
{% if reorder_point.is_error() -%}
aria-invalid="true"
aria-describedby="invalid-reorder-point"
{% endif -%}
/>
{% if reorder_point.is_error() -%}
<small id="invalid-reorder-point" class="block mt-2 text-sm text-cerise">
{{ reorder_point.error_string() }}
</small>
{% endif -%}
</div>
<div class="col-span-2">
<label for="display_unit" class="mb-2 block text-sm font-medium">Unit</label>
<select
id="display_unit"
name="display_unit"
class="block rounded-lg border border-neutral-900 p-2 text-sm text-neutral-900 focus:border-paynes-gray focus:ring-paynes-gray dark:text-slate-100"
aria-label="name"
required
{% if display_unit_value.is_error() -%}
aria-invalid="true"
aria-describedby="invalid-display-unit"
{% endif -%}
>
{% for unit in display_units -%}
<option value="{{ unit.abbreviation }}" {% if unit.abbreviation == display_unit_value.value %} selected {% endif %}>{{ unit.unit }}</option>
{% endfor %}
</select>
{% if display_unit_value.is_error() -%}
<small id="invalid-display-unit" class="block mt-2 text-sm text-cerise">
{{ display_unit_value.error_string() }}
</small>
{% endif -%}
</div>
<div class="col-span-2">
<label for="allow_fractional_units" class="block mb-2 text-sm font-medium">Fractional</label>
<input
type="checkbox"
id="allow_fractional_units"
name="allow_fractional_units"
class="block w-4 h-4 text-dark-cyan border-slate-100 rounded-sm focus:ring-space-cadet focus:ring-2"
{% if allow_fractional_units.value -%} checked {% endif -%}
/>
</div>
<div class="col-span-3">
<label for="pims_id" class="mb-2 block text-sm font-medium">PIMS Id</label>
<input
type="text"
id="pims_id"
name="pims_id"
class="block p-2 rounded-lg border border-neutral-900 text-sm text-neutral-900 focus:border-paynes-gray focus:ring-paynes-gray dark:text-slate-100"
value="{{ pims_id.value }}"
aria-label="pims id"
{% if pims_id.is_error() -%}
aria-invalid="true"
aria-describedby="invalid-pims-id"
{% endif -%}
/>
{% if pims_id.is_error() -%}
<small id="invalid-pims-id" class="block mt-2 text-sm text-cerise">
{{ pims_id.error_string() }}
</small>
{% endif -%}
</div>
<div class="col-span-3">
<label for="vetcove_id" class="mb-2 block text-sm font-medium">Vetcove Id</label>
<input
type="text"
id="vetcove_id"
name="vetcove_id"
class="block p-2 rounded-lg border border-neutral-900 text-sm text-neutral-900 focus:border-paynes-gray focus:ring-paynes-gray dark:text-slate-100"
value="{{ vetcove_id.value }}"
aria-label="vetcove id"
{% if vetcove_id.is_error() -%}
aria-invalid="true"
aria-describedby="invalid-vetcove-id"
{% endif -%}
/>
{% if vetcove_id.is_error() -%}
<small id="invalid-vetcove-id" class="block mt-2 text-sm text-cerise">
{{ vetcove_id.error_string() }}
</small>
{% endif -%}
</div>
<div class="col-span-4">
<button
class="mb-2 me-2 rounded-lg bg-english-violet px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"
>
Save
</button>
</div>
<div class="col-span-4">
<button
class="mb-2 me-2 rounded-lg bg-cerise px-5 py-2.5 text-sm font-medium text-neutral-900 hover:bg-orchid-pink focus:outline-none focus:ring-4 focus:ring-dark-cyan"
hx-delete="/item/{{item_id}}"
>
Delete
</button>
</div>
</div>
</form>

@ -37,6 +37,12 @@
> >
Plus Plus
</button> </button>
<button
class="mb-2 me-2 rounded-lg bg-paynes-gray px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"
@click="showSidebar('item-edit-form')"
>
Edit
</button>
</div> </div>
</section> </section>
{% endif %} {% endif %}
@ -48,14 +54,15 @@
hx-swap="outerHTML" hx-swap="outerHTML"
></div> ></div>
</section> </section>
</div> </div>
</div>
{% endblock %} {% endblock %}
{% block sidebar_title %} {% block sidebar_title %}
<h2 class="text-lg font-semibold uppercase" x-show="sidebar.content === 'negative-adjustment-form'">Negative Adjustment</h2> <h2 class="text-lg font-semibold uppercase" x-show="sidebar.content === 'negative-adjustment-form'">Negative Adjustment</h2>
<h2 class="text-lg font-semibold uppercase" x-show="sidebar.content === 'positive-adjustment-form'">Positive Adjustment</h2> <h2 class="text-lg font-semibold uppercase" x-show="sidebar.content === 'positive-adjustment-form'">Positive Adjustment</h2>
<h2 class="text-lg font-semibold uppercase" x-show="sidebar.content === 'item-edit-form'">Edit</h2>
{% endblock %} {% endblock %}
{% block sidebar_content %} {% block sidebar_content %}
@ -74,5 +81,12 @@
hx-swap="outerHTML" hx-swap="outerHTML"
></div> ></div>
</div> </div>
<div x-show="sidebar.content === 'item-edit-form'">
<div
hx-get="/item/{{item_id}}/edit"
hx-trigger="load"
hx-swap="outerHTML"
></div>
</div>
</div> </div>
{% endblock %} {% endblock %}

Loading…
Cancel
Save

Powered by TurnKey Linux.