Work on online classroom user creation
This commit is contained in:
parent
816e26b790
commit
adae0bd028
14
backend/src/controller/classroom/mod.rs
Normal file
14
backend/src/controller/classroom/mod.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use rocket::{http::Status, serde::json::Json};
|
||||
|
||||
use crate::{json_result::JsonResult, model::classroom_user::ClassroomPersonCreate};
|
||||
|
||||
#[options("/classroom/user")]
|
||||
pub fn create_user_options() -> Status { Status::Ok }
|
||||
|
||||
#[post("/classroom/user", format = "json", data = "<data>")]
|
||||
pub fn create_user(data: Json<ClassroomPersonCreate>) -> (Status, Json<JsonResult<()>>) {
|
||||
|
||||
|
||||
|
||||
(Status::InternalServerError, JsonResult::err("Error creando usuario".into()))
|
||||
}
|
@ -2,3 +2,4 @@ pub mod course;
|
||||
pub mod custom_label;
|
||||
pub mod person;
|
||||
pub mod register;
|
||||
pub mod classroom;
|
||||
|
@ -58,7 +58,9 @@ async fn rocket() -> _ {
|
||||
// Online classroom routes
|
||||
//
|
||||
online_classroom::connection,
|
||||
online_classroom::users::get_users,
|
||||
online_classroom::user::get_users,
|
||||
controller::classroom::create_user_options,
|
||||
controller::classroom::create_user,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
21
backend/src/model/classroom_user.rs
Normal file
21
backend/src/model/classroom_user.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ClassroomPerson {
|
||||
pub name: String,
|
||||
pub surname: String,
|
||||
pub username: String,
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ClassroomPersonCreate {
|
||||
pub person_email: String,
|
||||
pub person_expiration_date: String,
|
||||
pub person_id: i32,
|
||||
pub person_names: String,
|
||||
pub person_surnames: String,
|
||||
pub person_username: String,
|
||||
pub person_password: String,
|
||||
}
|
@ -3,3 +3,4 @@ pub mod custom_label;
|
||||
pub mod person;
|
||||
pub mod register;
|
||||
pub mod reniec_person;
|
||||
pub mod classroom_user;
|
||||
|
225
backend/src/online_classroom/create_user.rs
Normal file
225
backend/src/online_classroom/create_user.rs
Normal file
@ -0,0 +1,225 @@
|
||||
use scraper::{Selector, Html};
|
||||
|
||||
use crate::model::classroom_user::ClassroomPersonCreate;
|
||||
|
||||
use super::session::request;
|
||||
|
||||
|
||||
/// Creates an online classroom user
|
||||
pub async fn create(data: &ClassroomPersonCreate) -> Result<(), String> {
|
||||
let sec_token = get_form_sec_token().await?;
|
||||
|
||||
let body = get_request_body(
|
||||
&data.person_surnames,
|
||||
&data.person_names,
|
||||
&data.person_email,
|
||||
&data.person_username,
|
||||
&data.person_password,
|
||||
&data.person_expiration_date,
|
||||
&sec_token,
|
||||
);
|
||||
|
||||
Err("Not implemented".into())
|
||||
}
|
||||
|
||||
async fn get_form_sec_token() -> Result<String, String> {
|
||||
let creation_form = request("/main/admin/user_add.php".into()).await?;
|
||||
|
||||
let sec_token_selector = Selector::parse("#user_add_sec_token")
|
||||
.or_else(|err| Err(format!("Error creating sec_token selector: {:?}", err)))?;
|
||||
|
||||
let fragment = Html::parse_document(&creation_form);
|
||||
|
||||
let input_element = match fragment.select(&sec_token_selector).next() {
|
||||
Some(el) => el,
|
||||
None => return Err(format!("Error selecting sec_token element. Not found")),
|
||||
};
|
||||
|
||||
let sec_token_value = match input_element.value().attr("value") {
|
||||
Some(val) => val,
|
||||
None => return Err(format!("Error getting sec_token value from input. Not found")),
|
||||
};
|
||||
|
||||
Ok(sec_token_value.into())
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
surnames: &String,
|
||||
names: &String,
|
||||
email: &String,
|
||||
username: &String,
|
||||
password: &String,
|
||||
expiration_date: &String,
|
||||
sec_token: &String,
|
||||
) -> String {
|
||||
format!(
|
||||
r#"
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="lastname"
|
||||
|
||||
{surnames}
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="firstname"
|
||||
|
||||
{names}
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="official_code"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="email"
|
||||
|
||||
{email}
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="phone"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="picture"; filename=""
|
||||
Content-Type: application/octet-stream
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="username"
|
||||
|
||||
{username}
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="password[password_auto]"
|
||||
|
||||
0
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="password[password]"
|
||||
|
||||
{password}
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="status"
|
||||
|
||||
5
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="admin[platform_admin]"
|
||||
|
||||
0
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="language"
|
||||
|
||||
spanish
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="mail[send_mail]"
|
||||
|
||||
1
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="radio_expiration_date"
|
||||
|
||||
1
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="expiration_date"
|
||||
|
||||
{expiration_date} 23:59
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="active"
|
||||
|
||||
1
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_legal_accept"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_already_logged_in"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_update_type"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_rssfeeds"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_dashboard"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_timezone"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_mail_notify_invitation"
|
||||
|
||||
1
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_mail_notify_message"
|
||||
|
||||
1
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_mail_notify_group_message"
|
||||
|
||||
1
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_user_chat_status"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_google_calendar_url"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_captcha_blocked_until_date"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_skype"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_linkedin_url"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_request_for_legal_agreement_consent_removal_justification"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_request_for_delete_account_justification"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_request_for_legal_agreement_consent_removal"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="extra_request_for_delete_account"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="submit"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="_qf__user_add"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="MAX_FILE_SIZE"
|
||||
|
||||
536870912
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="picture_crop_result"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="picture_crop_image_base_64"
|
||||
|
||||
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="item_id"
|
||||
|
||||
0
|
||||
-----------------------------83919643214156711801978607619
|
||||
Content-Disposition: form-data; name="sec_token"
|
||||
|
||||
{sec_token}
|
||||
-----------------------------83919643214156711801978607619--
|
||||
"#
|
||||
)
|
||||
}
|
@ -5,7 +5,8 @@ use crate::json_result::JsonResult;
|
||||
use self::session::ensure_session;
|
||||
|
||||
mod session;
|
||||
pub mod users;
|
||||
pub mod user;
|
||||
pub mod create_user;
|
||||
|
||||
/// Tries to connect to the online classroom, and get a session cookie
|
||||
#[get("/classroom/connect")]
|
||||
|
@ -1,18 +1,11 @@
|
||||
use crate::json_result::JsonResult;
|
||||
use crate::{json_result::JsonResult, model::classroom_user::ClassroomPerson};
|
||||
|
||||
use super::session::request;
|
||||
use rocket::{http::Status, serde::json::Json};
|
||||
use scraper::{ElementRef, Html, Selector};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use urlencoding::encode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ClassroomPerson {
|
||||
name: String,
|
||||
surname: String,
|
||||
username: String,
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
|
||||
// Instead of requesting pages and managing session & cookies manually,
|
||||
// create a wrapper that:
|
||||
@ -127,7 +120,7 @@ fn get_person_data(td_vec: &Vec<ElementRef>) -> Result<ClassroomPerson, String>
|
||||
// Get the username
|
||||
let username = username_ref.inner_html();
|
||||
|
||||
// Parse userid from href
|
||||
// Parse user_id from href
|
||||
// format: https://testing.aulavirtual.eegsac.com/main/admin/user_information.php?user_id=1087
|
||||
// Get the position of 'user_id='
|
||||
let user_id_start = href_value.find("user_id=").ok_or("Error parsing user_id")? + 8;
|
@ -1,11 +1,68 @@
|
||||
import { createSignal } from "solid-js";
|
||||
import { Show, createSignal, onMount } from "solid-js";
|
||||
import { FilledButton } from "../components/FilledButton";
|
||||
import { UserPlusIcon } from "../icons/UserPlusIcon";
|
||||
import { MaterialInput } from "../components/MaterialInput";
|
||||
import { Person } from "../types/Person";
|
||||
import { LoadingStatus, backend, useLoading, wait } from "../utils/functions";
|
||||
import { JsonResult } from "../types/JsonResult";
|
||||
import { LoadingIcon } from "../icons/LoadingIcon";
|
||||
|
||||
export function ClassroomUserCreation() {
|
||||
export function ClassroomUserCreation(props: {person: Person}) {
|
||||
const [email, setEmail] = createSignal("yuli.palo.apaza@gmail.com");
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
const [username, setUsername] = createSignal("USERNAME");
|
||||
const {setError, status, setStatus} = useLoading();
|
||||
const [names, setNames] = createSignal(props.person.person_names);
|
||||
const [surnames, setSurnames] = createSignal(`${props.person.person_paternal_surname} ${props.person.person_maternal_surname}`);
|
||||
const [password, setPassword] = createSignal(props.person.person_dni);
|
||||
const [date, setDate] = createSignal("2023-10-03");
|
||||
|
||||
onMount(() => {
|
||||
// Setup initial values
|
||||
const [firstName, secondName] = props.person.person_names.split(" ");
|
||||
const paternalSurname = props.person.person_paternal_surname;
|
||||
const maternalSurname = props.person.person_maternal_surname;
|
||||
|
||||
const user = `${firstName[0]}${secondName[0] ?? ""}${paternalSurname}${maternalSurname[0]}`;
|
||||
|
||||
const next_date = new Date();
|
||||
next_date.setDate(next_date.getDate() + 60);
|
||||
setDate(next_date.toISOString().split("T")[0]);
|
||||
|
||||
setUsername(user.toUpperCase());
|
||||
});
|
||||
|
||||
const createClassroomUser = async() => {
|
||||
setStatus(LoadingStatus.Loading);
|
||||
console.log("creating...");
|
||||
|
||||
if (import.meta.env.DEV) await wait(1500);
|
||||
|
||||
backend.post<JsonResult<null>>("/api/classroom/user", {
|
||||
person_id: props.person.person_id,
|
||||
person_names: names(),
|
||||
person_surnames: surnames(),
|
||||
person_email: email(),
|
||||
person_username: username(),
|
||||
person_password: password(),
|
||||
person_expiration_date: date(),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
alert("Usuario creado con éxito");
|
||||
setStatus(LoadingStatus.Ok);
|
||||
} else {
|
||||
console.error(response.data);
|
||||
setError(response.data.Err.reason);
|
||||
setStatus(LoadingStatus.Error);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
setError(`Error fatal: ${err.message}`);
|
||||
alert(`Error fatal: ${err.message}`);
|
||||
setStatus(LoadingStatus.Error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="px-4 pb-4">
|
||||
@ -13,19 +70,69 @@ export function ClassroomUserCreation() {
|
||||
O cree un nuevo usuario:
|
||||
</p>
|
||||
|
||||
<form>
|
||||
<form
|
||||
onsubmit={(ev) => {
|
||||
ev.preventDefault();
|
||||
createClassroomUser();
|
||||
}}
|
||||
>
|
||||
<MaterialInput
|
||||
resourceName="Correo"
|
||||
value={email()}
|
||||
setValue={setEmail}
|
||||
disabled={loading()}
|
||||
resourceName="Apellidos"
|
||||
value={surnames()}
|
||||
setValue={setSurnames}
|
||||
disabled={status() === LoadingStatus.Loading}
|
||||
/>
|
||||
<MaterialInput
|
||||
resourceName="Nombres"
|
||||
value={names()}
|
||||
setValue={setNames}
|
||||
disabled={status() === LoadingStatus.Loading}
|
||||
/>
|
||||
<MaterialInput
|
||||
resourceName="Correo electrónico"
|
||||
value={email()}
|
||||
type="email"
|
||||
setValue={setEmail}
|
||||
disabled={status() === LoadingStatus.Loading}
|
||||
/>
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
<MaterialInput
|
||||
class="relative"
|
||||
resourceName="Usuario"
|
||||
value={username()}
|
||||
setValue={(v) => setUsername(v.toUpperCase())}
|
||||
disabled={status() === LoadingStatus.Loading}
|
||||
/>
|
||||
<MaterialInput
|
||||
class="relative"
|
||||
resourceName="Contraseña"
|
||||
value={password().toString()}
|
||||
setValue={setPassword}
|
||||
disabled={status() === LoadingStatus.Loading}
|
||||
/>
|
||||
</div>
|
||||
<MaterialInput
|
||||
resourceName="Fecha de expiracion"
|
||||
type="date"
|
||||
value={date()}
|
||||
setValue={setDate}
|
||||
disabled={status() === LoadingStatus.Loading}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<FilledButton class="ml-2">
|
||||
<UserPlusIcon fill="var(--c-on-primary)" class="mr-2 relative scale-150" size={16} />
|
||||
Crear usuario
|
||||
</FilledButton>
|
||||
<FilledButton type="submit" class="ml-2" disabled={status() === LoadingStatus.Loading}>
|
||||
<Show when={status() === LoadingStatus.Loading}>
|
||||
<LoadingIcon
|
||||
class="animate-spin relative mr-2"
|
||||
fill="var(--c-primary-container)"
|
||||
size={16}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={status() !== LoadingStatus.Loading}>
|
||||
<UserPlusIcon fill="var(--c-on-primary)" class="mr-2 relative scale-150" size={16} />
|
||||
</Show>
|
||||
Crear usuario
|
||||
</FilledButton>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
148
frontend/src/OnlineClassroom/ClassroomVinculation.tsx
Normal file
148
frontend/src/OnlineClassroom/ClassroomVinculation.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
import { ClassroomRegistrationUser } from "../types/ClassroomRegistrationUser";
|
||||
import { QuestionIcon } from "../icons/QuestionIcon";
|
||||
import { JsonResult } from "../types/JsonResult";
|
||||
import { XcircleIcon } from "../icons/XCircleIcon";
|
||||
import { LoadingStatus, backend, useLoading, wait } from "../utils/functions";
|
||||
import { LinkIcon } from "../icons/LinkIcon";
|
||||
import { For, Show, createSignal, onMount } from "solid-js";
|
||||
import { LoadingIcon } from "../icons/LoadingIcon";
|
||||
|
||||
type Status = "Init" | "Ok" | "Loading" | "Error";
|
||||
export function ClassroomVinculation(props: {person_surname: string, personId: number, onLink: (classroom_id: number) => void,}) {
|
||||
const [classroomUsers, setClassroomUsers] = createSignal<ClassroomRegistrationUser[]>([]);
|
||||
const [error, setError] = createSignal("");
|
||||
const [status, setStatus] = createSignal<Status>("Init");
|
||||
|
||||
const loadUsers = async() => {
|
||||
setStatus("Loading");
|
||||
const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/classroom/users/${encodeURIComponent(props.person_surname)}`);
|
||||
const json: JsonResult<Array<ClassroomRegistrationUser>> = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
setClassroomUsers(json.Ok);
|
||||
setStatus("Ok");
|
||||
} else {
|
||||
console.error("Error loading users", json);
|
||||
setError(json.Err.reason);
|
||||
setStatus("Error");
|
||||
}
|
||||
};
|
||||
|
||||
onMount(loadUsers);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p class="py-2 px-4">
|
||||
Vincule un usuario existente:
|
||||
</p>
|
||||
|
||||
<Show when={status() === "Loading"}>
|
||||
<div class="text-center h-12 scale-150">
|
||||
<LoadingIcon class="animate-spin" fill="var(--c-primary)" />
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={status() === "Ok"}>
|
||||
<Show when={classroomUsers().length === 0}>
|
||||
<div class="px-4 pb-2">
|
||||
<div class="text-center pt-6 pb-4">
|
||||
<QuestionIcon class="scale-[200%]" fill="var(--c-outline)" />
|
||||
</div>
|
||||
<p>No se encontraron usuarios en el aula virtual.</p>
|
||||
</div>
|
||||
</Show>
|
||||
<For each={classroomUsers()}>
|
||||
{(u) => (
|
||||
<ClassroomSingleUser
|
||||
user={u}
|
||||
personId={props.personId}
|
||||
onLink={props.onLink}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
|
||||
<Show when={status() === "Error"}>
|
||||
<div class="px-4 pb-2 text-c-error">
|
||||
<div class="text-center pt-6 pb-4">
|
||||
<XcircleIcon class="scale-[200%]" fill="var(--c-error)" />
|
||||
</div>
|
||||
<p>Error buscando usuarios: {error()}.</p>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ClassroomSingleUser(props: {
|
||||
user: ClassroomRegistrationUser,
|
||||
onLink: (classroom_id: number) => void,
|
||||
personId: number,
|
||||
}) {
|
||||
const {setError, status, setStatus} = useLoading();
|
||||
|
||||
const linkUser = async() => {
|
||||
setStatus(LoadingStatus.Loading);
|
||||
|
||||
if (import.meta.env.DEV) await wait(1500);
|
||||
|
||||
backend.put<JsonResult<null>>(
|
||||
"/api/person/link",
|
||||
{
|
||||
person_id: props.personId,
|
||||
person_classroom_id: parseInt(props.user.user_id, 10),
|
||||
},
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
setStatus(LoadingStatus.Ok);
|
||||
props.onLink(parseInt(props.user.user_id, 10));
|
||||
} else {
|
||||
console.error(response.data);
|
||||
setError(response.data.Err.reason);
|
||||
setStatus(LoadingStatus.Error);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(`Error fatal: ${err.message}`);
|
||||
console.error(err);
|
||||
setStatus(LoadingStatus.Error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
class="hover:bg-c-surface-variant hover:text-c-on-surface-variant transition-colors
|
||||
grid grid-cols-[auto_3rem] gap-4"
|
||||
>
|
||||
<div class="pl-4 py-2">
|
||||
{props.user.name} {props.user.surname}
|
||||
<br />
|
||||
<div class="grid grid-cols-[auto_10rem]">
|
||||
<span class="font-mono">
|
||||
{props.user.username}
|
||||
</span>
|
||||
<div>
|
||||
registrado:
|
||||
<span class="font-mono">
|
||||
??/??/????
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Vincular usuario"
|
||||
class="border-2 border-c-transparent hover:border-c-primary transition-colors rounded"
|
||||
onclick={linkUser}
|
||||
disabled={status() === LoadingStatus.Loading}
|
||||
>
|
||||
<LinkIcon fill="var(--c-primary)" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,15 +1,9 @@
|
||||
import { For, Show, createSignal, onMount } from "solid-js";
|
||||
import { Show, createSignal } from "solid-js";
|
||||
import { Search } from "../certs/Search";
|
||||
import { Person } from "../types/Person";
|
||||
import { FilledCard } from "../components/FilledCard";
|
||||
import { LinkIcon } from "../icons/LinkIcon";
|
||||
import { ClassroomUserCreation } from "./ClassroomUserCreation";
|
||||
import { ClassroomRegistrationUser } from "../types/ClassroomRegistrationUser";
|
||||
import { SpinnerGapIcon } from "../icons/SpinnerGapIcon";
|
||||
import { QuestionIcon } from "../icons/QuestionIcon";
|
||||
import { JsonResult } from "../types/JsonResult";
|
||||
import { XcircleIcon } from "../icons/XCircleIcon";
|
||||
import { LoadingStatus, backend, useLoading, wait } from "../utils/functions";
|
||||
import { ClassroomVinculation } from "./ClassroomVinculation";
|
||||
|
||||
type TabType = "Vinculate" | "Create";
|
||||
|
||||
@ -21,13 +15,13 @@ export function OnlineClassroom() {
|
||||
<Search setPerson={setPerson} />
|
||||
<div>
|
||||
|
||||
<Show when={person() !== null && person()!.person_classroom_id === undefined}>
|
||||
<Show when={person() !== null && person()!.person_classroom_id === null}>
|
||||
<ClassroomUser
|
||||
person={person()!}
|
||||
onLink={(classroom_id) => setPerson((p) => ({...p!, person_classroom_id: classroom_id}))}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={person() !== null && person()!.person_classroom_id !== undefined}>
|
||||
<Show when={person() !== null && person()!.person_classroom_id !== null}>
|
||||
<p>Person has classroom_id</p>
|
||||
</Show>
|
||||
|
||||
@ -58,7 +52,7 @@ function ClassroomUser(props: {person: Person, onLink: (classroom_id: number) =>
|
||||
/>
|
||||
</Show>
|
||||
<Show when={active() === "Create"}>
|
||||
<ClassroomUserCreation />
|
||||
<ClassroomUserCreation person={props.person} />
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@ -67,144 +61,6 @@ function ClassroomUser(props: {person: Person, onLink: (classroom_id: number) =>
|
||||
);
|
||||
}
|
||||
|
||||
type Status = "Init" | "Ok" | "Loading" | "Error";
|
||||
function ClassroomVinculation(props: {person_surname: string, personId: number, onLink: (classroom_id: number) => void,}) {
|
||||
const [classroomUsers, setClassroomUsers] = createSignal<ClassroomRegistrationUser[]>([]);
|
||||
const [error, setError] = createSignal("");
|
||||
const [status, setStatus] = createSignal<Status>("Init");
|
||||
|
||||
const loadUsers = async() => {
|
||||
setStatus("Loading");
|
||||
const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/classroom/users/${encodeURIComponent(props.person_surname)}`);
|
||||
const json: JsonResult<Array<ClassroomRegistrationUser>> = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
setClassroomUsers(json.Ok);
|
||||
setStatus("Ok");
|
||||
} else {
|
||||
console.error("Error loading users", json);
|
||||
setError(json.Err.reason);
|
||||
setStatus("Error");
|
||||
}
|
||||
};
|
||||
|
||||
onMount(loadUsers);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p class="py-2 px-4">
|
||||
Vincule un usuario existente:
|
||||
</p>
|
||||
|
||||
<Show when={status() === "Loading"}>
|
||||
<div class="text-center h-12 scale-150">
|
||||
<SpinnerGapIcon class="animate-spin" fill="var(--c-primary)" />
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={status() === "Ok"}>
|
||||
<Show when={classroomUsers().length === 0}>
|
||||
<div class="px-4 pb-2">
|
||||
<div class="text-center pt-6 pb-4">
|
||||
<QuestionIcon class="scale-[200%]" fill="var(--c-outline)" />
|
||||
</div>
|
||||
<p>No se encontraron usuarios en el aula virtual.</p>
|
||||
</div>
|
||||
</Show>
|
||||
<For each={classroomUsers()}>
|
||||
{(u) => (
|
||||
<ClassroomSingleUser
|
||||
user={u}
|
||||
personId={props.personId}
|
||||
onLink={props.onLink}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
|
||||
<Show when={status() === "Error"}>
|
||||
<div class="px-4 pb-2 text-c-error">
|
||||
<div class="text-center pt-6 pb-4">
|
||||
<XcircleIcon class="scale-[200%]" fill="var(--c-error)" />
|
||||
</div>
|
||||
<p>Error buscando usuarios: {error()}.</p>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ClassroomSingleUser(props: {
|
||||
user: ClassroomRegistrationUser,
|
||||
onLink: (classroom_id: number) => void,
|
||||
personId: number,
|
||||
}) {
|
||||
const {setError, status, setStatus} = useLoading();
|
||||
|
||||
const linkUser = async() => {
|
||||
setStatus(LoadingStatus.Loading);
|
||||
|
||||
if (import.meta.env.DEV) await wait(1500);
|
||||
|
||||
backend.put<JsonResult<null>>(
|
||||
"/api/person/link",
|
||||
{
|
||||
person_id: props.personId,
|
||||
person_classroom_id: parseInt(props.user.user_id, 10),
|
||||
},
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
setStatus(LoadingStatus.Ok);
|
||||
props.onLink(parseInt(props.user.user_id, 10));
|
||||
} else {
|
||||
console.error(response.data);
|
||||
setError(response.data.Err.reason);
|
||||
setStatus(LoadingStatus.Error);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(`Error fatal: ${err.message}`);
|
||||
console.error(err);
|
||||
setStatus(LoadingStatus.Error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
class="hover:bg-c-surface-variant hover:text-c-on-surface-variant transition-colors
|
||||
grid grid-cols-[auto_3rem] gap-4"
|
||||
>
|
||||
<div class="pl-4 py-2">
|
||||
{props.user.name} {props.user.surname}
|
||||
<br />
|
||||
<div class="grid grid-cols-[auto_10rem]">
|
||||
<span class="font-mono">
|
||||
{props.user.username}
|
||||
</span>
|
||||
<div>
|
||||
registrado:
|
||||
<span class="font-mono">
|
||||
??/??/????
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Vincular usuario"
|
||||
class="border-2 border-c-transparent hover:border-c-primary transition-colors rounded"
|
||||
onclick={linkUser}
|
||||
disabled={status() === LoadingStatus.Loading}
|
||||
>
|
||||
<LinkIcon fill="var(--c-primary)" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function ClassroomTabs(props: {active: TabType, setActive: (v: TabType) => void}) {
|
||||
const presetsClasses = () => ((props.active === "Vinculate") ? "font-bold border-c-primary" : "border-c-transparent");
|
||||
const manualClasses = () => ((props.active === "Create") ? "font-bold border-c-primary" : "border-c-transparent");
|
||||
|
@ -3,12 +3,14 @@ import { JSX } from "solid-js";
|
||||
export function FilledButton(props: {
|
||||
children?: Array<JSX.Element> | JSX.Element,
|
||||
class?: string,
|
||||
type?: "button" | "input",
|
||||
type?: "button" | "submit",
|
||||
disabled?: boolean,
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
class={`bg-c-primary text-c-on-primary px-4 py-2 rounded-full cursor-pointer mt-4
|
||||
disabled:opacity-50 disabled:cursor-not-allowed ${props.class}`}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
|
@ -3,19 +3,21 @@ export function MaterialInput(props: {
|
||||
disabled: boolean,
|
||||
value: string,
|
||||
setValue: (v: string) => void,
|
||||
type?: string,
|
||||
class?: string
|
||||
}) {
|
||||
let inputElement: HTMLInputElement | undefined;
|
||||
|
||||
return (
|
||||
<div class="relative my-6">
|
||||
<div class={props.class ?? "relative my-6"}>
|
||||
<input
|
||||
ref={inputElement}
|
||||
id="search-dni"
|
||||
id={props.resourceName.toLowerCase().replace(" ", "-")}
|
||||
class="bg-c-background text-c-on-background border-c-outline border-2 rounded px-2 py-1 w-full
|
||||
invalid:border-c-error invalid:text-c-error
|
||||
focus:border-c-primary outline-none font-mono
|
||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
type="text"
|
||||
type={props.type ?? "text"}
|
||||
placeholder={props.resourceName}
|
||||
value={props.value}
|
||||
required
|
||||
@ -24,7 +26,10 @@ export function MaterialInput(props: {
|
||||
/>
|
||||
|
||||
|
||||
<label for="search-dni" class="absolute -top-2 left-2 text-xs bg-c-surface px-1 select-none">
|
||||
<label
|
||||
for={props.resourceName.toLowerCase().replace(" ", "-")}
|
||||
class="absolute -top-2 left-2 text-xs bg-c-surface px-1 select-none"
|
||||
>
|
||||
{props.resourceName}
|
||||
</label>
|
||||
</div>
|
||||
|
@ -5,6 +5,6 @@ export type Person = {
|
||||
person_names: string
|
||||
person_paternal_surname: string
|
||||
person_maternal_surname: string
|
||||
person_classroom_id: number | undefined
|
||||
person_classroom_id: number | null
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user