New things

This commit is contained in:
2023-11-27 15:13:44 +00:00
parent 4f74257c1b
commit da22d3c86b
9 changed files with 742 additions and 35 deletions

View File

@@ -12,11 +12,10 @@ require_once "../include/bd_pdo.php";
global $pdo;
//print_r($_POST);
if (!isset($_POST['periodo']) || count($_POST["periodo"])==0) {
//header("Location: ../días_festivos.php?error=1");
echo "Error no hay periodo";
header("Location: ../días_festivos.php?error=0");
exit();
}
$periodo = $_POST['periodo'];
$periodoArr = $_POST['periodo'];
if (isset($_POST['rango'])) {
$diaInicio = new DateTime(date("Y-m-d", strtotime(str_replace("/", "-", $_POST['diaFestivo']))));
@@ -24,27 +23,35 @@ if (isset($_POST['rango'])) {
$cantidad = $diaFin->diff($diaInicio);
$date = date("Y-m-d", strtotime(str_replace("/", "-", $_POST['diaFestivo'])));
for ($dias = 0; $dias <= $cantidad->days; $dias++) {
$sql = "SELECT fi_diasfestivos(:periodo, :dia)";
$db->querySingle('SELECT fi_diasfestivos({'.implode(",",$fieldName).'}, :dia)', [':dia' => $date]);
/*$sql = "SELECT fi_diasfestivos(:periodo, :dia)";
$params = [':periodo' => $periodo, ':dia' => $date];
query($sql, $params, false);
query($sql, $params, false);*/
$date = date("Y-m-d", strtotime($date . "+ 1 days"));
}
header("Location: ../días_festivos.php");
exit();
} else {
$sql = "SELECT * FROM fs_diasfestivos(null, :dia)";
/*$sql = "SELECT * FROM fs_diasfestivos(null, :dia)";
$params = [':dia' => $_POST['diaFestivo']];
$dia_general = query($sql, $params, false);
$sql = "SELECT * FROM fs_diasfestivos(null, null, :periodo, :dia)";
$params = [':periodo' => $periodo, ":dia" => $_POST['diaFestivo']];
$dia = query($sql, $params, false);
if (!$dia && !$dia_general) { //no hay repetidos
$sql = "SELECT fi_diasfestivos(:periodo, :dia)";
$id = query($sql, $params, false);
$dia = query($sql, $params, false);*/
//if (!$dia && !$dia_general) { //no hay repetidos
foreach($periodoArr as $periodo){
$db->querySingle('SELECT fi_diasfestivos({'.implode(",",$fieldName).'}, :dia)', [':dia' => $_POST['diaFestivo']]);
/*$sql = "SELECT fi_diasfestivos(:periodo, :dia)";
$params = [':periodo' => $periodo, ":dia" => $_POST['diaFestivo']];
$id = query($sql, $params, false);*/
}
header("Location: ../días_festivos.php");
exit();
} else {
/*} else {
header("Location: ../días_festivos.php?error=1");
exit();
}
}*/
}

135
action/profesor_faltas.php Normal file
View File

@@ -0,0 +1,135 @@
<?
require_once "{$_SERVER['DOCUMENT_ROOT']}/class/c_login.php";
header('Content-Type: application/json');
if (!Login::is_logged()) {
header('HTTP/1.1 401 Unauthorized');
echo json_encode(['error' => 'No se ha iniciado sesión']);
exit();
}
$user = Login::get_user();
try {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
$facultad = $_GET['facultad'] ?? $user->facultad['facultad_id'] ?? null;
$porcentaje = $_GET['porcentaje'] ?? null;
$faltas = $_GET['faltas'] ?? null;
if (!isset($facultad) || !is_numeric($facultad)) {
$error = 'No se ha seleccionado una facultad';
} else if ((!isset($faltas) || !is_numeric($faltas)) && (!isset($porcentaje) || !is_numeric($porcentaje))) {
$error = 'Debe especificar las faltas o el porcentaje';
} else if (isset($faltas) && (!is_numeric($faltas) || $faltas <= 0)) {
$error = 'Las faltas deben ser un número mayor a 0';
} else if (isset($porcentaje) && (!is_numeric($porcentaje) || $porcentaje <= 0)) {
$error = 'El porcentaje debe ser un número mayor a 0';
} else if (isset($faltas) && isset($porcentaje)) {
$error = 'No se puede especificar las faltas y el porcentaje al mismo tiempo';
} else if (!isset($facultad) || !is_numeric($facultad)) {
$error = 'Debe especificar una facultad';
}
if (isset($error)) {
header('HTTP/1.1 400 Bad Request');
echo json_encode(['error' => $error]);
exit();
}
// Initialize the data array
$data = array();
// Check if 'profesor' or 'supervisor' is set and prepare the specific part of the SQL query accordingly.
if (isset($_GET['profesor']) || isset($_GET['supervisor'])) {
$condition = isset($_GET['profesor'])
? "r.registro_fecha IS NULL AND NOT COALESCE(r.registro_justificada, FALSE)"
: "estado_supervisor_id = 2";
$filter = isset($faltas)
? "afcp.faltas >= :faltas"
: "afcp.porcentaje >= :porcentaje";
// Prepare the SQL query with placeholders for parameters
$data = array_column($db->query(
"WITH fechas AS (
SELECT
fcc.registro_fecha_ideal,
fcc.horario_id,
hp.profesor_id
FROM fechas_clase_cache fcc
JOIN horario_profesor hp USING (horario_id)
JOIN horario h USING (horario_id)
WHERE (h.PERIODO_ID, h.FACULTAD_ID) = (:periodo_id, :facultad_id) and profesor_id <> 0
),
asistencia_faltas AS (
SELECT
f.profesor_id,
COUNT(1) AS total,
COUNT(1) FILTER (WHERE $condition AND f.registro_fecha_ideal <= current_date) AS faltas
FROM fechas f
LEFT JOIN registro r USING (registro_fecha_ideal, horario_id, profesor_id)
GROUP BY f.profesor_id
),
asistencia_faltas_con_porcentaje AS (
SELECT
af.profesor_id,
af.faltas,
af.total,
CASE
WHEN af.total > 0 THEN ROUND((af.faltas::NUMERIC / af.total) * 100, 2)
ELSE NULL
END AS porcentaje
FROM asistencia_faltas af
WHERE af.faltas > 0
)
SELECT
json_build_object(
'profesor', json_build_object(
'profesor_nombre', p.profesor_nombre,
'profesor_clave', p.profesor_clave,
'profesor_correo', p.profesor_correo
),
'profesor_id', afcp.profesor_id,
'faltas', afcp.faltas,
'total', afcp.total,
'porcentaje', afcp.porcentaje
) AS result_json
FROM asistencia_faltas_con_porcentaje afcp
JOIN profesor p USING (profesor_id)
WHERE $filter
ORDER BY afcp.porcentaje DESC",
[
'periodo_id' => $user->periodo_id,
'facultad_id' => $facultad,
] + (isset($faltas)
? ['faltas' => $faltas]
: ['porcentaje' => $porcentaje])
), 'result_json');
} else {
// Send a 400 Bad Request header and an error message in JSON format
header('HTTP/1.1 400 Bad Request');
echo json_encode(['error' => 'Especifique si las faltas son de profesor o supervisor']);
exit();
}
if (empty($data)) {
header('HTTP/1.1 404 Not Found');
echo json_encode(['error' => 'No se encontraron faltas']);
} else {
echo json_encode(
array_map(fn($item) => json_decode($item), $data)
);
}
break;
default:
header('HTTP/1.1 405 Method Not Allowed');
echo json_encode(['error' => 'Método no permitido']);
break;
}
} catch (PDOException $e) {
echo json_encode([
'error' => $e->getMessage(),
'query' => $db->getLastQuery(),
]);
}

