diff --git a/migrations/20260412150652_add_dashboard_to_links.down.sql b/migrations/20260412150652_add_dashboard_to_links.down.sql new file mode 100644 index 0000000..a2ad9fa --- /dev/null +++ b/migrations/20260412150652_add_dashboard_to_links.down.sql @@ -0,0 +1 @@ +ALTER TABLE links DROP COLUMN dashboard; diff --git a/migrations/20260412150652_add_dashboard_to_links.up.sql b/migrations/20260412150652_add_dashboard_to_links.up.sql new file mode 100644 index 0000000..32a3ad1 --- /dev/null +++ b/migrations/20260412150652_add_dashboard_to_links.up.sql @@ -0,0 +1 @@ +ALTER TABLE links ADD COLUMN dashboard BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/src/app.rs b/src/app.rs index 42d22f2..226e0bf 100644 --- a/src/app.rs +++ b/src/app.rs @@ -59,13 +59,6 @@ pub fn Navigation() -> impl IntoView { } } -#[component] -pub fn Home() -> impl IntoView { - view! { -

Home

- } -} - #[component] pub fn NotFound() -> impl IntoView { view! { diff --git a/src/models/link.rs b/src/models/link.rs index d3d6825..87b1048 100644 --- a/src/models/link.rs +++ b/src/models/link.rs @@ -7,6 +7,7 @@ pub struct Link { pub name: String, pub icon: String, pub alternate_link: String, + pub dashboard: bool, position: i64, created_at: String, } @@ -18,13 +19,15 @@ impl Link { link: String, icon: String, alternate_link: String, + dashboard: bool, ) -> Result { sqlx::query!( - "INSERT INTO links (name, link, icon, alternate_link, position, created_at) VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(position) + 1, 1) FROM links lin), ?)", + "INSERT INTO links (name, link, icon, alternate_link, dashboard, position, created_at) VALUES (?, ?, ?, ?, ?, (SELECT COALESCE(MAX(position) + 1, 1) FROM links lin), ?)", name, link, icon, alternate_link, + dashboard, chrono::Local::now().naive_local(), ) .execute(crate::database::get_db()) @@ -34,7 +37,7 @@ impl Link { #[cfg(feature = "ssr")] pub async fn get_all() -> Result, sqlx::Error> { sqlx::query!( - "SELECT id, name, link, icon, alternate_link, position, created_at FROM links ORDER BY position" + "SELECT id, name, link, icon, alternate_link, dashboard, position, created_at FROM links ORDER BY position" ) .map(|x| Self { id: x.id, @@ -42,6 +45,7 @@ impl Link { link: x.link, icon: x.icon, alternate_link: x.alternate_link, + dashboard: x.dashboard != 0, position: x.position, created_at: x.created_at.format("%d/%m/%Y %H:%M").to_string(), }) @@ -55,7 +59,7 @@ impl Link { direction: String, ) -> Result { let link = sqlx::query!( - "SELECT id, name, link, icon, alternate_link, position, created_at FROM links WHERE id = ?", + "SELECT id, name, link, icon, alternate_link, dashboard, position, created_at FROM links WHERE id = ?", link_id ) .map(|x| Self { @@ -64,6 +68,7 @@ impl Link { link: x.link, icon: x.icon, alternate_link: x.alternate_link, + dashboard: x.dashboard != 0, position: x.position, created_at: x.created_at.format("%d/%m/%Y %H:%M").to_string(), }) @@ -108,13 +113,15 @@ impl Link { link: String, icon: String, alternate_link: String, + dashboard: bool, ) -> Result { sqlx::query!( - "UPDATE links SET name = ?, link = ?, icon = ?, alternate_link = ? WHERE id = ?", + "UPDATE links SET name = ?, link = ?, icon = ?, alternate_link = ?, dashboard = ? WHERE id = ?", name, link, icon, alternate_link, + dashboard, id ) .execute(crate::database::get_db()) diff --git a/src/routes/home.rs b/src/routes/home.rs new file mode 100644 index 0000000..7b07452 --- /dev/null +++ b/src/routes/home.rs @@ -0,0 +1,57 @@ +use leptos::*; +use leptos_meta::*; + +#[server(GetDashboardLinksAction, "/api", "GetJson")] +#[tracing::instrument] +pub async fn get_dashboard_links() -> Result, ServerFnError> { + crate::models::Link::get_all() + .await + .map(|links| links.into_iter().filter(|l| l.dashboard).collect()) + .map_err(|x| { + let err = format!("Error while fetching dashboard links: {x:?}"); + tracing::error!("{err}"); + ServerFnError::ServerError("Could not fetch dashboard links, try again later".into()) + }) +} + +#[component] +pub fn Home() -> impl IntoView { + let links = create_resource(|| (), |_| async move { get_dashboard_links().await }); + + view! { + + <ul class="flex gap-5 m-5 flex-wrap justify-center"> + <Suspense fallback=move || view! { <p>"Chargement..."</p> }> + <ErrorBoundary fallback=|_| { + view! { <p class="error-messages text-xs-center">"Something went wrong."</p> } + }> + {move || links.get().map(move |x| x.map(move |c| { + view! { + <For each=move || c.clone().into_iter().enumerate() + key=|(i, _)| *i + children=move |(_, link)| { + view! { <DashboardLink link /> } + }/> + } + }))} + </ErrorBoundary> + </Suspense> + </ul> + } +} + +#[component] +fn DashboardLink(link: crate::models::Link) -> impl IntoView { + view! { + <li class="w-44 lg:w-fit"> + <a class="bg-prim-light border-b border-third container flex flex-col lg:gap-2 lg:flex-row w-full item-center hover:scale-110 transition hover:bg-prim-lightest rounded-lg text-center hover:text-third px-5 py-4" + target="_blank" + href=link.link.clone()> + <img src=link.icon.clone() + alt=link.name.clone() + class="size-32 lg:size-10 object-cover overflow-hidden" /> + <span class="flex-1 text-2xl mt-2 lg:mt-0 flex justify-center items-center">{link.name.clone()}</span> + </a> + </li> + } +} diff --git a/src/routes/link.rs b/src/routes/link.rs index bab111c..aaeea51 100644 --- a/src/routes/link.rs +++ b/src/routes/link.rs @@ -13,8 +13,8 @@ pub async fn get_links() -> Result<Vec<crate::models::Link>, ServerFnError> { } #[server(LinkAction, "/api")] -pub async fn add_value(name: String, link: String, icon: String, alternate_link: String) -> Result<(), ServerFnError> { - crate::models::Link::insert(name, link, icon, alternate_link) +pub async fn add_value(name: String, link: String, icon: String, alternate_link: String, dashboard: bool) -> Result<(), ServerFnError> { + crate::models::Link::insert(name, link, icon, alternate_link, dashboard) .await .map(|_| ()) .map_err(|x| { @@ -120,7 +120,7 @@ fn Link<T: 'static + Clone, S: 'static>( ) -> impl IntoView { view! { <li class="w-44 lg:w-fit"> - <a class="bg-prim-light container flex flex-col lg:gap-2 lg:flex-row w-full item-center hover:scale-110 transition hover:bg-prim-lightest border-b hover:border-third border-transparent rounded-lg text-center hover:text-third px-5 py-4" + <a class=move || format!("bg-prim-light container flex flex-col lg:gap-2 lg:flex-row w-full item-center hover:scale-110 transition hover:bg-prim-lightest border-b hover:border-third rounded-lg text-center hover:text-third px-5 py-4 {}", if link.with(|x| x.dashboard) { "border-third" } else { "border-transparent" }) target="_blank" href={move || link.with(|x| if use_alternate.get() { x.alternate_link.clone() } else { x.link.clone() })}> <img src={move || link.with(|x| x.icon.to_string())} @@ -210,8 +210,9 @@ pub async fn update_link( link: String, icon: String, alternate_link: String, + dashboard: bool, ) -> Result<(), ServerFnError> { - crate::models::Link::update(id, name, link, icon, alternate_link) + crate::models::Link::update(id, name, link, icon, alternate_link, dashboard) .await .map(|_| ()) .map_err(|x| { @@ -235,6 +236,8 @@ fn LinkFormModal( let (icon, set_icon) = create_signal(link.as_ref().map(|x| x.icon.clone()).unwrap_or_default()); let (alternate_link, set_alternate_link) = create_signal(link.as_ref().map(|x| x.alternate_link.clone()).unwrap_or_default()); + let (dashboard, set_dashboard) = + create_signal(link.as_ref().map(|x| x.dashboard).unwrap_or(false)); let link_id = link.as_ref().map(|x| x.id.to_string()); view! { @@ -257,6 +260,8 @@ fn LinkFormModal( set_icon=set_icon alternate_link=alternate_link set_alternate_link=set_alternate_link + dashboard=dashboard + set_dashboard=set_dashboard /> <FormButtons set_show=set_show /> </ActionForm> @@ -273,6 +278,8 @@ fn LinkFormModal( set_icon=set_icon alternate_link=alternate_link set_alternate_link=set_alternate_link + dashboard=dashboard + set_dashboard=set_dashboard /> <FormButtons set_show=set_show /> </ActionForm> @@ -293,6 +300,8 @@ fn LinkFormFields( set_icon: WriteSignal<String>, alternate_link: ReadSignal<String>, set_alternate_link: WriteSignal<String>, + dashboard: ReadSignal<bool>, + set_dashboard: WriteSignal<bool>, ) -> impl IntoView { view! { <div> @@ -332,6 +341,15 @@ fn LinkFormFields( on:input=move |ev| set_icon.set(event_target_value(&ev)) class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" /> </div> + + <input type="hidden" name="dashboard" prop:value=move || dashboard.get().to_string() /> + <label class="flex items-center gap-2 mt-4 cursor-pointer select-none"> + <input type="checkbox" + prop:checked=move || dashboard.get() + on:change=move |ev| set_dashboard.set(event_target_checked(&ev)) + class="accent-third size-4 cursor-pointer" /> + "Tableau de bord" + </label> } } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index e98346e..d3eb725 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,5 +1,7 @@ +mod home; mod link; mod shutters; +pub use home::*; pub use link::*; pub use shutters::*;