use crate::i18n::use_i18n; use crate::pages::admin::{AdminContent, AdminDashboard, AdminRoles, AdminUsers}; use leptos::prelude::*; #[derive(Clone, Debug, PartialEq)] pub enum AdminSection { Dashboard, Users, Roles, Content, } impl AdminSection { pub fn route(&self) -> &'static str { match self { AdminSection::Dashboard => "/admin", AdminSection::Users => "/admin/users", AdminSection::Roles => "/admin/roles", AdminSection::Content => "/admin/content", } } pub fn title(&self, i18n: &crate::i18n::UseI18n) -> String { match self { AdminSection::Dashboard => i18n.t("admin.dashboard.title"), AdminSection::Users => i18n.t("admin.users.title"), AdminSection::Roles => i18n.t("admin.roles.title"), AdminSection::Content => i18n.t("admin.content.title"), } } pub fn icon(&self) -> &'static str { match self { AdminSection::Dashboard => { "M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586l-2 2V5H5v14h7v2H4a1 1 0 01-1-1V4z" } AdminSection::Users => { "M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" } AdminSection::Roles => { "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" } AdminSection::Content => { "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" } } } } #[component] pub fn AdminLayout( current_path: ReadSignal, #[prop(optional)] children: Option, ) -> impl IntoView { let i18n = use_i18n(); let current_section = Memo::new(move |_| { let pathname = current_path.get(); match pathname.as_str() { "/admin/users" => AdminSection::Users, "/admin/roles" => AdminSection::Roles, "/admin/content" => AdminSection::Content, _ => AdminSection::Dashboard, } }); view! {
// Sidebar

"Admin Dashboard"

// User info at bottom

"Admin User"

"admin@example.com"

// Main content
{match current_section.get() { AdminSection::Dashboard => view! { }.into_any(), AdminSection::Users => view! { }.into_any(), AdminSection::Roles => view! { }.into_any(), AdminSection::Content => view! { }.into_any(), }} {children.map(|c| c()).unwrap_or_else(|| view! {}.into_any())}
} } #[component] fn AdminNavItem( section: AdminSection, current_section: Memo, i18n: crate::i18n::UseI18n, ) -> impl IntoView { let section_route = section.route(); let section_icon = section.icon(); let section_title = section.title(&i18n); let is_current = Memo::new(move |_| current_section.get() == section); view! { {section_title} } } #[component] pub fn AdminBreadcrumb(current_path: ReadSignal) -> impl IntoView { let i18n = use_i18n(); let breadcrumb_items = Memo::new(move |_| { let pathname = current_path.get(); let mut items = vec![("Admin".to_string(), "/admin".to_string())]; match pathname.as_str() { "/admin/users" => items.push(( i18n.clone().t("admin.users.title"), "/admin/users".to_string(), )), "/admin/roles" => items.push(( i18n.clone().t("admin.roles.title"), "/admin/roles".to_string(), )), "/admin/content" => items.push(( i18n.clone().t("admin.content.title"), "/admin/content".to_string(), )), _ => {} } items }); view! { } } #[component] pub fn AdminHeader( #[prop(optional)] title: Option, #[prop(optional)] subtitle: Option, #[prop(optional)] actions: Option, ) -> impl IntoView { let title_text = title.unwrap_or_else(|| "Admin".to_string()); let subtitle_text = subtitle.unwrap_or_default(); let has_subtitle = !subtitle_text.is_empty(); view! {

{title_text}

{subtitle_text.clone()}
{actions.map(|a| a()).unwrap_or_else(|| view! {}.into_any())}
} } #[component] pub fn AdminCard( #[prop(optional)] title: Option, #[prop(optional)] class: Option, children: Children, ) -> impl IntoView { let class_str = class.unwrap_or_default(); let title_str = title.unwrap_or_default(); let has_title = !title_str.is_empty(); view! {

{title_str.clone()}

{children()}
} } #[component] pub fn AdminEmptyState( #[prop(optional)] icon: Option, #[prop(optional)] title: Option, #[prop(optional)] description: Option, #[prop(optional)] action: Option, ) -> impl IntoView { let icon_str = icon.unwrap_or_default(); let title_str = title.unwrap_or_else(|| "No items".to_string()); let description_str = description.unwrap_or_default(); let has_icon = !icon_str.is_empty(); let has_description = !description_str.is_empty(); view! {

{title_str}

{description_str.clone()}

{action.map(|a| a()).unwrap_or_else(|| view! {}.into_any())}
} }