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 custom_label;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
|
pub mod classroom;
|
||||||
|
@ -58,7 +58,9 @@ async fn rocket() -> _ {
|
|||||||
// Online classroom routes
|
// Online classroom routes
|
||||||
//
|
//
|
||||||
online_classroom::connection,
|
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 person;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
pub mod reniec_person;
|
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;
|
use self::session::ensure_session;
|
||||||
|
|
||||||
mod 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
|
/// Tries to connect to the online classroom, and get a session cookie
|
||||||
#[get("/classroom/connect")]
|
#[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 super::session::request;
|
||||||
use rocket::{http::Status, serde::json::Json};
|
use rocket::{http::Status, serde::json::Json};
|
||||||
use scraper::{ElementRef, Html, Selector};
|
use scraper::{ElementRef, Html, Selector};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use urlencoding::encode;
|
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,
|
// Instead of requesting pages and managing session & cookies manually,
|
||||||
// create a wrapper that:
|
// create a wrapper that:
|
||||||
@ -127,7 +120,7 @@ fn get_person_data(td_vec: &Vec<ElementRef>) -> Result<ClassroomPerson, String>
|
|||||||
// Get the username
|
// Get the username
|
||||||
let username = username_ref.inner_html();
|
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
|
// format: https://testing.aulavirtual.eegsac.com/main/admin/user_information.php?user_id=1087
|
||||||
// Get the position of 'user_id='
|
// Get the position of 'user_id='
|
||||||
let user_id_start = href_value.find("user_id=").ok_or("Error parsing user_id")? + 8;
|
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 { FilledButton } from "../components/FilledButton";
|
||||||
import { UserPlusIcon } from "../icons/UserPlusIcon";
|
import { UserPlusIcon } from "../icons/UserPlusIcon";
|
||||||
import { MaterialInput } from "../components/MaterialInput";
|
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 [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 (
|
return (
|
||||||
<div class="px-4 pb-4">
|
<div class="px-4 pb-4">
|
||||||
@ -13,19 +70,69 @@ export function ClassroomUserCreation() {
|
|||||||
O cree un nuevo usuario:
|
O cree un nuevo usuario:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form>
|
<form
|
||||||
|
onsubmit={(ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
createClassroomUser();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<MaterialInput
|
<MaterialInput
|
||||||
resourceName="Correo"
|
resourceName="Apellidos"
|
||||||
value={email()}
|
value={surnames()}
|
||||||
setValue={setEmail}
|
setValue={setSurnames}
|
||||||
disabled={loading()}
|
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">
|
<FilledButton type="submit" class="ml-2" disabled={status() === LoadingStatus.Loading}>
|
||||||
<UserPlusIcon fill="var(--c-on-primary)" class="mr-2 relative scale-150" size={16} />
|
<Show when={status() === LoadingStatus.Loading}>
|
||||||
Crear usuario
|
<LoadingIcon
|
||||||
</FilledButton>
|
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>
|
</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 { Search } from "../certs/Search";
|
||||||
import { Person } from "../types/Person";
|
import { Person } from "../types/Person";
|
||||||
import { FilledCard } from "../components/FilledCard";
|
import { FilledCard } from "../components/FilledCard";
|
||||||
import { LinkIcon } from "../icons/LinkIcon";
|
|
||||||
import { ClassroomUserCreation } from "./ClassroomUserCreation";
|
import { ClassroomUserCreation } from "./ClassroomUserCreation";
|
||||||
import { ClassroomRegistrationUser } from "../types/ClassroomRegistrationUser";
|
import { ClassroomVinculation } from "./ClassroomVinculation";
|
||||||
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";
|
|
||||||
|
|
||||||
type TabType = "Vinculate" | "Create";
|
type TabType = "Vinculate" | "Create";
|
||||||
|
|
||||||
@ -21,13 +15,13 @@ export function OnlineClassroom() {
|
|||||||
<Search setPerson={setPerson} />
|
<Search setPerson={setPerson} />
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<Show when={person() !== null && person()!.person_classroom_id === undefined}>
|
<Show when={person() !== null && person()!.person_classroom_id === null}>
|
||||||
<ClassroomUser
|
<ClassroomUser
|
||||||
person={person()!}
|
person={person()!}
|
||||||
onLink={(classroom_id) => setPerson((p) => ({...p!, person_classroom_id: classroom_id}))}
|
onLink={(classroom_id) => setPerson((p) => ({...p!, person_classroom_id: classroom_id}))}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</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>
|
<p>Person has classroom_id</p>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
@ -58,7 +52,7 @@ function ClassroomUser(props: {person: Person, onLink: (classroom_id: number) =>
|
|||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={active() === "Create"}>
|
<Show when={active() === "Create"}>
|
||||||
<ClassroomUserCreation />
|
<ClassroomUserCreation person={props.person} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</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}) {
|
function ClassroomTabs(props: {active: TabType, setActive: (v: TabType) => void}) {
|
||||||
const presetsClasses = () => ((props.active === "Vinculate") ? "font-bold border-c-primary" : "border-c-transparent");
|
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");
|
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: {
|
export function FilledButton(props: {
|
||||||
children?: Array<JSX.Element> | JSX.Element,
|
children?: Array<JSX.Element> | JSX.Element,
|
||||||
class?: string,
|
class?: string,
|
||||||
type?: "button" | "input",
|
type?: "button" | "submit",
|
||||||
|
disabled?: boolean,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
class={`bg-c-primary text-c-on-primary px-4 py-2 rounded-full cursor-pointer mt-4
|
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:opacity-50 disabled:cursor-not-allowed ${props.class}`}
|
||||||
|
disabled={props.disabled}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</button>
|
</button>
|
||||||
|
@ -3,19 +3,21 @@ export function MaterialInput(props: {
|
|||||||
disabled: boolean,
|
disabled: boolean,
|
||||||
value: string,
|
value: string,
|
||||||
setValue: (v: string) => void,
|
setValue: (v: string) => void,
|
||||||
|
type?: string,
|
||||||
|
class?: string
|
||||||
}) {
|
}) {
|
||||||
let inputElement: HTMLInputElement | undefined;
|
let inputElement: HTMLInputElement | undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative my-6">
|
<div class={props.class ?? "relative my-6"}>
|
||||||
<input
|
<input
|
||||||
ref={inputElement}
|
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
|
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
|
invalid:border-c-error invalid:text-c-error
|
||||||
focus:border-c-primary outline-none font-mono
|
focus:border-c-primary outline-none font-mono
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
type="text"
|
type={props.type ?? "text"}
|
||||||
placeholder={props.resourceName}
|
placeholder={props.resourceName}
|
||||||
value={props.value}
|
value={props.value}
|
||||||
required
|
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}
|
{props.resourceName}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,6 +5,6 @@ export type Person = {
|
|||||||
person_names: string
|
person_names: string
|
||||||
person_paternal_surname: string
|
person_paternal_surname: string
|
||||||
person_maternal_surname: string
|
person_maternal_surname: string
|
||||||
person_classroom_id: number | undefined
|
person_classroom_id: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user