[Classroom] Show & update user's account expiration date
This commit is contained in:
parent
d30a75697b
commit
34b6aab6b7
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@ -2,3 +2,4 @@ target
|
|||||||
.env
|
.env
|
||||||
aulavirtual
|
aulavirtual
|
||||||
scraps
|
scraps
|
||||||
|
request-logs
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::online_classroom::register_course::register_course;
|
use crate::online_classroom::register_course::register_course;
|
||||||
|
use crate::online_classroom::update_expiration_date::update_expiration_date;
|
||||||
use crate::{
|
use crate::{
|
||||||
json_result::JsonResult,
|
json_result::JsonResult,
|
||||||
model::{
|
model::{
|
||||||
@ -47,3 +48,40 @@ pub async fn register_course_contr(
|
|||||||
Err(err) => return (Status::InternalServerError, JsonResult::err(err)),
|
Err(err) => return (Status::InternalServerError, JsonResult::err(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/classroom/expiration_date/<user_id>")]
|
||||||
|
pub async fn get_expiration_date(user_id: i32) -> (Status, Json<JsonResult<String>>) {
|
||||||
|
match crate::online_classroom::get_expiration_date::get_expiration_date(user_id).await {
|
||||||
|
Ok(date) => return (Status::Ok, JsonResult::ok(date)),
|
||||||
|
Err(err) => return (Status::InternalServerError, JsonResult::err(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set expiration date
|
||||||
|
//
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct ExpirationDate {
|
||||||
|
pub date: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[options("/classroom/expiration_date/<_u>")]
|
||||||
|
pub async fn set_expiration_date_options(_u: i32) -> Status {
|
||||||
|
Status::Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put(
|
||||||
|
"/classroom/expiration_date/<user_id>",
|
||||||
|
format = "json",
|
||||||
|
data = "<data>"
|
||||||
|
)]
|
||||||
|
pub async fn set_expiration_date(
|
||||||
|
user_id: i32,
|
||||||
|
data: Json<ExpirationDate>,
|
||||||
|
) -> (Status, Json<JsonResult<()>>) {
|
||||||
|
match update_expiration_date(user_id, data.date.clone()).await {
|
||||||
|
Ok(()) => return (Status::Ok, JsonResult::ok(())),
|
||||||
|
Err(err) => return (Status::InternalServerError, JsonResult::err(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -64,6 +64,9 @@ async fn rocket() -> _ {
|
|||||||
controller::classroom::get_courses,
|
controller::classroom::get_courses,
|
||||||
controller::classroom::register_course_contr_options,
|
controller::classroom::register_course_contr_options,
|
||||||
controller::classroom::register_course_contr,
|
controller::classroom::register_course_contr,
|
||||||
|
controller::classroom::get_expiration_date,
|
||||||
|
controller::classroom::set_expiration_date_options,
|
||||||
|
controller::classroom::set_expiration_date,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
23
backend/src/online_classroom/get_expiration_date.rs
Normal file
23
backend/src/online_classroom/get_expiration_date.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use scraper::{Html, Selector};
|
||||||
|
|
||||||
|
use super::session::request;
|
||||||
|
|
||||||
|
/// Gets the expiration date of the user with the given id, in format "YYYY-MM-DD HH:MM"
|
||||||
|
pub async fn get_expiration_date(user_id: i32) -> Result<String, String> {
|
||||||
|
let html = request(format!("/main/admin/user_edit.php?user_id={}", user_id)).await?;
|
||||||
|
|
||||||
|
let fragment = Html::parse_document(&html);
|
||||||
|
|
||||||
|
let date_selector = Selector::parse("#expiration_date_alt")
|
||||||
|
.or_else(|err| Err(format!("Error creating date selector: {:?}", err)))?;
|
||||||
|
|
||||||
|
let input_el = match fragment.select(&date_selector).next() {
|
||||||
|
Some(el) => el,
|
||||||
|
None => return Err(format!("#expiration_date_alt not found")),
|
||||||
|
};
|
||||||
|
|
||||||
|
match input_el.value().attr("value") {
|
||||||
|
Some(date) => Ok(String::from(date)),
|
||||||
|
None => return Err(format!("value attribute not found in #expiration_date_alt")),
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,10 @@ use self::session::ensure_session;
|
|||||||
|
|
||||||
pub mod create_user;
|
pub mod create_user;
|
||||||
pub mod get_courses;
|
pub mod get_courses;
|
||||||
|
pub mod get_expiration_date;
|
||||||
pub mod register_course;
|
pub mod register_course;
|
||||||
mod session;
|
mod session;
|
||||||
|
pub mod update_expiration_date;
|
||||||
pub mod user;
|
pub mod 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
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use chrono::{DateTime, Local, TimeZone, Utc};
|
use chrono::{DateTime, Local};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use urlencoding::encode;
|
use urlencoding::encode;
|
||||||
@ -17,6 +17,47 @@ lazy_static! {
|
|||||||
static ref SESSION_TIME: RwLock<u64> = RwLock::new(0);
|
static ref SESSION_TIME: RwLock<u64> = RwLock::new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Make a post request with a body and content type
|
||||||
|
pub async fn classroom_post_redirect(
|
||||||
|
url: String,
|
||||||
|
content_type: String,
|
||||||
|
body: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");
|
||||||
|
|
||||||
|
ensure_session().await?;
|
||||||
|
|
||||||
|
// Get the stored client
|
||||||
|
let jar = SESSION_COOKIE.read().unwrap().jar.clone();
|
||||||
|
|
||||||
|
let uri = format!("{}{}", classroom_url, url);
|
||||||
|
|
||||||
|
// Do the request
|
||||||
|
let response = Request::post(uri)
|
||||||
|
.header("Content-Type", content_type)
|
||||||
|
.cookie_jar(jar)
|
||||||
|
.body(body)
|
||||||
|
.or_else(|err| Err(format!("Error creating request: {:?}", err)))?
|
||||||
|
.send();
|
||||||
|
|
||||||
|
let mut response = match response {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(err) => return Err(format!("Error sending request: {:?}", err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if response.status() == isahc::http::StatusCode::FOUND {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match response.text() {
|
||||||
|
Ok(t) => {
|
||||||
|
log_html(&t);
|
||||||
|
Err(format!("Unexpected response from classroom."))
|
||||||
|
}
|
||||||
|
Err(err) => Err(format!("Error getting text from response: {:?}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Makes a request to the online classroom, and returns the html string
|
/// Makes a request to the online classroom, and returns the html string
|
||||||
pub async fn request(url: String) -> Result<String, String> {
|
pub async fn request(url: String) -> Result<String, String> {
|
||||||
let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");
|
let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");
|
||||||
@ -119,7 +160,7 @@ pub async fn register_courses_request(url: String, body: String) -> Result<Strin
|
|||||||
Ok(t) => {
|
Ok(t) => {
|
||||||
log_html(&t);
|
log_html(&t);
|
||||||
Ok(t)
|
Ok(t)
|
||||||
},
|
}
|
||||||
Err(err) => Err(format!("Error getting text from response: {:?}", err)),
|
Err(err) => Err(format!("Error getting text from response: {:?}", err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,7 +236,7 @@ async fn login() -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_html(html: &String) {
|
pub fn log_html(html: &String) {
|
||||||
// Get current time and date in iso
|
// Get current time and date in iso
|
||||||
let now: DateTime<Local> = Local::now();
|
let now: DateTime<Local> = Local::now();
|
||||||
let now = now.to_rfc3339();
|
let now = now.to_rfc3339();
|
||||||
|
274
backend/src/online_classroom/update_expiration_date.rs
Normal file
274
backend/src/online_classroom/update_expiration_date.rs
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
use scraper::{Html, Selector};
|
||||||
|
|
||||||
|
use super::session::{classroom_post_redirect, request};
|
||||||
|
|
||||||
|
pub async fn update_expiration_date(
|
||||||
|
user_id: i32,
|
||||||
|
new_expiration_date: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let html = request(format!("/main/admin/user_edit.php?user_id={}", user_id)).await?;
|
||||||
|
|
||||||
|
let req_body = {
|
||||||
|
let html = Html::parse_document(&html);
|
||||||
|
|
||||||
|
let surnames_selector =
|
||||||
|
Selector::parse("#user_edit_lastname").expect("Error creating surnames selector");
|
||||||
|
let names_selector =
|
||||||
|
Selector::parse("#user_edit_firstname").expect("Error creating surnames selector");
|
||||||
|
let email_selector =
|
||||||
|
Selector::parse("#user_edit_email").expect("Error creating surnames selector");
|
||||||
|
let username_selector =
|
||||||
|
Selector::parse("#user_edit_username").expect("Error creating surnames selector");
|
||||||
|
let protect_token_sel = Selector::parse("#user_edit_protect_token")
|
||||||
|
.expect("Error creating protect_token selector");
|
||||||
|
|
||||||
|
let surnames = html
|
||||||
|
.select(&surnames_selector)
|
||||||
|
.next()
|
||||||
|
.ok_or(format!("surnames element not found"))?
|
||||||
|
.value()
|
||||||
|
.attr("value")
|
||||||
|
.ok_or(format!("surnames input has no value attribute"))?;
|
||||||
|
|
||||||
|
let names = html
|
||||||
|
.select(&names_selector)
|
||||||
|
.next()
|
||||||
|
.ok_or(format!("names element not found"))?
|
||||||
|
.value()
|
||||||
|
.attr("value")
|
||||||
|
.ok_or(format!("names input has no value attribute"))?;
|
||||||
|
|
||||||
|
let email = html
|
||||||
|
.select(&email_selector)
|
||||||
|
.next()
|
||||||
|
.ok_or(format!("email element not found"))?
|
||||||
|
.value()
|
||||||
|
.attr("value")
|
||||||
|
.ok_or(format!("email input has no value attribute"))?;
|
||||||
|
|
||||||
|
let username = html
|
||||||
|
.select(&username_selector)
|
||||||
|
.next()
|
||||||
|
.ok_or(format!("username element not found"))?
|
||||||
|
.value()
|
||||||
|
.attr("value")
|
||||||
|
.ok_or(format!("username input has no value attribute"))?;
|
||||||
|
|
||||||
|
let protect_token = html
|
||||||
|
.select(&protect_token_sel)
|
||||||
|
.next()
|
||||||
|
.ok_or(format!("protect_token element not found"))?
|
||||||
|
.value()
|
||||||
|
.attr("value")
|
||||||
|
.ok_or(format!("protect_token input has no value attribute"))?;
|
||||||
|
|
||||||
|
get_body(
|
||||||
|
surnames.into(),
|
||||||
|
names.into(),
|
||||||
|
email.into(),
|
||||||
|
username.into(),
|
||||||
|
new_expiration_date,
|
||||||
|
protect_token.into(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
classroom_post_redirect(
|
||||||
|
"/main/admin/user_edit.php".into(),
|
||||||
|
"multipart/form-data; boundary=---------------------------318235432819784070062970146417"
|
||||||
|
.into(),
|
||||||
|
req_body,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_body(
|
||||||
|
surnames: String,
|
||||||
|
names: String,
|
||||||
|
email: String,
|
||||||
|
username: String,
|
||||||
|
new_expiration_date: String,
|
||||||
|
protect_token: String,
|
||||||
|
) -> String {
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="lastname"
|
||||||
|
|
||||||
|
{surnames}
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="firstname"
|
||||||
|
|
||||||
|
{names}
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="official_code"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="email"
|
||||||
|
|
||||||
|
{email}
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="phone"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="picture"; filename=""
|
||||||
|
Content-Type: application/octet-stream
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="username"
|
||||||
|
|
||||||
|
{username}
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="reset_password"
|
||||||
|
|
||||||
|
0
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="password"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="status"
|
||||||
|
|
||||||
|
5
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="platform_admin"
|
||||||
|
|
||||||
|
0
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="language"
|
||||||
|
|
||||||
|
spanish
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="send_mail"
|
||||||
|
|
||||||
|
0
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="radio_expiration_date"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="expiration_date"
|
||||||
|
|
||||||
|
{new_expiration_date} 23:00
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="active"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="q"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="q"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_legal_accept"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_already_logged_in"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_update_type"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_rssfeeds"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_dashboard"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_timezone"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_mail_notify_invitation"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_mail_notify_message"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_mail_notify_group_message"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_user_chat_status"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_google_calendar_url"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_captcha_blocked_until_date"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_skype"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_linkedin_url"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_request_for_legal_agreement_consent_removal_justification"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_request_for_delete_account_justification"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_request_for_legal_agreement_consent_removal"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="extra_request_for_delete_account"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="submit"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="_qf__user_edit"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="protect_token"
|
||||||
|
|
||||||
|
{protect_token}
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="user_id"
|
||||||
|
|
||||||
|
2140
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="MAX_FILE_SIZE"
|
||||||
|
|
||||||
|
536870912
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="picture_crop_result"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="picture_crop_image_base_64"
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------318235432819784070062970146417
|
||||||
|
Content-Disposition: form-data; name="item_id"
|
||||||
|
|
||||||
|
2140
|
||||||
|
-----------------------------318235432819784070062970146417--
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
}
|
@ -15,7 +15,7 @@ export function ClassroomRegistration(props: {
|
|||||||
<FilledCard class="border border-c-outline overflow-hidden">
|
<FilledCard class="border border-c-outline overflow-hidden">
|
||||||
<h2 class="p-3 font-bold text-xl">Inscribir en Aula Virtual</h2>
|
<h2 class="p-3 font-bold text-xl">Inscribir en Aula Virtual</h2>
|
||||||
|
|
||||||
<div class="bg-c-surface p-4 h-[23rem]">
|
<div class="bg-c-surface p-4 h-[17rem]">
|
||||||
<ManualClassroomRegistration
|
<ManualClassroomRegistration
|
||||||
onAdd={(x) => setSelections((s) => new Set([...s, x]))}
|
onAdd={(x) => setSelections((s) => new Set([...s, x]))}
|
||||||
/>
|
/>
|
||||||
@ -45,9 +45,6 @@ export function ClassroomRegistration(props: {
|
|||||||
function ManualClassroomRegistration(props: {onAdd: (k: ClassroomCourseValue) => void}) {
|
function ManualClassroomRegistration(props: {onAdd: (k: ClassroomCourseValue) => void}) {
|
||||||
return (
|
return (
|
||||||
<form>
|
<form>
|
||||||
<p>Haz click en un curso para agregarlo</p>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<ClassroomSearchableSelect
|
<ClassroomSearchableSelect
|
||||||
onAdd={props.onAdd}
|
onAdd={props.onAdd}
|
||||||
/>
|
/>
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import { createSignal } from "solid-js";
|
|
||||||
import { ClassroomCourse } from "../../types/ClassroomCourse";
|
|
||||||
import { Person } from "../../types/Person";
|
|
||||||
import { CoursesList } from "./Courses";
|
|
||||||
import { Message } from "./Message";
|
|
||||||
|
|
||||||
export function ClassroomUserCourses(props: {userid: number, updateSignal: number, person: Person}) {
|
|
||||||
const [courses, setCourses] = createSignal<Array<ClassroomCourse>>([]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="flex gap-2 flex-wrap">
|
|
||||||
<CoursesList
|
|
||||||
userid={props.userid}
|
|
||||||
updateSignal={props.updateSignal}
|
|
||||||
courses={courses()}
|
|
||||||
setCourses={setCourses}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Message courses={courses()} person={props.person} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -0,0 +1,115 @@
|
|||||||
|
import { createMemo, createSignal, onMount } from "solid-js";
|
||||||
|
import { OutlinedCard } from "../../components/OutlinedCard";
|
||||||
|
import { LoadingStatus, backend, useLoading, wait } from "../../utils/functions";
|
||||||
|
import { LoadingIcon } from "../../icons/LoadingIcon";
|
||||||
|
import { ArrowsClockwiseIcon } from "../../icons/ArrowsClockwiseIcon";
|
||||||
|
import { JsonResult } from "../../types/JsonResult";
|
||||||
|
|
||||||
|
export function AccountExpiration(props: {userId: number}) {
|
||||||
|
const [expirationDate, setExpirationDate] = createSignal<string | null>(null); // YYYY-MM-DD
|
||||||
|
const {status, setStatus} = useLoading();
|
||||||
|
|
||||||
|
const loading = createMemo(() => status() === LoadingStatus.Loading);
|
||||||
|
|
||||||
|
const loadExpiration = async() => {
|
||||||
|
setStatus(LoadingStatus.Loading);
|
||||||
|
setExpirationDate(null);
|
||||||
|
if (import.meta.env.DEV) await wait(1500);
|
||||||
|
|
||||||
|
backend.get<JsonResult<string>>(`/api/classroom/expiration_date/${props.userId}`)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
const date = response.data.Ok.substring(0, 10);
|
||||||
|
console.log(date);
|
||||||
|
setExpirationDate(date);
|
||||||
|
setStatus(LoadingStatus.Ok);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
|
||||||
|
setStatus(LoadingStatus.Error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setExpiration = async() => {
|
||||||
|
setStatus(LoadingStatus.Loading);
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) await wait(1500);
|
||||||
|
|
||||||
|
backend.put<JsonResult<null>>(
|
||||||
|
`/api/classroom/expiration_date/${props.userId}`,
|
||||||
|
{date: expirationDate()},
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
setStatus(LoadingStatus.Ok);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
setStatus(LoadingStatus.Error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(loadExpiration);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OutlinedCard class="w-[24rem] h-[10.5rem] overflow-hidden">
|
||||||
|
<h2 class="text-xl p-3 bg-c-surface-variant text-c-on-surface-variant">
|
||||||
|
Fecha de expiración del acceso
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="px-4 py-4">
|
||||||
|
<p class="grid grid-cols-[auto_9rem] items-center">
|
||||||
|
<span>
|
||||||
|
Fecha de expiración:
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
class="bg-c-surface text-c-on-surface border border-c-outline rounded-lg py-1 px-2 font-mono
|
||||||
|
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
type="date"
|
||||||
|
name="classroom-expiration-update"
|
||||||
|
id="classroom-expiration-update"
|
||||||
|
value={expirationDate() ?? ""}
|
||||||
|
oninput={(e) => setExpirationDate(e.target.value)}
|
||||||
|
disabled={status() === LoadingStatus.Loading}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="text-right pt-2">
|
||||||
|
<button
|
||||||
|
class="bg-c-primary text-c-on-primary px-4 py-2 rounded-full cursor-pointer
|
||||||
|
disabled:opacity-50 disabled:cursor-not-allowed relative"
|
||||||
|
type="button"
|
||||||
|
disabled={loading()}
|
||||||
|
onclick={setExpiration}
|
||||||
|
>
|
||||||
|
<span class="mr-6">
|
||||||
|
Actualizar expiracion
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="absolute top-1 right-2"
|
||||||
|
style={{display: loading() ? "inline-block" : "none"}}
|
||||||
|
>
|
||||||
|
<LoadingIcon
|
||||||
|
class="animate-spin"
|
||||||
|
fill="var(--c-primary-container)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="absolute top-1 right-2"
|
||||||
|
style={{display: loading() ? "none" : "inline-block"}}
|
||||||
|
>
|
||||||
|
<ArrowsClockwiseIcon
|
||||||
|
fill="var(--c-on-primary)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</OutlinedCard>
|
||||||
|
);
|
||||||
|
}
|
@ -41,7 +41,7 @@ export function CoursesList(props: {userid: number, updateSignal: number, course
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OutlinedCard class="w-[24rem] h-[26.5rem] overflow-hidden">
|
<OutlinedCard class="w-[24rem] h-[19.5rem] overflow-hidden">
|
||||||
<h2 class="text-xl p-3 bg-c-surface-variant text-c-on-surface-variant">
|
<h2 class="text-xl p-3 bg-c-surface-variant text-c-on-surface-variant">
|
||||||
Cursos matriculados
|
Cursos matriculados
|
||||||
</h2>
|
</h2>
|
@ -30,15 +30,15 @@ Correo electrónico: soporte@eegsac.com
|
|||||||
|
|
||||||
const companyMessage = `
|
const companyMessage = `
|
||||||
Buen día estimado,
|
Buen día estimado,
|
||||||
Se adjunta la lista de (los) colaboradore(s) y su(s) acceso(s) en la plataforma virtual.
|
Se adjunta usuario, contraseña y cursos de la plataforma virtual para el personal solicitado.
|
||||||
|
|
||||||
El vínculo para acceder al aula virtual es el siguiente: https://aulavirtual.eegsac.com/ .
|
El vínculo para acceder al aula virtual es el siguiente: https://aulavirtual.eegsac.com/ .
|
||||||
Quedo atenta cualquier duda o consulta.
|
Quedo atenta cualquier duda o consulta.
|
||||||
Saludos Cordiales!
|
Saludos Cordiales.
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OutlinedCard class="w-[24rem] h-[26.5rem] overflow-hidden">
|
<OutlinedCard class="w-[24rem] h-[30.5rem] overflow-hidden">
|
||||||
<h2 class="text-xl p-3 bg-c-surface-variant text-c-on-surface-variant">
|
<h2 class="text-xl p-3 bg-c-surface-variant text-c-on-surface-variant">
|
||||||
Generar mensajes
|
Generar mensajes
|
||||||
</h2>
|
</h2>
|
||||||
@ -60,7 +60,7 @@ Saludos Cordiales!
|
|||||||
<pre
|
<pre
|
||||||
class="w-full p-2 bg-c-surface text-c-on-surface
|
class="w-full p-2 bg-c-surface text-c-on-surface
|
||||||
border border-c-outline rounded font-mono text-sm
|
border border-c-outline rounded font-mono text-sm
|
||||||
whitespace-pre-wrap max-h-32 overflow-x-scroll shadow-md"
|
whitespace-pre-wrap max-h-40 overflow-x-scroll shadow-md"
|
||||||
>
|
>
|
||||||
{personMessage()}
|
{personMessage()}
|
||||||
</pre>
|
</pre>
|
||||||
@ -82,7 +82,7 @@ Saludos Cordiales!
|
|||||||
<pre
|
<pre
|
||||||
class="w-full p-2 bg-c-surface text-c-on-surface
|
class="w-full p-2 bg-c-surface text-c-on-surface
|
||||||
border border-c-outline rounded font-mono text-sm
|
border border-c-outline rounded font-mono text-sm
|
||||||
whitespace-pre-wrap max-h-28 overflow-x-scroll shadow-md"
|
whitespace-pre-wrap max-h-36 overflow-x-scroll shadow-md"
|
||||||
>
|
>
|
||||||
{companyMessage}
|
{companyMessage}
|
||||||
</pre>
|
</pre>
|
27
frontend/src/OnlineClassroom/ClassroomUserInfo/index.tsx
Normal file
27
frontend/src/OnlineClassroom/ClassroomUserInfo/index.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { createSignal } from "solid-js";
|
||||||
|
import { ClassroomCourse } from "../../types/ClassroomCourse";
|
||||||
|
import { Person } from "../../types/Person";
|
||||||
|
import { CoursesList } from "./Courses";
|
||||||
|
import { Message } from "./Message";
|
||||||
|
import { AccountExpiration } from "./AccountExpiration";
|
||||||
|
|
||||||
|
export function ClassroomUserInfo(props: {userid: number, updateSignal: number, person: Person}) {
|
||||||
|
const [courses, setCourses] = createSignal<Array<ClassroomCourse>>([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex gap-2 flex-wrap">
|
||||||
|
<div>
|
||||||
|
<CoursesList
|
||||||
|
userid={props.userid}
|
||||||
|
updateSignal={props.updateSignal}
|
||||||
|
courses={courses()}
|
||||||
|
setCourses={setCourses}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AccountExpiration userId={props.userid} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Message courses={courses()} person={props.person} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -4,7 +4,7 @@ import { Person } from "../types/Person";
|
|||||||
import { FilledCard } from "../components/FilledCard";
|
import { FilledCard } from "../components/FilledCard";
|
||||||
import { ClassroomUserCreation } from "./ClassroomUserCreation";
|
import { ClassroomUserCreation } from "./ClassroomUserCreation";
|
||||||
import { ClassroomVinculation } from "./ClassroomVinculation";
|
import { ClassroomVinculation } from "./ClassroomVinculation";
|
||||||
import { ClassroomUserCourses } from "./ClassroomUserCourses";
|
import { ClassroomUserInfo } from "./ClassroomUserInfo";
|
||||||
import { ClassroomRegistration } from "./ClassroomRegistration";
|
import { ClassroomRegistration } from "./ClassroomRegistration";
|
||||||
|
|
||||||
type TabType = "Vinculate" | "Create";
|
type TabType = "Vinculate" | "Create";
|
||||||
@ -40,7 +40,7 @@ export function OnlineClassroom() {
|
|||||||
onSuccess={() => setUpdateSIgnal((s) => s + 1)}
|
onSuccess={() => setUpdateSIgnal((s) => s + 1)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ClassroomUserCourses
|
<ClassroomUserInfo
|
||||||
userid={person()!.person_classroom_id!}
|
userid={person()!.person_classroom_id!}
|
||||||
updateSignal={updateSignal()}
|
updateSignal={updateSignal()}
|
||||||
person={person()!}
|
person={person()!}
|
||||||
|
15
frontend/src/icons/ArrowsClockwiseIcon.tsx
Normal file
15
frontend/src/icons/ArrowsClockwiseIcon.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
export function ArrowsClockwiseIcon(props: {fill: string, size?: number, class?: string}) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class={`inline-block w-6 ${props.class}`}
|
||||||
|
width={props.size ?? 32}
|
||||||
|
height={props.size ?? 32}
|
||||||
|
fill={props.fill}
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
>
|
||||||
|
<path d="M197.67,186.37a8,8,0,0,1,0,11.29C196.58,198.73,170.82,224,128,224c-37.39,0-64.53-22.4-80-39.85V208a8,8,0,0,1-16,0V160a8,8,0,0,1,8-8H88a8,8,0,0,1,0,16H55.44C67.76,183.35,93,208,128,208c36,0,58.14-21.46,58.36-21.68A8,8,0,0,1,197.67,186.37ZM216,40a8,8,0,0,0-8,8V71.85C192.53,54.4,165.39,32,128,32,85.18,32,59.42,57.27,58.34,58.34a8,8,0,0,0,11.3,11.34C69.86,69.46,92,48,128,48c35,0,60.24,24.65,72.56,40H168a8,8,0,0,0,0,16h48a8,8,0,0,0,8-8V48A8,8,0,0,0,216,40Z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user