add tests; Closes #30
This commit is contained in:
parent
bbb78cbc44
commit
dc4b4b3499
@ -50,7 +50,6 @@ pub struct BoatToAdd<'r> {
|
|||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct BoatToUpdate<'r> {
|
pub struct BoatToUpdate<'r> {
|
||||||
pub id: i32,
|
|
||||||
pub name: &'r str,
|
pub name: &'r str,
|
||||||
pub amount_seats: i64,
|
pub amount_seats: i64,
|
||||||
pub year_built: Option<i64>,
|
pub year_built: Option<i64>,
|
||||||
@ -58,8 +57,8 @@ pub struct BoatToUpdate<'r> {
|
|||||||
pub default_shipmaster_only_steering: bool,
|
pub default_shipmaster_only_steering: bool,
|
||||||
pub skull: bool,
|
pub skull: bool,
|
||||||
pub external: bool,
|
pub external: bool,
|
||||||
pub location_id: Option<i64>,
|
pub location_id: i64,
|
||||||
pub owner: Option<i64>,
|
pub owner_id: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Boat {
|
impl Boat {
|
||||||
@ -143,7 +142,7 @@ ORDER BY amount_seats DESC
|
|||||||
.await.is_ok()
|
.await.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&self, db: &SqlitePool, boat: BoatToUpdate<'_>) -> bool {
|
pub async fn update(&self, db: &SqlitePool, boat: BoatToUpdate<'_>) -> Result<(), String> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"UPDATE boat SET name=?, amount_seats=?, year_built=?, boatbuilder=?, default_shipmaster_only_steering=?, skull=?, external=?, location_id=?, owner=? WHERE id=?",
|
"UPDATE boat SET name=?, amount_seats=?, year_built=?, boatbuilder=?, default_shipmaster_only_steering=?, skull=?, external=?, location_id=?, owner=? WHERE id=?",
|
||||||
boat.name,
|
boat.name,
|
||||||
@ -154,12 +153,12 @@ ORDER BY amount_seats DESC
|
|||||||
boat.skull,
|
boat.skull,
|
||||||
boat.external,
|
boat.external,
|
||||||
boat.location_id,
|
boat.location_id,
|
||||||
boat.owner,
|
boat.owner_id,
|
||||||
self.id
|
self.id
|
||||||
)
|
)
|
||||||
.execute(db)
|
.execute(db)
|
||||||
.await
|
.await.map_err(|e| e.to_string())?;
|
||||||
.is_ok()
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, db: &SqlitePool) {
|
pub async fn delete(&self, db: &SqlitePool) {
|
||||||
@ -173,12 +172,17 @@ ORDER BY amount_seats DESC
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{
|
||||||
model::boat::{Boat, BoatToAdd},
|
model::{
|
||||||
|
boat::{Boat, BoatToAdd},
|
||||||
|
location::Location,
|
||||||
|
},
|
||||||
testdb,
|
testdb,
|
||||||
};
|
};
|
||||||
|
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
|
use super::BoatToUpdate;
|
||||||
|
|
||||||
#[sqlx::test]
|
#[sqlx::test]
|
||||||
fn test_find_correct_id() {
|
fn test_find_correct_id() {
|
||||||
let pool = testdb!();
|
let pool = testdb!();
|
||||||
@ -247,4 +251,133 @@ mod test {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_is_locked() {
|
||||||
|
let pool = testdb!();
|
||||||
|
let res = Boat::find_by_id(&pool, 5)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.is_locked(&pool)
|
||||||
|
.await;
|
||||||
|
assert_eq!(res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_is_not_locked() {
|
||||||
|
let pool = testdb!();
|
||||||
|
let res = Boat::find_by_id(&pool, 4)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.is_locked(&pool)
|
||||||
|
.await;
|
||||||
|
assert_eq!(res, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_is_not_locked_no_damage() {
|
||||||
|
let pool = testdb!();
|
||||||
|
let res = Boat::find_by_id(&pool, 3)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.is_locked(&pool)
|
||||||
|
.await;
|
||||||
|
assert_eq!(res, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_has_minor_damage() {
|
||||||
|
let pool = testdb!();
|
||||||
|
let res = Boat::find_by_id(&pool, 4)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.has_minor_damage(&pool)
|
||||||
|
.await;
|
||||||
|
assert_eq!(res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_has_no_minor_damage() {
|
||||||
|
let pool = testdb!();
|
||||||
|
let res = Boat::find_by_id(&pool, 5)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.has_minor_damage(&pool)
|
||||||
|
.await;
|
||||||
|
assert_eq!(res, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_on_water() {
|
||||||
|
let pool = testdb!();
|
||||||
|
let res = Boat::find_by_id(&pool, 2)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.on_water(&pool)
|
||||||
|
.await;
|
||||||
|
assert_eq!(res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_not_on_water() {
|
||||||
|
let pool = testdb!();
|
||||||
|
let res = Boat::find_by_id(&pool, 4)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.on_water(&pool)
|
||||||
|
.await;
|
||||||
|
assert_eq!(res, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_succ_update() {
|
||||||
|
let pool = testdb!();
|
||||||
|
let boat = Boat::find_by_id(&pool, 1).await.unwrap();
|
||||||
|
let location = Location::find_by_id(&pool, 1).await.unwrap();
|
||||||
|
let update = BoatToUpdate {
|
||||||
|
name: "my-new-boat-name",
|
||||||
|
amount_seats: 3,
|
||||||
|
year_built: None,
|
||||||
|
boatbuilder: None,
|
||||||
|
default_shipmaster_only_steering: false,
|
||||||
|
skull: true,
|
||||||
|
external: false,
|
||||||
|
location_id: 1,
|
||||||
|
owner_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
boat.update(&pool, update).await.unwrap();
|
||||||
|
|
||||||
|
let boat = Boat::find_by_id(&pool, 1).await.unwrap();
|
||||||
|
assert_eq!(boat.name, "my-new-boat-name");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_failed_update() {
|
||||||
|
let pool = testdb!();
|
||||||
|
let boat = Boat::find_by_id(&pool, 1).await.unwrap();
|
||||||
|
let location = Location::find_by_id(&pool, 1).await.unwrap();
|
||||||
|
let update = BoatToUpdate {
|
||||||
|
name: "my-new-boat-name",
|
||||||
|
amount_seats: 3,
|
||||||
|
year_built: None,
|
||||||
|
boatbuilder: None,
|
||||||
|
default_shipmaster_only_steering: false,
|
||||||
|
skull: true,
|
||||||
|
external: false,
|
||||||
|
location_id: 999,
|
||||||
|
owner_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match boat.update(&pool, update).await {
|
||||||
|
Ok(_) => panic!("Update with invalid location should not succeed"),
|
||||||
|
Err(e) => assert_eq!(
|
||||||
|
e,
|
||||||
|
"error returned from database: (code: 787) FOREIGN KEY constraint failed"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let boat = Boat::find_by_id(&pool, 1).await.unwrap();
|
||||||
|
assert_eq!(boat.name, "Haichenbach");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use rocket::form::FromFormField;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, SqlitePool};
|
use sqlx::{FromRow, SqlitePool};
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
||||||
|
|
||||||
use super::{logbook::Logbook, user::User};
|
use super::{
|
||||||
|
logbook::Logbook,
|
||||||
|
user::{MyNaiveDateTime, User},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
||||||
pub struct Rower {
|
pub struct Rower {
|
||||||
@ -11,18 +14,29 @@ pub struct Rower {
|
|||||||
|
|
||||||
impl Rower {
|
impl Rower {
|
||||||
pub async fn for_log(db: &SqlitePool, log: &Logbook) -> Vec<User> {
|
pub async fn for_log(db: &SqlitePool, log: &Logbook) -> Vec<User> {
|
||||||
sqlx::query_as!(
|
sqlx::query!(
|
||||||
User,
|
|
||||||
"
|
"
|
||||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
||||||
FROM user
|
FROM user
|
||||||
WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?)
|
WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?)
|
||||||
",
|
",
|
||||||
log.id
|
log.id
|
||||||
)
|
)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| User {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
pw: row.pw,
|
||||||
|
is_cox: row.is_cox,
|
||||||
|
is_admin: row.is_admin,
|
||||||
|
is_guest: row.is_guest,
|
||||||
|
deleted: row.deleted,
|
||||||
|
last_access: row.last_access.map(MyNaiveDateTime),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(db: &mut Transaction<'_, Sqlite>, logbook_id: i64, rower_id: i64) {
|
pub async fn create(db: &mut Transaction<'_, Sqlite>, logbook_id: i64, rower_id: i64) {
|
||||||
|
@ -1,21 +1,44 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
|
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
|
||||||
use chrono::{Datelike, Local, NaiveDate};
|
use chrono::{Datelike, Local, NaiveDate, NaiveDateTime};
|
||||||
use log::info;
|
use log::info;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
async_trait,
|
async_trait,
|
||||||
|
form::{self, FromFormField, ValueField},
|
||||||
http::{Cookie, Status},
|
http::{Cookie, Status},
|
||||||
request::{self, FromRequest, Outcome},
|
request::{self, FromRequest, Outcome},
|
||||||
time::{Duration, OffsetDateTime},
|
time::{Duration, OffsetDateTime},
|
||||||
Request,
|
FromForm, Request,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, SqlitePool};
|
use sqlx::{sqlite::SqliteValueRef, Decode, FromRow, SqlitePool};
|
||||||
|
|
||||||
use super::{log::Log, tripdetails::TripDetails, Day};
|
use super::{log::Log, tripdetails::TripDetails, Day};
|
||||||
|
|
||||||
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct MyNaiveDateTime(pub chrono::NaiveDateTime);
|
||||||
|
|
||||||
|
impl<'r> Decode<'r, sqlx::Sqlite> for MyNaiveDateTime {
|
||||||
|
fn decode(value: SqliteValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let dt: NaiveDateTime = Decode::decode(value)?;
|
||||||
|
Ok(MyNaiveDateTime(dt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromFormField<'r> for MyNaiveDateTime {
|
||||||
|
fn from_value(field: ValueField<'r>) -> form::Result<'r, Self> {
|
||||||
|
let dt = NaiveDateTime::parse_from_str(field.value, "%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
match dt {
|
||||||
|
Ok(parsed_dt) => Ok(MyNaiveDateTime(parsed_dt)),
|
||||||
|
Err(_) => Err(rocket::form::Error::validation("Invalid date/time format.").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromRow, Debug, Serialize, Deserialize, FromForm)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -23,9 +46,8 @@ pub struct User {
|
|||||||
pub is_cox: bool,
|
pub is_cox: bool,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
pub is_guest: bool,
|
pub is_guest: bool,
|
||||||
#[serde(default = "bool::default")]
|
|
||||||
pub deleted: bool,
|
pub deleted: bool,
|
||||||
pub last_access: Option<chrono::NaiveDateTime>,
|
pub last_access: Option<MyNaiveDateTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -65,8 +87,7 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
|
pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
|
||||||
sqlx::query_as!(
|
let row = sqlx::query!(
|
||||||
User,
|
|
||||||
"
|
"
|
||||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
||||||
FROM user
|
FROM user
|
||||||
@ -76,12 +97,22 @@ WHERE id like ?
|
|||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()?;
|
||||||
|
|
||||||
|
Some(User {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
pw: row.pw,
|
||||||
|
is_cox: row.is_cox,
|
||||||
|
is_admin: row.is_admin,
|
||||||
|
is_guest: row.is_guest,
|
||||||
|
deleted: row.deleted,
|
||||||
|
last_access: row.last_access.map(MyNaiveDateTime),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_name(db: &SqlitePool, name: &str) -> Option<Self> {
|
pub async fn find_by_name(db: &SqlitePool, name: &str) -> Option<Self> {
|
||||||
sqlx::query_as!(
|
let row = sqlx::query!(
|
||||||
User,
|
|
||||||
"
|
"
|
||||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
||||||
FROM user
|
FROM user
|
||||||
@ -91,7 +122,18 @@ WHERE name like ?
|
|||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()?;
|
||||||
|
|
||||||
|
Some(User {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
pw: row.pw,
|
||||||
|
is_cox: row.is_cox,
|
||||||
|
is_admin: row.is_admin,
|
||||||
|
is_guest: row.is_guest,
|
||||||
|
deleted: row.deleted,
|
||||||
|
last_access: row.last_access.map(MyNaiveDateTime),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn on_water(&self, db: &SqlitePool) -> bool {
|
pub async fn on_water(&self, db: &SqlitePool) -> bool {
|
||||||
@ -122,8 +164,7 @@ WHERE name like ?
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn all(db: &SqlitePool) -> Vec<Self> {
|
pub async fn all(db: &SqlitePool) -> Vec<Self> {
|
||||||
sqlx::query_as!(
|
sqlx::query!(
|
||||||
User,
|
|
||||||
"
|
"
|
||||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
||||||
FROM user
|
FROM user
|
||||||
@ -133,12 +174,23 @@ ORDER BY last_access DESC
|
|||||||
)
|
)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
.unwrap() //TODO: fixme
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| User {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
pw: row.pw,
|
||||||
|
is_cox: row.is_cox,
|
||||||
|
is_admin: row.is_admin,
|
||||||
|
is_guest: row.is_guest,
|
||||||
|
deleted: row.deleted,
|
||||||
|
last_access: row.last_access.map(MyNaiveDateTime),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cox(db: &SqlitePool) -> Vec<Self> {
|
pub async fn cox(db: &SqlitePool) -> Vec<Self> {
|
||||||
sqlx::query_as!(
|
sqlx::query!(
|
||||||
User,
|
|
||||||
"
|
"
|
||||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access
|
||||||
FROM user
|
FROM user
|
||||||
@ -148,7 +200,19 @@ ORDER BY last_access DESC
|
|||||||
)
|
)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
.unwrap() //TODO: fixme
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| User {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
pw: row.pw,
|
||||||
|
is_cox: row.is_cox,
|
||||||
|
is_admin: row.is_admin,
|
||||||
|
is_guest: row.is_guest,
|
||||||
|
deleted: row.deleted,
|
||||||
|
last_access: row.last_access.map(MyNaiveDateTime),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(db: &SqlitePool, name: &str, is_guest: bool) -> bool {
|
pub async fn create(db: &SqlitePool, name: &str, is_guest: bool) -> bool {
|
||||||
|
@ -50,25 +50,22 @@ async fn delete(db: &State<SqlitePool>, _admin: AdminUser, boat: i32) -> Flash<R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/boat", data = "<data>")]
|
#[post("/boat/<boat_id>", data = "<data>")]
|
||||||
async fn update(
|
async fn update(
|
||||||
db: &State<SqlitePool>,
|
db: &State<SqlitePool>,
|
||||||
data: Form<BoatToUpdate<'_>>,
|
data: Form<BoatToUpdate<'_>>,
|
||||||
|
boat_id: i32,
|
||||||
_admin: AdminUser,
|
_admin: AdminUser,
|
||||||
) -> Flash<Redirect> {
|
) -> Flash<Redirect> {
|
||||||
let boat = Boat::find_by_id(db, data.id).await;
|
let boat = Boat::find_by_id(db, boat_id).await;
|
||||||
let Some(boat) = boat else {
|
let Some(boat) = boat else {
|
||||||
return Flash::error(
|
return Flash::error(Redirect::to("/admin/boat"), "Boat does not exist!");
|
||||||
Redirect::to("/admin/boat"),
|
|
||||||
"Boat does not exist!",
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !boat.update(db, data.into_inner()).await {
|
match boat.update(db, data.into_inner()).await {
|
||||||
return Flash::error(Redirect::to("/admin/boat"), "Boat could not be updated!");
|
Ok(_) => Flash::success(Redirect::to("/admin/boat"), "Successfully updated boat"),
|
||||||
|
Err(e) => Flash::error(Redirect::to("/admin/boat"), e),
|
||||||
}
|
}
|
||||||
|
|
||||||
Flash::success(Redirect::to("/admin/boat"), "Successfully updated boat")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/boat/new", data = "<data>")]
|
#[post("/boat/new", data = "<data>")]
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
|
|
||||||
{% macro edit(boat, uuid) %}
|
{% macro edit(boat, uuid) %}
|
||||||
<form action="/admin/boat" data-filterable="true" method="post" class="bg-white p-3 rounded-md flex items-end md:items-center justify-between">
|
<form action="/admin/boat/{{ boat.id }}" data-filterable="true" method="post" class="bg-white p-3 rounded-md flex items-end md:items-center justify-between">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<input type="hidden" name="id" value="{{ boat.id }}"/>
|
<input type="hidden" name="id" value="{{ boat.id }}"/>
|
||||||
<div class="font-bold mb-1">{{ boat.name }}<br/></div>
|
<div class="font-bold mb-1">{{ boat.name }}<br/></div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user