View File

@@ -33,26 +33,29 @@ if(isset($_POST["salon"]) && $_POST["salon"] != "")
//--------------
//Obtiene datos reposición
//TODO , SALÓN SALIÓ PENDIENTE Y FALTA REVISAR LISTA DE CORREOS TO
$reposicion_rs = $db->querySingle('SELECT h.materia, r.fecha_nueva, r.hora_nueva, r.fecha_clase, h.horario_hora, h.facultad_id, h.facultad, f.clave_dependencia, s.salon_id, s.salon_array, r.motivo_cancelacion, ta.tipoaula_supervisor , ta.tipoaula_nombre
$reposicion_rs = $db->querySingle('SELECT h.materia, r.fecha_nueva, r.hora_nueva, r.fecha_clase, h.horario_hora, h.facultad_id, h.facultad, f.clave_dependencia, r.motivo_cancelacion, ta.tipoaula_supervisor , ta.tipoaula_nombre
from reposicion_solicitud r
inner join horario_view h on h.horario_id = r.horario_id
inner join facultad f on f.facultad_id = h.facultad_id
inner join tipoaula ta on ta.tipoaula_id = r.tipoaula_id
left join salon_view s on r.salon_id = s.salon_id
where r.reposicion_solicitud_id = :id_repo',
[':id_repo' => $id_repo]
);
if($reposicion_rs["salon_id"] == "" || $reposicion_rs["salon_id"] == NULL){
//Obtiene datos de salón asignado
$salon_rs = $db->querySingle('SELECT s.salon_id, s.salon_array FROM salon_view s where s.salon_id = :id_salon',
[':id_salon' => $salon]
);
if($salon_rs["salon_id"] == "" || $salon_rs["salon_id"] == NULL){
$salon_desc = "Pendiente";
}else{
$salon_json = json_decode($reposicion_rs["salon_array"], true);
$salon_json = json_decode($salon_rs["salon_array"], true);
if($salon_json[0]== "UNIVERSIDAD LA SALLE"){
unset($salon_json[0]);
}
$salon_desc = join(" / ",$salon_json);
}
//Obtiene correos
$correos_rs = $db->query('SELECT p.profesor_nombre, p.profesor_correo, u.usuario_nombre as jefe_nombre, u.usuario_correo as jefe_correo,
coor.usuario_nombre as coordinador_nombre, coor.usuario_correo as coordinador_correo
@@ -163,12 +166,12 @@ if($to!= "" && ENVIO_CORREOS){
</body>';
require_once('../include/phpmailer/PHPMailerAutoload.php');
/*if(DB_NAME == "poad_pruebas"){
if($_ENV['DB_NAME'] == "paad_pruebas"){
$asunto = "PRUEBAS-".$asunto;
Mailer::enviarCorreo("alejandro.lara@lasalle.mx", $asunto, $texto, true);
}else{*/
}else{
Mailer::enviarCorreo($to, $asunto, $texto, true);
//}
}
}
/*

View File

@@ -39,6 +39,7 @@ else
$comentario = trim(htmlspecialchars($_POST["comentario"], ENT_QUOTES, "UTF-8"));//limpia texto
$duracion_rs = $db->querySingle("select * from duracion where duracion_id = :id", [":id"=>$duracion_id]);
$duracion_tiempo = $duracion_rs["duracion_interval"];
@@ -73,6 +74,9 @@ if(intval($dia) != intval($dia_falta)){
exit();
}
//Obtiene materia
$materia_rs = $db->querySingle('SELECT materia_nombre from materia where materia_id = :mat',[':mat' => $materia]);
//Obtiene correo
$correos_rs = $db->querySingle('SELECT coor.usuario_correo, coor.usuario_nombre from usuario coor where rol_id = :rol_coord and facultad_id = (
select coalesce(facultad_id,0) from usuario u where u.usuario_id = :id_usr)',[':rol_coord' => COORDINADOR, ':id_usr' => $user->user["id"]]
@@ -98,6 +102,7 @@ if($tipo == 1){//Reposición
if($traslape){
//print_r($_POST);
//echo "SELECT * from traslape_profesor_reposicion($prof,'".DateTime::createFromFormat('d/m/Y', $fecha)->format('Y-m-d')."' , '$hora', $duracion)";
header("Location:".$pag."?error=9");
exit();
}
@@ -110,12 +115,13 @@ if($tipo == 1){//Reposición
]
);
}catch(Exception $e){
echo $e->getMessage();
//header("Location: ".$pag."?error=1");
exit();
}
$texto = "<p>Se creó una reposición nueva.</p>";
$texto .= "<p><b>".mb_strtoupper($reposicion_rs["materia"])."</b> del día <b>".$fecha_falta." a las ".$hor." hrs. </b> se propone reponer el <b>".$fecha_new." a las ".$hora." hrs.</b>";
$texto .= "<p><b>".mb_strtoupper($materia_rs["materia_nombre"])."</b> del día <b>".$fecha_falta." a las ".$hor." hrs. </b> se propone reponer el <b>".$fecha_new." a las ".$hora." hrs.</b>";
$texto .= "<p>Ingresa al <a href='https://paad.lci.ulsa.mx'>sistema PAAD</a> para autorizarla.</p>";
/*
@@ -134,11 +140,12 @@ if($tipo == 1){//Reposición
]
);
}catch(Exception $e){
header("Location: ".$pag."?error=1");
exit();
}
$texto = "<p>Se creó un cambio de salón nuevo.</p>";
$texto .= "<p><b>".mb_strtoupper($reposicion_rs["materia"])."</b> del día <b>".$fecha_falta." a las ".$hora." hrs. </b> se propone reponer el <b>".$fecha_nueva." a las ".$hora_nueva." hrs.</b>";
$texto .= "<p><b>".mb_strtoupper($materia_rs["materia_nombre"])."</b> del día <b>".$fecha_falta." a las ".$hora." hrs. </b> se propone reponer el <b>".$fecha_nueva." a las ".$hora_nueva." hrs.</b>";
$texto .= "<p>Ingresa al <a href='https://paad.lci.ulsa.mx'>sistema PAAD</a> para autorizarlo.</p>";
/*
@@ -159,14 +166,14 @@ if($to!= "" && ENVIO_CORREOS){
</body>';
require_once('../include/phpmailer/PHPMailerAutoload.php');
/*if(DB_NAME == "poad_pruebas"){
if($_ENV['DB_NAME'] == "paad_pruebas"){
$asunto = "PRUEBAS-".$asunto;
Mailer::enviarCorreo("alejandro.lara@lasalle.mx", $asunto, $texto, true);
}else{*/
}else{
Mailer::enviarCorreo($to, $asunto, $texto, true);
//}
}
}
header("Location: ".$pag."?ok=0");
exit();
header("Location: ".$pag."?ok=0");
?>

View File

@@ -5,7 +5,7 @@
class Mailer{
private const FROM = "academia@lasalle.mx";
private const FROM_NAME = "Vicerrectoría Académica";
private const FROM_PASS = "Foy25193";
private const FROM_PASS = "4c4d3m14S3gur4##";//Foy25193
private const FOOTER = "<p style='margin-top:5em; color:#aaa;font-style:italics'><small>Este es un correo automatizado, esta cuenta no recibe correos.<small></p>";
//private $lista_to, $asunto, $texto;
@@ -50,12 +50,6 @@ class Mailer{
}else{//cadena de texto separada por ;
if(strpos($lista_to, ";")!==false){
$toArr = explode(";", $lista_to);
}elseif(strpos($lista_to, ",")!==false){
$toArr = explode(",", $lista_to);
}else{
echo "Cadena de correos inválida";
return false;
}
foreach($toArr as $correo){
if(trim($correo)!=""){
if($bcc)
@@ -64,10 +58,32 @@ class Mailer{
$mail->AddAddress($correo);
}
}
}elseif(strpos($lista_to, ",")!==false){
$toArr = explode(",", $lista_to);
foreach($toArr as $correo){
if(trim($correo)!=""){
if($bcc)
$mail->addBCC($correo);
else
$mail->AddAddress($correo);
}
}
}else{
if(trim($lista_to)!=""){
if($bcc)
$mail->addBCC($lista_to);
else
$mail->AddAddress($lista_to);
}
}
}
//Success
if ($mail->Send()) {
return true;
}else{
echo "Error al enviar correo";
return false;
}
}catch(phpmailerException $e){
echo $mail->ErrorInfo;

142
export/faltas_excel.php Normal file
View File

@@ -0,0 +1,142 @@
<?php
$fecha = date('d_m_Y');
header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
header("Content-Disposition: attachment;filename=horario_$fecha.xlsx");
header("Cache-Control: max-age=0");
require_once "../vendor/autoload.php";
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\IOFactory;
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Image settings
$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
$drawing->setName('La Salle')
->setDescription('La Salle')
->setPath('../imagenes/logo.png')
->setCoordinates('B1')
->setHeight(100)
->setOffsetX(10)
->setWorksheet($spreadsheet->getActiveSheet());
$json = file_get_contents('php://input');
$data = json_decode($json, true);
empty($data) and die(json_encode(['error' => 'No se recibieron datos', 'data' => $data]));
$data_excel = array(
"CLAVE" => 'profesor_clave',
"PROFESOR" => 'profesor_nombre',
"CORREO" => 'profesor_correo',
"FALTAS" => 'faltas',
"TOTAL" => 'total',
"PORCENTAJE" => 'porcentaje',
); // Same as before
const ROW = 6;
// Merge cells from A1 to C+ ROW
$sheet->mergeCells('A1:B' . (ROW - 1));
// Merge cells from D1 to size of $data_excel + 1
$sheet->mergeCells('C1:' . chr(65 + count($data_excel) - 1) . (ROW - 1));
// Set the title in D1 Sistema de Auditoría de Asistencia
$sheet->setCellValue('C1', 'Sistema de Auditoría de Asistencia');
$sheet->getStyle('C1')->applyFromArray([
'font' => [
'bold' => true,
'size' => 30,
'name' => 'Indivisa Text Sans',
'color' => ['argb' => '001d68'],
],
'alignment' => [
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
$lastColumnLetter = chr(65 + count($data_excel) - 1);
$headers_range = 'A' . ROW . ':' . $lastColumnLetter . ROW;
$keys = array_keys($data_excel);
array_walk($keys, function ($key, $index) use ($sheet) {
$sheet->setCellValue(chr(65 + $index) . ROW, $key);
});
// Apply the header styles
$sheet->getStyle($headers_range)->applyFromArray([
'font' => [
'bold' => true,
'size' => 15,
'name' => 'Indivisa Text Sans',
'color' => ['argb' => Color::COLOR_WHITE],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['argb' => '001d68'],
]
]);
// set filters
$sheet->setAutoFilter($headers_range);
// Styles that are common for all rows can be set outside the loop
const DEFAULT_FONT = [
'size' => 12,
'name' => 'Indivisa Text Sans',
'color' => ['argb' => '001d68']
];
const DEFAULT_STYLE = [
'alignment' => [
'vertical' => Alignment::VERTICAL_CENTER,
'wrapText' => true,
],
'font' => DEFAULT_FONT,
'borders' => [
'outline' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['argb' => Color::COLOR_WHITE],
]
]
];
foreach ($data as $index => $registro) {
$pair = $index % 2 == 0;
$cellRange = 'A' . (ROW + $index + 1) . ':' . $lastColumnLetter . (ROW + $index + 1);
$styleArray = DEFAULT_STYLE;
$styleArray['fill'] = [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['argb' => $pair ? 'd4d9dd' : 'f6f7f8'],
];
$sheet->getStyle($cellRange)->applyFromArray($styleArray);
$values = array_values($data_excel);
array_walk($values, function ($row, $column_index) use ($sheet, $index, $registro) {
$cellLocation = chr(65 + $column_index) . (ROW + $index + 1);
$sheet->setCellValue($cellLocation, $registro[$row]);
});
}
foreach ($sheet->getColumnIterator() as $column) {
$sheet->getColumnDimension($column->getColumnIndex())->setAutoSize(true);
}
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$writer->save('php://output');

275
faltas.php Normal file
View File

@@ -0,0 +1,275 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Auditoría de faltas</title>
<?php
include 'import/html_css_files.php';
?>
<style>
[v-cloak] {
display: none;
}
</style>
<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"></script>
<script src="js/bootstrap/bootstrap.min.js"></script>
</head>
<body>
<?
$redirect = $_SERVER['PHP_SELF'];
include "import/html_header.php";
global $user;
html_header(
"Faltas",
"Sistema de gestión de checador",
);
if (!$user->periodo_id) { ?>
<script defer src="js/jquery.min.js"></script>
<script src="js/bootstrap/bootstrap.min.js"></script>
<div class="modal" id="seleccionar-periodo" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">Seleccionar periodo</h2>
</div>
<div class="modal-body container">
<? include 'import/periodo.php' ?>
</div>
</div>
</div>
</div>
<script>
$('#seleccionar-periodo').modal({
backdrop: 'static',
keyboard: false,
});
$('#seleccionar-periodo').modal('show');
</script>
<? exit;
} ?>
<main class="container-fluid px-4 mt-4" id="app" v-cloak @vue:mounted="mounted" style="min-height: 60vh;"
v-scope="">
<?php include "import/periodo.php" ?>
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="form-box marco">
<? if (!$user->facultad['facultad_id']) { ?>
<div class="form-group row">
<label for="dlFacultad" class="col-4 col-form-label" id="facultad">Facultad</label>
<div class="col-6">
<div id="dlFacultad" class="datalist datalist-select mb-1 w-100">
<div class="datalist-input">
Selecciona una facultad
</div>
<span class="icono ing-buscar"></span>
<ul style="display:none">
<li class="datalist-option d-none" data-id="-1">
Selecciona una facultad
</li>
<li class="datalist-option" v-for="facultad in facultades"
:key="facultad.facultad_id" :data-id="facultad.facultad_id"
@click="filter.facultad = facultad.facultad_id;">
(<small> {{facultad.clave_dependencia}} </small>) {{ facultad.facultad_nombre }}
</li>
</ul>
<input type="hidden" id="facultad_id" name="id">
</div>
</div>
</div>
<? } ?>
<div class="form-group row">
<label for="profesor" class="col-4 col-form-label">Profesor</label>
<div class="col-6">
<div class="form-row justify-content-around align-items-center">
<input id="profesor" name="profesor" class="form-control col-11 mr-1 px-2"
placeholder="Seleccione una profesor" list="dlProfesor" v-model="filter.profesor">
<button type="button" class="btn btn-outline-danger btn-sm form-control col ml-auto"
@click="filter.profesor = '';">
<i class="ing-borrar"></i>
</button>
</div>
<datalist id="dlProfesor">
<option v-for="profesor in profesores" :key="profesor.profesor_id"
:value="`(${profesor.profesor_clave}) ${profesor.profesor_nombre}`">
</datalist>
</div>
</div>
<div class="form-group row" v-scope ="{
input_faltas: true,
}">
<label for="porcentaje" class="col-4 col-form-label" id="facultad">
<div class="custom-control custom-switch">
<span class="mr-5" :class="{'text-muted': input_faltas}">Número</span>
<input type="checkbox" class="custom-control-input" id="faltas_porcentaje"
v-model="input_faltas" @change="<? if ($user->facultad['facultad_id']) { ?> filter.facultad = <?= $user->facultad['facultad_id'] ?>; <? } ?> filter.porcentaje = input_faltas ? 10 : 0; filter.faltas = input_faltas ? 0 : 1">
<label class="custom-control-label" for="faltas_porcentaje"
:class="{'text-muted': !input_faltas}">
Porcentaje
</label>
</div>
de faltas
</label>
<div class="col-6">
<div id="porcentaje" class="datalist datalist-select mb-1 w-100" V-if="input_faltas">
<div class="datalist-input">
Selecciona una porcentaje
</div>
<span class="icono ing-buscar"></span>
<ul style="display:none">
<li class="datalist-option d-none" data-id="-1">
Selecciona un porcentaje
</li>
<li class="datalist-option"
v-for="porcentaje in Array.from({length: 5}, (_, i) => (i + 1) * 10)"
:key="facultad.facultad_id" :data-id="facultad.facultad_id"
<? if ($user->facultad['facultad_id']) { ?>
@click="filter.porcentaje = porcentaje; filter.facultad = <?= $user->facultad['facultad_id'] ?>;"
<? } else { ?>
@click="filter.porcentaje = porcentaje;"
<? } ?> >
{{ porcentaje }}%
</li>
</ul>
<input type="hidden" id="facultad_id" name="id">
</div>
<input type="number" class="form-control" v-model="filter.faltas" v-else
@change="" min="1" max="100" step="1"
<? if ($user->facultad['facultad_id']) { ?>
@click="filter.facultad = <?= $user->facultad['facultad_id'] ?>"
<? } ?>
placeholder="Número de faltas">
</div>
</div>
<div class="form-group row">
<label for="porcentaje" class="col-4 col-form-label" id="facultad">Faltas</label>
<div class="col-6">
<div class="form-row justify-content-center align-items-center text-center">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="tipoFaltas"
v-model="filter.tipoFaltas" @change="">
<label class="custom-control-label" for="tipoFaltas"
:class="{'text-muted': !filter.tipoFaltas}">
Faltas del {{ filter.tipoFaltas ? 'Supervisor' : 'Profesor' }}
</label>
</div>
</div>
</div>
</div>
<div class="form-row justify-content-center align-items-center">
<div class="col-auto">
<button type="button" class="btn btn-success" @click="toExcel()" :disabled="!faltas.length" :class="{'disabled': !faltas.length}">
<i class=" ing-descarga"></i>
Exportar a Excel</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" @click="refresh()" :disabled="(filter.facultad <= 0) || (filter.faltas <= 0 && filter.porcentaje <= 0)" :class="{'disabled': !filter.facultad || !filter.faltas && !filter.porcentaje}">
<i class=" ing-buscar"></i>
Buscar faltas
</button>
</div>
</div>
</div>
</div>
</div>
<div class="table-responsive marco" v-if="faltas.length > 0" v-scope="{orderBy: [
{column: 'Profesor', order: 'asc'},
{column: 'Faltas', order: 'desc'},
{column: 'Total', order: 'desc'},
{column: 'Porcentaje', order: 'desc'},
]}">
<table class="table table-hover table-striped table-bordered table-sm">
<thead class="thead-dark">
<tr>
<th scope="col" class="text-center align-middle px-2" v-for="column in orderBy">
{{ column.column }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="falta in faltas.filter(({profesor}) => filter.profesor ? (profesor.profesor_nombre.toLowerCase().includes(filter.profesor.toLowerCase()) || filter.profesor.toLowerCase().includes(`(${profesor.profesor_clave}) ${profesor.profesor_nombre}`.toLowerCase())) : true)"
:key="`flata-${falta.profesor_id}`">
<td class="align-middle px-2">
<strong>{{ falta.profesor.profesor_clave }}</strong>
{{ falta.profesor.profesor_nombre }}
</td>
<td class="align-middle px-2 text-center">
{{ falta.faltas }}
</td>
<td class="align-middle px-2 text-center">
{{ falta.total }}
</td>
<td class="align-middle px-2 text-center">
{{ falta.porcentaje }}%
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal" tabindex="-1" id="cargando" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Cargando datos...</h4>
</div>
<div class="modal-body container">
<div class="row">
<div class="col-12 text-center">
<span class="spinner-border spinner-border-lg"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" id="mensaje">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{mensaje.titulo}}</h4>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body container">
<div class="row">
<div class="col-12 text-center">
{{mensaje.texto}}
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- <script src=" js/datalist.js"></script> -->
<script src="js/datepicker-es.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="js/faltas.js?<?= rand(0, 2) ?>" type="module"></script>
<script src="js/scrollables.js"></script>
</body>
</html>

84
js/faltas.js Normal file
View File

@@ -0,0 +1,84 @@
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module';
const filter = reactive({
facultad: -1,
profesor: '',
porcentaje: 0,
faltas: 0,
tipoFaltas: true,
});
const app = createApp({
filter,
facultades: [],
profesores: [],
faltas: [],
mensaje: {
titulo: '',
texto: '',
},
async refresh() {
if (filter.facultad == -1 || (filter.porcentaje < 10 && filter.faltas < 1)) {
console.log('Facultad: ', filter.facultad, 'Porcentaje: ', filter.porcentaje, 'Faltas: ', filter.faltas);
return;
}
$('#cargando').modal('show');
try {
this.faltas = await fetch(`action/profesor_faltas.php?facultad=${this.filter.facultad}&${this.filter.tipoFaltas ? 'supervisor' : 'profesor'}&${this.filter.faltas > 0 ? 'faltas' : 'porcentaje'}=${this.filter.faltas > 0 ? this.filter.faltas : this.filter.porcentaje}`).then(res => res.json());
if (this.faltas.error) {
$('.modal#mensaje').modal('show');
this.mensaje.titulo = 'Información';
this.mensaje.texto = this.faltas.error;
}
} catch (error) {
$('.modal#mensaje').modal('show');
this.mensaje.titulo = 'Error';
this.mensaje.texto = 'No se pudo cargar los datos';
}
finally {
$('#cargando').modal('hide');
}
},
async toExcel() {
if (filter.facultad == -1 || filter.porcentaje < 10) {
return;
}
$('#cargando').modal('show');
try {
const response = await fetch(`export/faltas_excel.php`, {
method: 'POST',
body: JSON.stringify(this.faltas.map(falta => ({
'profesor_clave': falta.profesor.profesor_clave,
'profesor_correo': falta.profesor.profesor_correo,
'profesor_nombre': falta.profesor.profesor_nombre,
'faltas': falta.faltas,
'porcentaje': `${falta.porcentaje}%`,
'total': falta.total,
}))),
})
const blob = await response.blob();
window.saveAs(blob, `faltas_${this.facultades.find(facultad => facultad.facultad_id == filter.facultad).facultad_nombre}_${new Date().toISOString().slice(0, 10)}.xlsx`);
} catch (error) {
$('.modal#mensaje').modal('show');
this.mensaje.titulo = 'Error';
this.mensaje.texto = 'No se pudo cargar los datos';
console.log('Error: ', error);
}
finally {
$('#cargando').modal('hide');
}
},
async mounted() {
try {
this.facultades = await fetch('action/action_facultad.php').then(res => res.json());
this.profesores = await fetch('action/action_profesor.php').then(res => res.json());
} catch (error) {
$('.modal#mensaje').modal('show');
this.mensaje.titulo = 'Error';
this.mensaje.texto = 'No se pudo cargar los datos';
console.log('Error: ', error);
}
}
}).mount('#app');

38
ts/faltas.ts Normal file
View File

@@ -0,0 +1,38 @@
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module';
// define that $ has type any
declare const $: any;
const filter = reactive({
facultad: -1,
profesor: '',
porcentaje: 0
});
const app = createApp({
filter,
facultades: [],
profesores: [],
faltas: [],
openModal() {
const modal = document.getElementById('cargando');
$(modal).modal('show');
},
closeModal() {
const modal = document.getElementById('cargando');
$(modal).modal('hide');
},
async refresh() {
if(filter.facultad == -1 || filter.porcentaje < 10) {
return;
}
this.openModal();
this.faltas = await fetch(`action/profesor_faltas.php?facultad=${this.filter.facultad}&profesor=${this.filter.profesor}&porcentaje=${this.filter.porcentaje}`).then(res => res.json());
this.closeModal();
},
async mounted() {
this.facultades = await fetch('action/action_facultad.php').then(res => res.json());
this.profesores = await fetch('action/action_profesor.php').then(res => res.json());
}
}).mount('#app');