[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
|
||||
aulavirtual
|
||||
scraps
|
||||
request-logs
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::online_classroom::register_course::register_course;
|
||||
use crate::online_classroom::update_expiration_date::update_expiration_date;
|
||||
use crate::{
|
||||
json_result::JsonResult,
|
||||
model::{
|
||||
@ -47,3 +48,40 @@ pub async fn register_course_contr(
|
||||
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::register_course_contr_options,
|
||||
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 get_courses;
|
||||
pub mod get_expiration_date;
|
||||
pub mod register_course;
|
||||
mod session;
|
||||
pub mod update_expiration_date;
|
||||
pub mod user;
|
||||
|
||||
/// 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 std::time::{SystemTime, UNIX_EPOCH};
|
||||
use urlencoding::encode;
|
||||
@ -17,6 +17,47 @@ lazy_static! {
|
||||
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
|
||||
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!");
|
||||
@ -119,7 +160,7 @@ pub async fn register_courses_request(url: String, body: String) -> Result<Strin
|
||||
Ok(t) => {
|
||||
log_html(&t);
|
||||
Ok(t)
|
||||
},
|
||||
}
|
||||
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
|
||||
let now: DateTime<Local> = Local::now();
|
||||
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">
|
||||
<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
|
||||
onAdd={(x) => setSelections((s) => new Set([...s, x]))}
|
||||
/>
|
||||
@ -45,9 +45,6 @@ export function ClassroomRegistration(props: {
|
||||
function ManualClassroomRegistration(props: {onAdd: (k: ClassroomCourseValue) => void}) {
|
||||
return (
|
||||
<form>
|
||||
<p>Haz click en un curso para agregarlo</p>
|
||||
<br />
|
||||
|
||||
<ClassroomSearchableSelect
|
||||
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 (
|
||||
<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">
|
||||
Cursos matriculados
|
||||
</h2>
|
@ -30,15 +30,15 @@ Correo electrónico: soporte@eegsac.com
|
||||
|
||||
const companyMessage = `
|
||||
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/ .
|
||||
Quedo atenta cualquier duda o consulta.
|
||||
Saludos Cordiales!
|
||||
Saludos Cordiales.
|
||||
`.trim();
|
||||
|
||||
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">
|
||||
Generar mensajes
|
||||
</h2>
|
||||
@ -60,7 +60,7 @@ Saludos Cordiales!
|
||||
<pre
|
||||
class="w-full p-2 bg-c-surface text-c-on-surface
|
||||
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()}
|
||||
</pre>
|
||||
@ -82,7 +82,7 @@ Saludos Cordiales!
|
||||
<pre
|
||||
class="w-full p-2 bg-c-surface text-c-on-surface
|
||||
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}
|
||||
</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 { ClassroomUserCreation } from "./ClassroomUserCreation";
|
||||
import { ClassroomVinculation } from "./ClassroomVinculation";
|
||||
import { ClassroomUserCourses } from "./ClassroomUserCourses";
|
||||
import { ClassroomUserInfo } from "./ClassroomUserInfo";
|
||||
import { ClassroomRegistration } from "./ClassroomRegistration";
|
||||
|
||||
type TabType = "Vinculate" | "Create";
|
||||
@ -40,7 +40,7 @@ export function OnlineClassroom() {
|
||||
onSuccess={() => setUpdateSIgnal((s) => s + 1)}
|
||||
/>
|
||||
|
||||
<ClassroomUserCourses
|
||||
<ClassroomUserInfo
|
||||
userid={person()!.person_classroom_id!}
|
||||
updateSignal={updateSignal()}
|
||||
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