2025-02-17 01:36:01 -05:00

252 lines
7.8 KiB
Rust

use leptos::{either::Either, prelude::*};
use serde::{Deserialize, Serialize};
#[cfg(feature = "ssr")]
use {crate::db::user, leptos::server_fn::error::NoCustomError, sqlx::SqlitePool};
use super::BeaconResources;
#[derive(Clone, Serialize, Deserialize)]
pub struct Category {
pub category_id: i64,
pub category_name: String,
}
#[server(prefix = "/api/categories", endpoint = "get")]
pub async fn get_categories() -> Result<Vec<Category>, ServerFnError> {
let user = user::get_auth_session().await?;
if user.is_none() {
return Err(ServerFnError::<NoCustomError>::ServerError(
"You are not signed in!".to_owned(),
));
}
let db = crate::db::get_db()?;
Ok(sqlx::query_as!(Category, "SELECT * FROM beacon_category")
.fetch_all(&db)
.await?)
}
#[server(prefix = "/api/categories", endpoint = "add")]
pub async fn add_category(name: String) -> Result<(), ServerFnError> {
let user = user::get_auth_session().await?;
if user.is_none() {
return Err(ServerFnError::<NoCustomError>::ServerError(
"You are not signed in!".to_owned(),
));
}
let db = crate::db::get_db()?;
sqlx::query!(
"INSERT INTO beacon_category (category_name) VALUES (?)",
name
)
.execute(&db)
.await?;
Ok(())
}
#[server(prefix = "/api/categories", endpoint = "remove")]
pub async fn remove_category(id: i64) -> Result<(), ServerFnError> {
let user = user::get_auth_session().await?;
if user.is_none() {
return Err(ServerFnError::<NoCustomError>::ServerError(
"You are not signed in!".to_owned(),
));
}
let db = crate::db::get_db()?;
sqlx::query!("DELETE FROM beacon_category WHERE category_id = ?", id)
.execute(&db)
.await?;
Ok(())
}
#[server(prefix = "/api/categories", endpoint = "rename")]
pub async fn rename_category(id: i64, name: String) -> Result<(), ServerFnError> {
let user = user::get_auth_session().await?;
if user.is_none() {
return Err(ServerFnError::<NoCustomError>::ServerError(
"You are not signed in!".to_owned(),
));
}
let db = crate::db::get_db()?;
sqlx::query!(
"UPDATE beacon_category SET category_name = ? WHERE category_id = ?",
name,
id
)
.execute(&db)
.await?;
Ok(())
}
#[component]
pub fn CategoriesView() -> impl IntoView {
let BeaconResources {
add_category,
categories,
..
} = expect_context();
view! {
<div class="categories">
<h2>"Categories"</h2>
<p>
"Categories are an optional organization method that can be used to group beacons. Beacons can be assigned to multiple categories"
</p>
<ActionForm action=add_category>
<fieldset>
{move || match add_category.value().get() {
Some(Ok(_)) => Either::Right(()),
None => Either::Right(()),
Some(Err(e)) => Either::Left(view! {
<p>"Error creating category:"</p>
<p>{format!("{e:?}")}</p>
})
}}
<legend>"Add a new beacon category"</legend>
<label>"Category name"</label>
<input name="name"/>
<div></div>
<input type="submit" value="Submit" disabled=move||add_category.pending() />
</fieldset>
</ActionForm>
<Suspense fallback=|| view! { <p>"Loading..."</p> }>
{ move || match categories.get() {
Some(inner) => Either::Right(match inner {
Err(e) => Either::Left(view! {
<p>"There was an error loading categories:"</p>
<p>{format!("error: {e:?}")}</p>
}),
Ok(cs) => Either::Right(view! {
<DisplayCategories categories=cs/>
})
}),
None => Either::Left(view! {
<p>"Loading..."</p>
})
}}
</Suspense>
</div>
}
}
#[component]
fn DisplayCategories(categories: Vec<Category>) -> impl IntoView {
let BeaconResources {
remove_category,
rename_category,
..
} = expect_context();
let (target_rename_id, set_target_rename_id) = signal(0);
let target_rename_name = RwSignal::new("".to_owned());
let dialog_ref = NodeRef::<leptos::html::Dialog>::new();
let categories_view = categories
.iter()
.map(|category| {
view! {
<li>
{category.category_id}
": "
{category.category_name.clone()}
<button
on:click={
let id = category.category_id;
let name = category.category_name.clone();
let set_target_rename_id = set_target_rename_id.clone();
let target_rename_name = target_rename_name.clone();
move |_| {
set_target_rename_id(id);
target_rename_name.set(name.clone());
if let Some(dialog) = dialog_ref.get() {
let _ = dialog.show_modal();
}
}
}
>
"rename"
</button>
<button
on:click={
let id = category.category_id;
move |_| {
remove_category.dispatch(RemoveCategory { id });
}
}
>
"delete"
</button>
</li>
}
})
.collect_view();
Effect::watch(
move || {
(
rename_category.version().get(),
rename_category.value().get(),
dialog_ref.get(),
)
},
move |(_, res, dialog_ref), _, _| {
if let (Some(Ok(())), Some(dialog)) = (res, dialog_ref) {
let _ = dialog.close();
}
},
false,
);
view! {
<dialog node_ref=dialog_ref>
<ActionForm action=rename_category>
{move || match rename_category.value().get() {
Some(Ok(_)) => Either::Right(()),
None => Either::Right(()),
Some(Err(e)) => Either::Left(view! {
<p>"Failed to rename category:"</p>
<p>{format!("{e:?}")}</p>
})
}}
<fieldset>
<legend>"Rename category"</legend>
<label>"Name"</label>
<input name="name" bind:value=target_rename_name />
<input type="hidden" name="id" value=move||target_rename_id.get() />
<div></div>
<input type="submit" value="Submit" disabled=move||rename_category.pending()/>
</fieldset>
</ActionForm>
</dialog>
{move || match remove_category.value().get() {
Some(Ok(_)) => Either::Right(()),
None => Either::Right(()),
Some(Err(e)) => Either::Left(view! {
<p>"Failed to remove category:"</p>
<p>{format!("{e:?}")}</p>
})
}}
<ul>
{categories_view}
</ul>
}
}