User management completed. Leptos upgraded to 0.5.0.
							parent
							
								
									a7188e8153
								
							
						
					
					
						commit
						e7af2d402d
					
				@ -1,44 +1,18 @@
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use crate::locales::trl;
 | 
			
		||||
use crate::pages::company_info::CompanyInfo;
 | 
			
		||||
use crate::pages::users::Users;
 | 
			
		||||
 | 
			
		||||
#[component]
 | 
			
		||||
pub fn Settings() -> impl IntoView {
 | 
			
		||||
    view! {
 | 
			
		||||
        <h1>{trl("Settings")}</h1>
 | 
			
		||||
        <div class="row mb-5">
 | 
			
		||||
            <div class="col-md-6 col-lg-4 mb-3">
 | 
			
		||||
            <div class="col-md">
 | 
			
		||||
                <CompanyInfo/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-md-6 col-lg-4 mb-3">
 | 
			
		||||
              <div class="card h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                  <h5 class="card-title">"Card title"</h5>
 | 
			
		||||
                  <h6 class="card-subtitle text-muted">"Support card subtitle"</h6>
 | 
			
		||||
                </div>
 | 
			
		||||
                <img class="img-fluid" src="../assets/img/elements/13.jpg" alt="Card image cap" />
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                  <p class="card-text">"Bear claw sesame snaps gummies chocolate."</p>
 | 
			
		||||
                  <a href="javascript:void(0);" class="card-link">"Card link"</a>
 | 
			
		||||
                  <a href="javascript:void(0);" class="card-link">"Another link"</a>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-md-6 col-lg-4 mb-3">
 | 
			
		||||
              <div class="card h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                  <h5 class="card-title">"Card title"</h5>
 | 
			
		||||
                  <h6 class="card-subtitle text-muted">"Support card subtitle"</h6>
 | 
			
		||||
                  <img
 | 
			
		||||
                    class="img-fluid d-flex mx-auto my-4"
 | 
			
		||||
                    src="../assets/img/elements/4.jpg"
 | 
			
		||||
                    alt="Card image cap"
 | 
			
		||||
                  />
 | 
			
		||||
                  <p class="card-text">"Bear claw sesame snaps gummies chocolate."</p>
 | 
			
		||||
                  <a href="javascript:void(0);" class="card-link">Card link</a>
 | 
			
		||||
                  <a href="javascript:void(0);" class="card-link">Another link</a>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            <div class="col-md">
 | 
			
		||||
                <Users/>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,17 @@
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use crate::backend::data::User;
 | 
			
		||||
use crate::backend::user::DeleteUser;
 | 
			
		||||
use crate::components::data_form::QuestionDialog;
 | 
			
		||||
use crate::components::modal_box::DialogOpener;
 | 
			
		||||
 | 
			
		||||
#[component]
 | 
			
		||||
pub fn user_delete(user: ReadSignal<User>, opener: DialogOpener) -> impl IntoView {
 | 
			
		||||
    let del_user = create_server_action::<DeleteUser>();
 | 
			
		||||
 | 
			
		||||
    view! {
 | 
			
		||||
        <QuestionDialog opener=opener action=del_user title="Delete user">
 | 
			
		||||
            <input type="hidden" prop:value={move || user.get().id()} name="id"/>
 | 
			
		||||
            <div>"Are you sure you want to delete user "{move || user.get().full_name}"?"</div>
 | 
			
		||||
        </QuestionDialog>
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,101 @@
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use crate::backend::user::CreateUser;
 | 
			
		||||
use crate::components::data_form::DataForm;
 | 
			
		||||
use crate::components::modal_box::DialogOpener;
 | 
			
		||||
 | 
			
		||||
#[component]
 | 
			
		||||
pub fn user_edit(opener: DialogOpener) -> impl IntoView {
 | 
			
		||||
    let create_usr = create_server_action::<CreateUser>();
 | 
			
		||||
    view! {
 | 
			
		||||
        <DataForm opener=opener action=create_usr title="Create user">
 | 
			
		||||
                //<input type="hidden" value={move || company.get().id()} name="company[id]"/>
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col mb-3">
 | 
			
		||||
                        <label  for="username" class="form-label">"Username"</label>
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="text"
 | 
			
		||||
                            id="username"
 | 
			
		||||
                            class="form-control"
 | 
			
		||||
                            placeholder="Enter username"
 | 
			
		||||
                            prop:value={move || opener.empty()}
 | 
			
		||||
                            name="user[login]"
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col mb-3">
 | 
			
		||||
                        <label  for="password" class="form-label">"Password"</label>
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="password"
 | 
			
		||||
                            id="password"
 | 
			
		||||
                            class="form-control"
 | 
			
		||||
                            prop:value={move || opener.empty()}
 | 
			
		||||
                            name="user[password]"
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col mb-3">
 | 
			
		||||
                        <label  for="passwordVer" class="form-label">"Verify password"</label>
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="password"
 | 
			
		||||
                            id="passwordVer"
 | 
			
		||||
                            class="form-control"
 | 
			
		||||
                            prop:value={move || opener.empty()}
 | 
			
		||||
                            name="user[password_ver]"
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col mb-3">
 | 
			
		||||
                        <label  for="fullName" class="form-label">"Full name"</label>
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="text"
 | 
			
		||||
                            id="fullName"
 | 
			
		||||
                            class="form-control"
 | 
			
		||||
                            placeholder="Enter full name"
 | 
			
		||||
                            prop:value={move || opener.empty()}
 | 
			
		||||
                            name="user[full_name]"
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col mb-3">
 | 
			
		||||
                        <label  for="email" class="form-label">"Email"</label>
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="text"
 | 
			
		||||
                            id="email"
 | 
			
		||||
                            class="form-control"
 | 
			
		||||
                            placeholder="Enter email"
 | 
			
		||||
                            prop:value={move || opener.empty()}
 | 
			
		||||
                            name="user[email]"
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col mb-3">
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="checkbox"
 | 
			
		||||
                            id="admin"
 | 
			
		||||
                            class="form-check-input"
 | 
			
		||||
                            prop:checked={move || opener.not_checked()}
 | 
			
		||||
                            name="user[admin]"
 | 
			
		||||
                        />
 | 
			
		||||
                        <label  for="admin" class="form-label">"Admin"</label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col mb-3">
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="checkbox"
 | 
			
		||||
                            id="getEmails"
 | 
			
		||||
                            class="form-check-input"
 | 
			
		||||
                            prop:checked={move || opener.not_checked()}
 | 
			
		||||
                            name="user[get_emails]"
 | 
			
		||||
                        />
 | 
			
		||||
                        <label  for="geEmails" class="form-label">"Get emails"</label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </DataForm>
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,116 @@
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use crate::backend::data::{ApiResponse, User};
 | 
			
		||||
use crate::backend::user::get_users;
 | 
			
		||||
use crate::components::modal_box::DialogOpener;
 | 
			
		||||
use crate::components::user_menu::MenuOpener;
 | 
			
		||||
use crate::locales::trl;
 | 
			
		||||
use crate::pages::change_pwd::ChangePassword;
 | 
			
		||||
use crate::pages::profile_edit::ProfileEdit;
 | 
			
		||||
use crate::pages::user_delete::UserDelete;
 | 
			
		||||
use crate::pages::user_edit::UserEdit;
 | 
			
		||||
 | 
			
		||||
#[component]
 | 
			
		||||
pub fn users() -> impl IntoView {
 | 
			
		||||
    let editor = DialogOpener::new();
 | 
			
		||||
    let profile_editor = DialogOpener::new();
 | 
			
		||||
    let pwd_dialog = DialogOpener::new();
 | 
			
		||||
    let delete_dialog = DialogOpener::new();
 | 
			
		||||
    let users = create_blocking_resource(
 | 
			
		||||
        move || editor.visible() || profile_editor.visible() || delete_dialog.visible(), move |_| {get_users()});
 | 
			
		||||
    let (usr, set_usr) = create_signal::<Vec<User>>(vec![]);
 | 
			
		||||
    let (profile, set_profile) = create_signal(User::default());
 | 
			
		||||
 | 
			
		||||
    view! {
 | 
			
		||||
        <UserEdit opener=editor/>
 | 
			
		||||
        <ProfileEdit user=profile opener=profile_editor/>
 | 
			
		||||
        <ChangePassword opener=pwd_dialog user=profile/>
 | 
			
		||||
        <UserDelete opener=delete_dialog user=profile/>
 | 
			
		||||
        <div class="card mb-3">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
            <h5 class="card-title"><i class="bx bx-user"></i>" "{trl("Users")}</h5>
 | 
			
		||||
            <Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
 | 
			
		||||
            <table class="table card-table">
 | 
			
		||||
                <thead>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <th>{trl("Username")}</th>
 | 
			
		||||
                        <th>{trl("Full name")}</th>
 | 
			
		||||
                        <th>{trl("Admin")}</th>
 | 
			
		||||
                        <th>{trl("Actions")}</th>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
            {move || {
 | 
			
		||||
                users.get().map(|u| match u {
 | 
			
		||||
                    Err(e) => {
 | 
			
		||||
                        let err = if e.to_string().contains("403") {
 | 
			
		||||
                            "Only admin can edit users".to_string()
 | 
			
		||||
                        } else {
 | 
			
		||||
                            e.to_string()
 | 
			
		||||
                        };
 | 
			
		||||
                        view! {<tbody class="table-border-bottom-0">
 | 
			
		||||
                        <tr><td colspan=4>{trl(&err)}</td></tr></tbody>}}
 | 
			
		||||
                    Ok(u) => {
 | 
			
		||||
                        match u {
 | 
			
		||||
                            ApiResponse::Data(u) => {
 | 
			
		||||
                                set_usr.update(|users| *users = u.clone());
 | 
			
		||||
                                view! {<tbody class="table-border-bottom-0">
 | 
			
		||||
                                    <For each=move || usr.get()
 | 
			
		||||
                                        key=|user| user.id()
 | 
			
		||||
                                        children=move |user: User| {
 | 
			
		||||
                                        let menu = MenuOpener::new();
 | 
			
		||||
                                        let user_profile = user.clone();
 | 
			
		||||
                                        let user_passwd = user.clone();
 | 
			
		||||
                                        let user_delete = user.clone();
 | 
			
		||||
                                        view! {
 | 
			
		||||
                                            <tr>
 | 
			
		||||
                                                <td>{&user.login}</td>
 | 
			
		||||
                                                <td>{&user.full_name.unwrap_or("".to_string())}</td>
 | 
			
		||||
                                                <td>{if user.admin {view! {<i class="bx bx-check"></i>}}
 | 
			
		||||
                                                    else {view! {<i></i>}}}</td>
 | 
			
		||||
                                                <td>
 | 
			
		||||
                                                <div class="dropdown">
 | 
			
		||||
                                                    <button type="button" class="btn p-0 dropdown-toggle hide-arrow"
 | 
			
		||||
                                                        on:click=move |_| menu.toggle()>
 | 
			
		||||
                                                        <i class="bx bx-dots-vertical-rounded"></i>
 | 
			
		||||
                                                    </button>
 | 
			
		||||
                                                    <div class={move || if menu.visible() {"dropdown-menu show"} else {"dropdown-menu"} }
 | 
			
		||||
                                                    style="position: absolute; insert: 0px 0px auto; margin: 0px; transform: translate3d(-160px, 0px, 0px);"
 | 
			
		||||
                                                    on:mouseleave=move |_| menu.toggle()>
 | 
			
		||||
                                                        <a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
 | 
			
		||||
                                                                set_profile.set(user_profile.clone());
 | 
			
		||||
                                                                profile_editor.show();
 | 
			
		||||
                                                            }>
 | 
			
		||||
                                                            <i class="bx bx-edit-alt me-1"></i> {trl("Edit")}</a>
 | 
			
		||||
                                                        <a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
 | 
			
		||||
                                                                set_profile.set(user_passwd.clone());
 | 
			
		||||
                                                                pwd_dialog.show();
 | 
			
		||||
                                                            }>
 | 
			
		||||
                                                            <i class="bx bx-lock me-1"></i> {trl("Change password")}</a>
 | 
			
		||||
                                                        <a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
 | 
			
		||||
                                                                set_profile.set(user_delete.clone());
 | 
			
		||||
                                                                delete_dialog.show();
 | 
			
		||||
                                                            }>
 | 
			
		||||
                                                            <i class="bx bx-trash me-1"></i> {trl("Delete")}</a>
 | 
			
		||||
                                                    </div>
 | 
			
		||||
                                                </div>
 | 
			
		||||
                                                </td>
 | 
			
		||||
                                            </tr>
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }/></tbody>
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            ApiResponse::Error(e) => {view! {<tbody class="table-border-bottom-0">
 | 
			
		||||
                                <tr><td colspan=4>{trl(&e)}</td></tr></tbody>}}
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            }}
 | 
			
		||||
            </table>
 | 
			
		||||
            </Transition>
 | 
			
		||||
            <a href="#" class="card-link" on:click=move |_| editor.show()>
 | 
			
		||||
                <i class="bx bx-plus-circle fs-4 lh-0"></i>
 | 
			
		||||
            </a>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue