92 Commits

Author SHA1 Message Date
IO d1bf9f6086 fixed error mesa 2026-03-10 17:47:26 -05:00
IO 9752ccce6b fixed table againd 2026-03-04 18:53:49 -06:00
IO 4ff4349fa7 modified table and fixed chow numTable 2026-03-04 15:00:47 -06:00
IO 7155f8c7b1 added new metadata tittle 2026-03-03 17:55:34 -06:00
IO b4eb945b40 merge axel 2026-03-03 17:40:44 -06:00
IO 59f8fa673d Merge branch 'Axel' of repositorio.acatlan.unam.mx:IO/front-AT 2026-03-03 17:36:15 -06:00
IO c69aa009b7 fixed size 2026-03-03 17:36:06 -06:00
Tyrannuss 6c80ecf3db se agrego la vista para el monto total por periodo y tipo de servicio 2026-03-03 17:26:16 -06:00
IO 1de700902a fixed name and headers 2026-03-02 18:38:14 -06:00
IO 32bbfe107f fixed view errors 2026-03-02 16:27:55 -06:00
IO c229b819d5 added new time 2026-03-02 16:14:22 -06:00
IO 34677f2d82 fixed select in active and the lock now can register ticket 1 month later 2026-03-02 15:01:53 -06:00
IO 1a632f15b9 new function lock 2026-03-02 14:17:43 -06:00
IO 9fea87e62f Merge branch 'Carlos' of repositorio.acatlan.unam.mx:IO/front-AT 2026-03-02 11:49:43 -06:00
IO 5fbe8982f6 builded 2026-03-02 11:47:56 -06:00
IO 5af56f4a12 fixed image whith / 2026-03-02 11:46:56 -06:00
IO 7c92ef6415 fixed minutos 2026-03-02 11:40:22 -06:00
IO 55f700f20a fixed checkboxMesa to separate bitacora 2026-03-02 11:36:46 -06:00
IO eb5f6b77c4 added headers to all axios 2026-03-02 10:34:42 -06:00
IO 46357ee7e0 added more headers 2026-02-27 19:52:23 -06:00
IO 805f4835a3 git added new style , little style 2026-02-27 19:15:56 -06:00
IO c2aba808ad new style now ist more little 2026-02-27 18:11:03 -06:00
frcarlos 552353f4d7 PeriodoPage 2026-02-27 16:30:52 -05:00
IO 1027bd0673 added new headers and added table in asignation table 2026-02-27 11:27:52 -06:00
frcarlos 3c44aa833e merge 2026-02-27 10:06:03 -05:00
IO 0ddb17a23f added new token 2026-02-26 14:38:28 -06:00
IO 08386f7ae5 fixed searchUser 2026-02-26 13:52:47 -06:00
IO 80be76f410 style 2026-02-26 13:43:43 -06:00
IO f19b0d1e11 value = null 2026-02-26 13:35:29 -06:00
frcarlos 649ebe6ab9 Periodo conexion 2026-02-26 12:24:51 -05:00
IO c0cf9ecda0 i thing i fix the impressions 2026-02-26 09:22:39 -06:00
IO b7aa2fa66b tring to put token 2026-02-25 19:27:04 -06:00
frcarlos 6b687a8be2 new view is create Periodo 2026-02-25 20:22:19 -05:00
frcarlos 9315bdcb22 merge 2026-02-25 12:53:04 -05:00
IO b0d186784f added show table in monitor 2026-02-24 18:47:23 -06:00
IO edb2fd2664 added new image 2026-02-24 18:29:42 -06:00
IO 01eafd4038 modified restrictions to acept 0 in numAcount 2026-02-24 17:24:48 -06:00
IO 0d37fb3536 modified to construct the component impressions 2026-02-24 16:57:23 -06:00
IO ee4a4be907 fixed checkbox 2026-02-24 11:53:29 -06:00
IO ea83311f4d changed background color 2026-02-23 17:38:34 -06:00
IO 1caafbc597 added new route to rol 2 and 3 2026-02-23 15:00:49 -06:00
IO 6c7c477d6a delete hardcode 2026-02-23 14:43:21 -06:00
IO f3d19962b9 axios costos 2026-02-23 14:37:28 -06:00
IO 661121a69d added sanction 2026-02-23 14:14:24 -06:00
IO 6cf361f281 export default 2026-02-23 13:46:09 -06:00
IO bce1d473a2 modified request 2026-02-23 13:31:02 -06:00
IO d53561e339 delete all fetch 2026-02-23 12:47:08 -06:00
IO d0b8cc3062 fixed checkbox equipo and mesa and delete fetch 2026-02-23 12:23:46 -06:00
IO a0cd2da717 apply DRY to alumno-sancio 2026-02-23 12:09:37 -06:00
IO 8f99440207 apply DRY principle by removing duplicated logic 2026-02-23 11:51:39 -06:00
IO 080ce4137f edded new button delete to programs 2026-02-23 11:26:00 -06:00
IO 8c6ca40a23 added new login 2026-02-23 10:01:45 -06:00
IO e4082c274c uninstall toast 2026-02-23 09:47:40 -06:00
IO d4b18b372e delete all toast and modified botton add equipo 2026-02-23 09:41:40 -06:00
IO 01e463d560 fixed barnavigation 2026-02-20 19:04:07 -06:00
IO 68f05fb517 fixing proxy 2026-02-20 18:58:04 -06:00
IO d3e59e63f7 Merge branch 'Carlos' of repositorio.acatlan.unam.mx:IO/front-AT into Develop 2026-02-20 14:20:25 -06:00
IO 39a10ce46f modified proxy to use jwt_secret 2026-02-20 14:19:51 -06:00
frcarlos 960769ea33 sweetalertcomplet 2026-02-20 14:48:26 -05:00
IO 57714ff36c modified impresions to role = 5 2026-02-20 14:46:14 -05:00
IO b7bee2d1fd fixed styles 2026-02-20 14:46:14 -05:00
IO ebc718daf2 added new animation 2026-02-20 12:07:44 -06:00
IO 4743eec697 added new images and new component call BackgroundRotator 2026-02-20 12:05:20 -06:00
IO 65fcf5eaff modified impresions to role = 5 2026-02-19 18:59:21 -06:00
IO b185a96eee alertsweet 2026-02-19 18:56:56 -06:00
IO b4222f7d9a delete toast 2026-02-19 18:05:45 -06:00
IO 3e85cb862d fixed styles 2026-02-19 17:52:17 -06:00
IO 8b803cbf81 new conexion to api 2026-02-19 15:25:50 -06:00
IO 6071f38af2 added bitacora mesa 2026-02-19 10:48:45 -06:00
IO f568bb307c now ist working bitacora alumno and bitacora equipo 2026-02-19 09:29:33 -06:00
IO 6ab123c87b new style to grid and to global 2026-02-19 08:30:45 -06:00
IO 30de190e22 added new alerts and modified restrictions to the lock 2026-02-19 08:04:54 -06:00
IO ff08c85a50 order by name 2026-02-18 19:30:19 -06:00
IO d24b0bebb4 Merge branch 'Carlos' of repositorio.acatlan.unam.mx:IO/front-AT into Develop 2026-02-18 19:19:43 -06:00
IO 608800f8fb alertlock 2026-02-18 19:16:00 -06:00
IO 8688bb028c fixed mesage to create ticket 2026-02-18 19:14:37 -06:00
IO 6abbe700d6 SweetAlert erros 2026-02-18 19:01:31 -06:00
IO 9f64f1305f m 2026-02-18 18:24:42 -06:00
IO 13be8d9c35 fixing styles 2026-02-18 17:50:15 -06:00
IO 54a2046d06 renamed middleware to proxy 2026-02-18 12:14:28 -06:00
IO e2c4734306 just fixed styles 2026-02-17 19:16:57 -06:00
IO 4de5a7d27d add current date and tyrannuss 2026-02-17 17:27:53 -06:00
IO 45d60d87ec fixed get alumno-sancion 2026-02-17 17:14:27 -06:00
IO 90bdb482cd added conection to api in inscritos 2026-02-17 15:04:16 -06:00
IO 62afe827dc fixed build 2026-02-16 19:22:34 -06:00
IO fc91b0a822 fixed to locate date 2026-02-16 19:04:18 -06:00
IO 6e15f279b0 fixed button 2026-02-16 18:47:54 -06:00
IO c989b54fdc fixed types 2026-02-16 18:45:05 -06:00
IO 05b766afe1 fixing create sanctions and delete sanctions 2026-02-16 18:16:04 -06:00
IO 0582269c31 fixed color titles 2026-02-13 17:48:35 -06:00
IO 812c8fbbe1 modified creadores 2026-02-13 16:10:05 -06:00
IO 5af0385352 added new view 2026-02-12 18:53:29 -06:00
127 changed files with 3978 additions and 3814 deletions
@@ -76,7 +76,7 @@
.checkbox-grid label {
display: inline-flex;
align-items: center;
font-size: 13px;
font-size: 1.4rem;
cursor: pointer;
}
+5 -3
View File
@@ -2,17 +2,19 @@ import Areas from "@/app/Components/ActivosMantenimiento/Areas";
import MesasDisponibles from "@/app/Components/ActivosMantenimiento/MesasDisponibles";
import TableEquipos from "@/app/Components/Equipos/tableequipos";
import Toggle from "@/app/Components/Global/Toggle/Toggle";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "ActivosMantenimiento",
};
export default async function Page(props: {
searchParams?: Promise<{
key?: string;
numAcount?: string;
machine?: string;
}>;
}) {
const params = await props.searchParams;
const key = params?.key && params.key;
const numAcount = params?.numAcount ? params.numAcount : null;
return (
<section className="containerSection">
+74 -21
View File
@@ -1,14 +1,14 @@
"use client";
import { useState } from "react";
import toast from "react-hot-toast";
import { useRouter } from "next/navigation";
import { envConfig } from "@/app/lib/config";
import SelectionCo from "@/app/Components/Selection/SelectionCo";
import axios from "axios";
import { envConfig } from "@/app/lib/config";
import Cookies from "js-cookie";
import Swal from "sweetalert2";
import "./addTime.css";
const PLATAFORMA_MAP: Record<string, number> = {
@@ -35,7 +35,7 @@ export default function AddTime({
const [folio, setFolio] = useState("");
const [amount, setAmount] = useState("");
const [lock, setLock] = useState<boolean>(true);
const todayISO = new Date().toISOString().split("T")[0];
const todayISO = new Date().toLocaleDateString("en-CA");
const [date, setDate] = useState(todayISO);
const day = new Date();
@@ -43,32 +43,62 @@ export default function AddTime({
const month = day.getMonth();
const today = day.getDate();
const minFecha = new Date(year, month, 1).toISOString().split("T")[0];
let minMount = month
if (!lock) {
minMount = month - 1
}
const minFecha = new Date(year, minMount, 1).toISOString().split("T")[0];
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
const handleSaveReceipt = async () => {
if (!numAcount) {
toast.error("Busca de nuevo al estudiante");
if (numAcount == null) {
Swal.fire({
title: "Busca de nuevo al estudiante!",
icon: "error",
draggable: true
});
return;
}
if (!plataformaSeleccionada) {
toast.error("Selecciona una plataforma");
Swal.fire({
title: "Selecciona una plataforma!",
icon: "error",
draggable: true
});
return;
}
if (!folio) {
toast.error("Ingresa el folio del ticket");
Swal.fire({
title: "Ingresa el folio del ticket!",
icon: "error",
draggable: true
});
return;
}
if (!amount) {
toast.error("Coloca el monto a depositar");
Swal.fire({
title: "Coloca el monto a depositar!",
icon: "error",
draggable: true
});
return;
}
if (!date) {
toast.error("Coloca la fecha");
Swal.fire({
title: "Coloca la fecha!",
icon: "error",
draggable: true
});
return;
}
@@ -90,19 +120,27 @@ export default function AddTime({
{ headers },
);
toast.success("Tiempo Guardado");
Swal.fire({
title: "Tiempo Guardado!",
title: "Tiempo Guardado Correctamente!",
icon: "success",
draggable: true
draggable: true,
});
setFolio("");
setAmount("");
setDate(todayISO);
router.refresh();
} catch (err: any) {
toast.error(err.message || "Error al guardar");
} catch (error: any) {
const msg =
error.response?.data?.message ||
error.message ||
"Error desconocido al crear recibo";
Swal.fire({
title: msg,
icon: "error",
draggable: true,
});
}
};
@@ -148,7 +186,23 @@ export default function AddTime({
const numericValue = parseFloat(value);
if (lock && numericValue > 1000) {
toast.error("El monto no puede superar $1000.00");
Swal.fire({
title:
"El monto no puede superar $1000.00 Desbloquea el candado !",
icon: "error",
draggable: true,
});
return;
}
if (numericValue > 10000) {
Swal.fire({
title: "El monto no puede superar $10000.00 pesos",
icon: "error",
draggable: true,
});
return;
}
@@ -171,7 +225,7 @@ export default function AddTime({
/>
</div>
<div className="containerButton" style={{ position: 'relative' }}>
<div className="containerButton" style={{ position: "relative" }}>
<button className="button buttonSearch" type="submit">
Guardar
</button>
@@ -181,15 +235,14 @@ export default function AddTime({
className={`button buttonCancel ${lock ? "buttonLock" : "buttonOpenLock"
}`}
onClick={() => {
setLock((prev) => !prev)
setAmount('');
setLock((prev) => !prev);
setAmount("");
}}
/>
</div>
</div>
</form>
</div>
);
}
//IO
//IO
+23 -5
View File
@@ -23,21 +23,21 @@
width: 100%;
}
.buttonLock{
.buttonLock {
padding: 0.8rem;
position: absolute;
right: 0;
right: 100px;
background-color: #d32f2f;
}
.buttonOpenLock{
.buttonOpenLock {
padding: 0.8rem;
position: absolute;
right: 0;
right: 100px;
background-color: #8cd32f;
}
.buttonOpenLock:hover{
.buttonOpenLock:hover {
background-color: #78b32c;
}
@@ -63,6 +63,24 @@
background-repeat: no-repeat;
}
@media(max-height:800px) {
.addTime {
margin-top: 0.5rem;
padding: 0.5rem;
max-width: 320px;
}
.firstPartInformationTime {
max-width: 320px;
}
}
@media(max-height:800px) {
.buttonLock {
padding: 0.5rem;
}
}
@media (max-width: 800px) {
.containeInformationTime {
flex-direction: column;
+8 -2
View File
@@ -4,12 +4,18 @@ import ShowError from "@/app/Components/Global/ShowError";
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
import "./addTime.css";
import AddTime from "./Addtime";
import Table from "@/app/Components/Global/table";
import "./addTime.css";
import { Metadata } from "next";
const headers = ["Inscrito", "Tiempo", "Confirmó"];
export const metadata: Metadata = {
title: "AgregarTiempo",
};
export default async function Page(props: {
searchParams?: Promise<{ numAcount: string }>;
}) {
@@ -46,7 +52,7 @@ export default async function Page(props: {
return (
<section className="containerSection">
{errorMessage && <ShowError message={errorMessage} />}
{errorMessage && <ShowError key={Date.now()} message={errorMessage} />}
<h2 className="title">AGREGAR TIEMPO</h2>
+5
View File
@@ -1,6 +1,11 @@
import RegisterAlta from "@/app/Components/Alta/registerAlta";
import "./style.css";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Alta",
};
export default async function Page(props: {
searchParams?: Promise<{
+63 -45
View File
@@ -2,13 +2,15 @@
import { useEffect, useState } from "react";
import { envConfig } from "@/app/lib/config";
import toast from "react-hot-toast";
import { getEquipoByCount } from "@/app/lib/getEquipoByCount";
import { getMesaByCount } from "@/app/lib/getMesaByCount";
import "./asignacion.css";
import Swal from "sweetalert2";
import axios from "axios";
import Cookies from "js-cookie";
import "./asignacion.css";
type Props = {
inscripcion: any[];
@@ -23,6 +25,10 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
const [open, setOpen] = useState(false);
const [tiempo, setTiempo] = useState<number>(15);
const [bitacora, setBitacora] = useState<[] | null>([]);
const [error, setError] = useState<string>("Equipo");
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const fetchByCuenta = async (idCuenta: number) => {
const [result, resultMesa] = await Promise.all([
@@ -34,17 +40,23 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
setBitacora(null);
} else {
setBitacora([]);
if (!result?.error) toast.error("Ya cuentas con un equipo asignado");
if (!resultMesa?.error) toast.error("Ya cuentas con una mesa asignada");
if (!result?.error) {
setError("Equipo");
return;
}
if (!resultMesa?.error) {
setError("Mesa");
return;
}
}
};
useEffect(() => {
if (!numAcount) return;
const idCuenta = numAcount;
if (isNaN(idCuenta)) return;
if (isNaN(numAcount)) return;
fetchByCuenta(idCuenta);
fetchByCuenta(numAcount);
}, [numAcount]);
useEffect(() => {
@@ -55,7 +67,11 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
);
if (!todasAsistieron) {
toast.error("El alumno no asistió a todas las pláticas");
Swal.fire({
title: "El alumno no asistió a todas las pláticas!",
icon: "error",
draggable: true,
});
setPuedeContinuar(false);
return;
}
@@ -64,27 +80,25 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
}, [inscripcion]);
useEffect(() => {
if (!puedeContinuar || !numAcount) return;
if (!puedeContinuar || !numAcount || !(error === "Equipo" || error === "Mesa")) return;
const fetchEquipos = async () => {
try {
setLoadingEquipos(true);
const res = await fetch(
const res = await axios.get(
`${envConfig.apiUrl}/equipo/student/${numAcount}`,
{
cache: "no-store",
},
{ headers },
);
if (!res.ok) {
throw new Error("No se pudieron cargar los equipos");
}
const data = await res.json();
setEquipos(data);
} catch (error) {
toast.error("Error al cargar equipos disponibles");
setEquipos(res.data);
} catch (err) {
Swal.fire({
title: "Error al cargar equipos disponibles!",
icon: "error",
draggable: true,
});
} finally {
setLoadingEquipos(false);
}
@@ -99,7 +113,11 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
e.preventDefault();
if (!equipoSeleccionado) {
toast.error("Selecciona un equipo");
Swal.fire({
title: "Selecciona un equipo!",
icon: "error",
draggable: true,
});
return;
}
@@ -111,32 +129,31 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
id_alumno_inscrito: inscripcion[0].id_alumno_inscrito,
};
const res = await fetch(`${envConfig.apiUrl}/bitacora`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
await axios.post(`${envConfig.apiUrl}/bitacora`, body, { headers }
);
setBitacora([]);
Swal.fire({
title: "Equipo asignado correctamente!",
icon: "success",
draggable: true,
});
} catch (err) {
Swal.fire({
title: "No se pudo asignar el equipo!",
icon: "error",
draggable: true,
});
if (!res.ok) {
throw new Error("Error al asignar equipo");
}
setBitacora([])
toast.success("Equipo asignado correctamente");
Swal.fire({
title: "Equipo asignado correctamente!",
icon: "success",
draggable: true,
});
} catch (error) {
toast.error("No se pudo asignar el equipo");
}
};
if (bitacora) {
return <></>;
return error === "Mesa"
? <p style={{ margin: "1rem", color: "red" }}>Ya cuentas con una mesa asignada</p>
: error === "Equipo"
? <p style={{ margin: "1rem", color: "red" }}>Ya cuentas con un equipo asignado</p>
: null;
}
return (
@@ -152,6 +169,8 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
<option value={40}>40 minutos</option>
<option value={60}>60 minutos</option>
<option value={90}>90 minutos</option>
<option value={120}>120 minutos</option>
</select>
<label>Seleccione un equipo</label>
@@ -165,8 +184,7 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
onClick={() => !loadingEquipos && setOpen(!open)}
>
{equipoSeleccionado ? (
<span
>
<span>
{equipoSeleccionado.ubicacion}{" "}
{equipoSeleccionado.nombre_equipo}
</span>
@@ -4,8 +4,8 @@
position: relative;
flex: 1;
width: 100%;
font-size: 14px;
margin-bottom: 1rem;
font-size: 1.4rem;
max-width: 500px;
}
.customSelect.disabled {
+9 -25
View File
@@ -6,33 +6,18 @@ import Information from "@/app/Components/Global/Information/information";
import ShowError from "@/app/Components/Global/ShowError";
import Table from "@/app/Components/Global/table";
import { envConfig } from "@/app/lib/config";
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
import PlaticaGate from "./PlaticaGate";
import "@/app/globals.css";
async function getInscripcion(idCuenta: number) {
try {
const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito/${idCuenta}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
});
if (!res.ok) {
throw new Error(`Error ${res.status}: ${res.statusText}`);
}
return await res.json();
} catch (error: any) {
return { error: error.message || "No se pudo cargar la inscripción" };
}
}
import { GetRegisterStudentSancion } from "@/app/lib/GetRegisterStudentSancion";
import { Metadata } from "next";
const headers = ["Inscrito", "Tiempo", "Confirmó"];
export const metadata: Metadata = {
title: "AsignacionEquipo",
};
export default async function Page(props: {
searchParams?: Promise<{
key?: string;
@@ -53,7 +38,7 @@ export default async function Page(props: {
if (numAcount) {
const idCuenta = parseInt(numAcount);
const result = await GetRegisterStudent(idCuenta);
const result = await GetRegisterStudentSancion(idCuenta);
if (result.error) {
errorMessage = `${result.error}`;
} else {
@@ -63,10 +48,9 @@ export default async function Page(props: {
rawPlatica?.type === "Buffer"
? Boolean(rawPlatica.data[0])
: Boolean(rawPlatica);
console.log(result);
}
const inscResult = await getInscripcion(idCuenta);
const inscResult = await GetRegisterStudent(idCuenta);
if (!inscResult.error && Array.isArray(inscResult)) {
inscripcion = inscResult;
} else {
@@ -124,7 +108,7 @@ export default async function Page(props: {
label: "Cancelar tiempo",
content: (
<>
<CheckBoxEquipo numAcount={params?.numAcount} machine={params?.machine} />
<CheckBoxEquipo numAcount={numAcount} machine={machine} />
</>
),
},
@@ -0,0 +1,70 @@
"use client";
import { useEffect, useState } from "react";
import styles from "../Monitor/Page.module.css";
import axios from "axios";
import Cookies from "js-cookie";
type Mesa = {
id_mesa: number;
ocupado: number;
};
export default function TableMesas() {
const [mesas, setMesas] = useState<Mesa[]>([]);
const [loading, setLoading] = useState(true);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const fetchMesas = async () => {
try {
const res = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/mesa/usoWhitout`,
{ headers },
);
setMesas(res.data);
} catch (error) {
console.error("Error cargando mesas:", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchMesas();
}, []);
if (loading) {
return <div className={styles.tableContainer}>
<table className={styles.machineTable}>
<thead>
<tr>
<th>Cargando mesas...</th>
</tr>
</thead>
</table>
</div>
}
return (
<div className={styles.tableContainer}>
<table className={styles.machineTable}>
<thead>
<tr>
<th>ID Mesa</th>
</tr>
</thead>
<tbody>
{mesas.map((mesa) => (
<tr key={mesa.id_mesa} className={!mesa.ocupado ? '' : styles.ocupado}>
<td>{mesa.id_mesa}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
@@ -0,0 +1,10 @@
.tableTable{
margin: 0.1rem;
width: 100%;
max-width: 250px;
}
.asignacion{
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
+50 -52
View File
@@ -4,28 +4,23 @@ import Information from "@/app/Components/Global/Information/information";
import SearchUser from "@/app/Components/Global/SearchUser/searchUser";
import ShowError from "@/app/Components/Global/ShowError";
import Toggle from "@/app/Components/Global/Toggle/Toggle";
import TableMesas from "./TableMesas";
import { envConfig } from "@/app/lib/config";
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
import { GetRegisterStudentSancion } from "@/app/lib/GetRegisterStudentSancion";
import { Metadata } from "next";
async function getInscripcion(idCuenta: number) {
try {
const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito/${idCuenta}`, {
cache: "no-store",
});
import './asignacion.css'
if (!res.ok) throw new Error("Error al cargar inscripción");
return await res.json();
} catch (error: any) {
return { error: error.message };
}
}
export const metadata: Metadata = {
title: "AsignacionMesas",
};
export default async function Page(props: {
searchParams?: Promise<{
key?: string;
numAcount?: string;
table?:number
table?: string
}>;
}) {
const params = await props.searchParams;
@@ -33,7 +28,6 @@ export default async function Page(props: {
const numAcount = params?.numAcount ?? null;
const table = params?.table ?? null;
let student: any = null;
let platica: any = null;
let inscripcion: any[] = [];
@@ -42,7 +36,7 @@ export default async function Page(props: {
if (numAcount) {
const idCuenta = parseInt(numAcount);
const result = await GetRegisterStudent(idCuenta);
const result = await GetRegisterStudentSancion(idCuenta);
if (result.error) {
errorMessage = `${result.error}`;
} else {
@@ -52,11 +46,10 @@ export default async function Page(props: {
rawPlatica?.type === "Buffer"
? Boolean(rawPlatica.data[0])
: Boolean(rawPlatica);
console.log(result);
}
const inscResult = await getInscripcion(idCuenta);
const inscResult = await GetRegisterStudent(idCuenta);
if (!inscResult.error && Array.isArray(inscResult)) {
inscripcion = inscResult;
} else {
@@ -66,45 +59,50 @@ export default async function Page(props: {
return (
<section className="containerSection">
{errorMessage && <ShowError message={errorMessage} />}
{errorMessage && <ShowError key={Date.now()} message={errorMessage} />}
<h2 className="title"> ASIGNACIÓN DE MESAS </h2>
<Toggle
defaultView={key}
options={[
{
key: "Asignar",
label: "Asignar mesa",
content: (
<>
<SearchUser value={numAcount} />
<div className="asignacion">
<Toggle
defaultView={key}
options={[
{
key: "Asignar",
label: "Asignar mesa",
content: (
<>
<SearchUser value={numAcount} />
{student && (
<>
<div>
<Information
NoCuenta={student.id_cuenta}
nombre={student.nombre}
{student && (
<>
<div>
<Information
NoCuenta={student.id_cuenta}
nombre={student.nombre}
/>
</div>
<AsignacionMesas
idCuenta={student.id_cuenta}
inscripcion={inscripcion}
/>
</div>
<AsignacionMesas
idCuenta={student.id_cuenta}
inscripcion={inscripcion}
/>
</>
)}
</>
),
},
{
key: "Liberar",
label: "Cancelar mesa",
content: <CheckBoxMesa numAcount={numAcount} table={table} />,
},
]}
/>
</>
)}
</>
),
},
{
key: "Liberar",
label: "Cancelar mesa",
content: <CheckBoxMesa numAcount={numAcount} table={table} />,
},
]}
/>
<div className="tableTable">
<TableMesas key={Date.now()}/>
</div>
</div>
</section>
);
}
+19 -6
View File
@@ -7,24 +7,31 @@ import SearchUserWithDate from "@/app/Components/Global/SearchUser/SearchUserWit
import ShowError from "@/app/Components/Global/ShowError";
import Toggle from "@/app/Components/Global/Toggle/Toggle";
import QuitarSancion from "@/app/Components/QuitarSancion/QuitarSancion";
import { GetStudent } from "@/app/lib/getStudent";
import { GetSancionByStudent } from "@/app/lib/getStudent copy";
import { GetStudentWhitoutSancion } from "@/app/lib/getStudentWhitoutSancion";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "BitacoraSanciones",
};
export default async function Page(props: {
searchParams?: Promise<{
key?: string;
numAcount?: string;
date?: string;
}>;
}) {
const params = await props.searchParams;
const key = params?.key && params.key;
const numAcount = params?.numAcount ? params.numAcount : null;
const date = params?.date ? params.date : null;
let student: any = null;
let errorMessage = "";
if (numAcount) {
const result = await GetStudent(parseInt(numAcount));
const result = await GetStudentWhitoutSancion(parseInt(numAcount));
if (result.error) {
errorMessage = `${result.error}`;
@@ -47,7 +54,7 @@ export default async function Page(props: {
label: "Bitácora equipo",
content: (
<>
<BitacoraEquipo />
<BitacoraEquipo date={date}/>
</>
),
},
@@ -57,7 +64,12 @@ export default async function Page(props: {
content: (
<>
<SearchUserWithDate value={numAcount} />
<BitacoraAlumno />
<BitacoraAlumno
key={`${numAcount}-${date}`}
numAcount={numAcount}
date={date}
/>
</>
),
},
@@ -75,6 +87,7 @@ export default async function Page(props: {
label: "Sanciones",
content: (
<>
<SearchUser value={numAcount} />
<Sanciones student={student} />
</>
),
@@ -86,7 +99,7 @@ export default async function Page(props: {
<>
<div className="containerSection">
<SearchUser value={numAcount} />
{/* <QuitarSancion data={student} /> */}
<QuitarSancion id_cuenta={Number(numAcount)} />
</div>
</>
),
+5
View File
@@ -1,4 +1,9 @@
import ChangePassword from "@/app/Components/auth/ChangePassword/changePassword";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "CambiarPass",
};
export default async function Page(props: {
searchParams?: Promise<{
+120 -88
View File
@@ -5,9 +5,16 @@ import Information from "../../Components/Global/Information/information";
import Impressions from "@/app/Components/Impressions/impressions";
import ShowError from "@/app/Components/Global/ShowError";
import { GetStudent } from "@/app/lib/getStudent";
import { cookies } from "next/headers";
import { getCost } from "@/app/lib/getCost";
import { GetStudentWhitoutSancion } from "@/app/lib/getStudentWhitoutSancion";
import "@/app/globals.css";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Impresiones",
};
export default async function Page(props: {
searchParams?: Promise<{
@@ -19,12 +26,16 @@ export default async function Page(props: {
const key = params?.key && params.key;
const numAcount = params?.numAcount ? params.numAcount : null;
const cookieStore = await cookies();
const role = cookieStore.get("role")?.value || "";
const roleNumber = parseInt(role);
let student: any = null;
let errorMessage = "";
let costos: any[] = [];
if (numAcount) {
const result = await GetStudent(parseInt(numAcount));
const result = await GetStudentWhitoutSancion(parseInt(numAcount));
if (result.error) {
errorMessage = `${result.error}`;
} else {
@@ -32,6 +43,110 @@ export default async function Page(props: {
}
}
let id_servicio: number;
switch (key) {
case "B/N":
id_servicio = 1;
break;
case "color":
id_servicio = 2;
break;
case "Plotter":
id_servicio = 3;
break;
case "Escaner":
id_servicio = 6;
break;
}
try {
const response = await getCost();
costos = response;
} catch (error) {
console.error("Error obteniendo costos:", error);
}
const getCostsByService = (id_servicio: number) => {
return costos
.filter((c) => c.id_servicio === id_servicio)
.map((c) => ({
value: Number(c.costo),
id_costo: c.id_costo,
fecha_registro: c.fecha_registro,
}));
};
let options: any[] = [];
if (student) {
options = [
{
key: "Recibo",
label: "Recibo",
content: <Receipt numAcount={student.id_cuenta} />,
},
{
key: "B/N",
label: "Impresiones B/N",
content: (
<Impressions
costs={getCostsByService(1)}
numAcount={student.id_cuenta}
id_servicio={1}
/>
),
},
{
key: "color",
label: "Impresiones color",
content: (
<Impressions
key={2}
costs={getCostsByService(2)}
numAcount={student.id_cuenta}
id_servicio={2}
/>
),
},
{
key: "Plotter",
label: "Plotter",
content: (
<Impressions
key={3}
costs={getCostsByService(3)}
numAcount={student.id_cuenta}
id_servicio={3}
/>
),
},
{
key: "Escaner",
label: "Escaner",
content: (
<Impressions
costs={getCostsByService(6)}
numAcount={student.id_cuenta}
id_servicio={6}
/>
),
},
];
}
const optionsToUse =
roleNumber === 5
? options.filter(
(option) => option.key === "Recibo" || option.key === "B/N"
)
: options;
return (
<section className="containerSection">
{errorMessage && <ShowError key={Date.now()} message={errorMessage} />}
@@ -47,92 +162,9 @@ export default async function Page(props: {
Carrera={student.carrera.carrera}
Credito={student.credito}
/>
<div style={{ height: "10px" }}></div>
<Toggle
defaultView={key}
options={[
{
key: "Recibo",
label: "Recibo",
content: <Receipt numAcount={student.id_cuenta} />,
},
{
key: "B/N",
label: "Impresiones B/N",
content: (
<Impressions
key={1}
costs={[{ value: 1 }, { value: 2 }]}
numAcount={student.id_cuenta}
id_servicio={1}
/>
),
},
{
key: "color",
label: "Impresiones color",
content: (
<Impressions
key={2}
costs={[
{ value: 4 },
{ value: 5 },
{ value: 6 },
{ value: 7 },
{ value: 8 },
{ value: 10 },
{ value: 12 },
{ value: 14 },
]}
numAcount={student.id_cuenta}
id_servicio={2}
/>
),
},
{
key: "Plotter",
label: "Plotter",
content: (
<Impressions
key={3}
costs={[
{ value: 15 },
{ value: 18 },
{ value: 20 },
{ value: 25 },
{ value: 30 },
{ value: 40 },
{ value: 45 },
{ value: 50 },
{ value: 60 },
{ value: 70 },
{ value: 80 },
{ value: 90 },
{ value: 100 },
{ value: 110 },
{ value: 120 },
{ value: 150 },
{ value: 200 },
]}
numAcount={student.id_cuenta}
id_servicio={3}
/>
),
},
{
key: "Escaner",
label: "Escaner",
content: (
<Impressions
key={4}
costs={[{ value: 1 }, { value: 2 }, { value: 5 }]}
numAcount={student.id_cuenta}
id_servicio={6}
/>
),
},
]}
/>
<Toggle defaultView={key} options={optionsToUse} />
</>
)}
</section>
@@ -15,17 +15,18 @@
.form-container {
gap: 10px;
margin-bottom: 1rem;
}
.form-container label {
font-size: 14px;
font-size: 1.4rem;
display: inline-block;
vertical-align: middle;
}
.form-container select {
font-size: 13px;
padding: 4px 8px;
font-size: 1.4rem;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 6px;
outline: none;
@@ -47,7 +48,8 @@
.checkbox-grid {
display: grid;
grid-template-columns: repeat(3, 240px); /* ancho fijo por columna */
grid-template-columns: repeat(3, 240px);
grid-template-rows: repeat(13, auto);
gap: 10px 20px;
flex-wrap: wrap;
justify-content: start;
@@ -56,7 +58,7 @@
.checkbox-grid label {
display: flex;
align-items: center;
font-size: 13px;
font-size: 1.4rem;
cursor: pointer;
white-space: nowrap;
width: fit-content;
+5
View File
@@ -1,7 +1,12 @@
import { Metadata } from "next";
import InformacionEquipo from "./InformacionEquipo";
import "./informacionequipo.css";
export const metadata: Metadata = {
title: "InformacionEquipo",
};
export default async function Page(props: {
searchParams?: Promise<{
key?: string;
+21 -4
View File
@@ -3,7 +3,7 @@
top: 0;
background-color: #ffffff;
width: 100%;
max-width: 700px;
max-width: 600px;
border-radius: 4px;
padding: 1rem;
margin-top: 1rem;
@@ -23,18 +23,35 @@
max-width: 450px;
}
.restarPass{
margin-top: 2rem;
.restarPass {
margin-top: 3rem;
height: fit-content;
}
@media(max-height:800px) {
.firstPartInformation {
max-width: 320px;
}
.inscripcion {
max-width: 400px;
padding: 0.5rem;
margin-top: 0.5rem;
}
.restarPass {
margin-top: 2rem;
}
}
@media (max-width: 800px) {
.containeInformation {
flex-direction: column;
justify-content: center;
align-items: center;
}
.restarPass{
.restarPass {
margin-top: 0;
}
}
+24 -36
View File
@@ -3,38 +3,20 @@ import Information from "@/app/Components/Global/Information/information";
import ShowError from "@/app/Components/Global/ShowError";
import Table from "@/app/Components/Global/table";
import Inscripcion from "@/app/Components/Receipt/Inscripcion";
import { GetStudent } from "@/app/lib/getStudent";
import { envConfig } from "@/app/lib/config";
import Link from "next/link";
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
import { GetStudentWhitoutSancion } from "@/app/lib/getStudentWhitoutSancion";
import "./inscripcion.css";
import Link from "next/link";
if (!envConfig.apiUrl) {
console.error("API URL is not defined in envConfig");
}
async function getInscripcion(idCuenta: number) {
try {
const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito/${idCuenta}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
});
if (!res.ok) {
throw new Error(`Error ${res.status}: ${res.statusText}`);
}
return await res.json();
} catch (error: any) {
return { error: error.message || "No se pudo cargar la inscripción" };
}
}
import { Metadata } from "next";
const headers = ["Inscrito", "Tiempo", "Confirmó"];
export const metadata: Metadata = {
title: "Inscripcion",
};
export default async function Page(props: {
searchParams?: Promise<{
numAcount: string;
@@ -46,18 +28,24 @@ export default async function Page(props: {
let student: any = null;
let inscripcion: any = null;
let errorMessage = "";
let isSancionado = false;
if (numAcount) {
const idCuenta = parseInt(numAcount);
const studentResult = await GetStudent(idCuenta);
const studentResult = await GetStudentWhitoutSancion(idCuenta);
if (studentResult.error) {
errorMessage = `${studentResult.error}`;
if (studentResult.status === 403) {
isSancionado = true;
}
} else {
student = studentResult;
}
const inscResult = await getInscripcion(idCuenta);
const inscResult = await GetRegisterStudent(idCuenta);
if (!inscResult.error && Array.isArray(inscResult)) {
inscripcion = inscResult;
} else {
@@ -67,12 +55,12 @@ export default async function Page(props: {
const tableData = Array.isArray(inscripcion)
? inscripcion.map((ins) => ({
Inscrito: ins.plataforma?.nombre || "—",
Tiempo: ins.tiempo_disponible
? `${ins.tiempo_disponible} minutos`
: "—",
Confirmó: ins.platica.data?.[0] === 1 ? "sí" : "no",
}))
Inscrito: ins.plataforma?.nombre || "—",
Tiempo: ins.tiempo_disponible
? `${ins.tiempo_disponible} minutos`
: "—",
Confirmó: ins.platica.data?.[0] === 1 ? "sí" : "no",
}))
: [];
const plataformasInscritas = Array.isArray(inscripcion)
@@ -99,7 +87,7 @@ export default async function Page(props: {
/>
</>
)}
{!student && numAcount && (
{!student && numAcount && !isSancionado && (
<Link href={`/Alta?numAcount=${numAcount}`}>
<button
className="button buttonSearch"
+121 -43
View File
@@ -1,79 +1,157 @@
"use client"
"use client";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import { useEffect, useState } from "react";
import axios from "axios";
import Cookies from "js-cookie";
interface Periodo{
semestre:string,
import "./inscripciones.css"
interface Periodo {
id_periodo: number;
semestre: string;
}
export default function Inscripciones() {
const [periodo,setPeriodo]= useState<Periodo[]>([])
interface ApiResponse {
carrera: string;
genero: string | null;
profesor: string;
total: string;
}
useEffect(()=>{
const getPeriodo = async()=>{
const response = await axios.get(`${envConfig.apiUrl}/periodo`)
setPeriodo(response.data)
}
getPeriodo()
},[])
interface TablaRow {
carrera: string;
genero: string | null;
WINDOWS: number;
MACINTOSH: number;
LINUX: number;
PROFESORES: number;
TOTAL: number;
}
type TipoConteo = "WINDOWS" | "MACINTOSH" | "LINUX" | "PROFESORES";
export default function Inscripciones() {
const [periodo, setPeriodo] = useState<Periodo[]>([]);
const [selectedPeriodo, setSelectedPeriodo] = useState<number | null>(null);
const [tablaData, setTablaData] = useState<TablaRow[]>([]);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
const getPeriodo = async () => {
const response = await axios.get(`${envConfig.apiUrl}/periodo`, { headers }
);
setPeriodo(response.data);
};
getPeriodo();
}, []);
const handleBuscar = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedPeriodo) return;
const response = await axios.get(
`${envConfig.apiUrl}/alumno-inscrito/inscritos/${selectedPeriodo}`,
{ headers }
);
const data: ApiResponse[] = response.data;
const agrupado: Record<string, TablaRow> = {};
data.forEach((item) => {
const generoNormalizado =
item.genero === "F" || item.genero === "Femenino"
? "Femenino"
: item.genero === "M" || item.genero === "Masculino"
? "Masculino"
: "-";
const key = `${item.carrera}_${generoNormalizado}`;
if (!agrupado[key]) {
agrupado[key] = {
carrera: item.carrera,
genero: generoNormalizado,
WINDOWS: 0,
MACINTOSH: 0,
LINUX: 0,
PROFESORES: 0,
TOTAL: 0,
};
}
const tipo = item.profesor as TipoConteo;
const cantidad = Number(item.total);
agrupado[key][tipo] += cantidad;
agrupado[key].TOTAL += cantidad;
});
setTablaData(Object.values(agrupado));
};
return (
<section className="containerSection">
<h2 className="title"> INSCRITOS </h2>
<h2 className="title">INSCRITOS</h2>
<form className="containerForm">
<form className="containerForm" onSubmit={handleBuscar}>
<label>Seleccione el periodo</label>
<div className="groupInput">
<select>
{periodo.map((p,index)=>(
<option key={index}>
{p.semestre}
</option>))}
<select
onChange={(e) => setSelectedPeriodo(Number(e.target.value))}
defaultValue=""
>
<option value="" disabled>
Seleccione
</option>
{periodo.map((p) => (
<option key={p.id_periodo} value={p.id_periodo}>
{p.semestre}
</option>
))}
</select>
<button className="button buttonSearch" type="submit">
Buscar
</button>
</div>
</form>
<div>
<div className="containerTable">
<table>
<thead>
<tr>
<th>Carrera</th>
<th>Genero</th>
<th>WINDOWS</th>
<th>MACINTOSH</th>
<th>LINUX</th>
<th>Profesores</th>
<th>Total</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div>
<table>
<thead>
<tr>
<th>Impresione B/N</th>
<th>Impresiones Color</th>
<th>Plotteo</th>
<th>Escaner</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>5,000</td>
<td>10,000</td>
<td>10,000</td>
<td>20,000</td>
<td>45,000</td>
</tr>
{tablaData.map((row, index) => (
<tr key={index}>
<td className="textLeft">{row.carrera}</td>
<td>{row.genero ?? "-"}</td>
<td>{row.WINDOWS}</td>
<td>{row.MACINTOSH}</td>
<td>{row.LINUX}</td>
<td>{row.PROFESORES}</td>
<td>{row.TOTAL}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
);
}
//IO
+14
View File
@@ -0,0 +1,14 @@
.containerTable{
overflow: auto;
scrollbar-width: none;
background-color:rgba(84, 84, 84, 0.1) ;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 5px;
}
.textLeft{
padding-left: 20px;
text-align: start;
font-weight: bold;
}
+5
View File
@@ -1,5 +1,10 @@
import { Metadata } from "next";
import Inscripciones from "./Inscripciones";
export const metadata: Metadata = {
title: "Inscritos",
};
export default async function Page(props: {
searchParams?: Promise<{
period: string;
+4 -4
View File
@@ -28,7 +28,7 @@
background: none;
border: none;
padding: 10px;
font-size: 14px;
font-size: 1.4rem;
cursor: pointer;
transition: 0.3s;
color: #333;
@@ -48,7 +48,7 @@
}
.formGroup label {
font-size: 14px;
font-size: 1.4rem;
margin-bottom: 6px;
color: #444;
font-weight: 500;
@@ -59,7 +59,7 @@
padding: 8px 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
font-size: 1.4rem;
transition: border 0.3s, box-shadow 0.3s;
}
@@ -78,7 +78,7 @@
.btn {
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
font-size: 1.4rem;
cursor: pointer;
border: none;
transition: 0.3s;
+5
View File
@@ -1,6 +1,11 @@
import EnviarMensaje from "@/app/Components/EviarMensaje/EnviarMensaje";
import EnviarMensajeSala from "@/app/Components/EviarMensaje/EnviarMensajeSala";
import Toggle from "@/app/Components/Global/Toggle/Toggle";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Mensajes",
};
export default async function Page(props: {
searchParams?: Promise<{
+133 -74
View File
@@ -1,85 +1,144 @@
'use client';
type EstadoPC = 'libre' | 'ocupada' | 'fallo';
const colores: Record<EstadoPC, string> = {
libre: '#22c55e', // verde
ocupada: '#ef4444', // rojo
fallo: '#facc15', // amarillo
};
const pcs = [
{
id: 'PC-01',
x: 120,
y: 180,
width: 40,
height: 40,
estado: 'libre' as EstadoPC,
},
{
id: 'PC-02',
x: 170,
y: 180,
width: 40,
height: 40,
estado: 'ocupada' as EstadoPC,
},
{
id: 'PC-03',
x: 220,
y: 180,
width: 40,
height: 40,
estado: 'fallo' as EstadoPC,
},
];
export default function DSC() {
return (
<svg viewBox="0 0 1500 900" width="100%" height="auto">
<svg viewBox="0 0 1600 1000">
{/* FONDO */}
<image
href="/plano.svg"
width="100%"
height="100%"
preserveAspectRatio="xMidYMid meet"
pointerEvents="none"
{/* CONTORNO EXTERIOR */}
<rect
x="20"
y="20"
width="1560"
height="960"
fill="none"
stroke="black"
strokeWidth="4"
/>
{/* CAPA INTERACTIVA */}
<g>
{pcs.map(pc => (
<g key={pc.id}>
{/* CUADRO */}
<rect
x={pc.x}
y={pc.y}
width={pc.width}
height={pc.height}
fill={colores[pc.estado]}
stroke="#bb4242"
strokeWidth={1}
rx={4}
style={{ cursor: 'pointer' }}
onClick={() => console.log(pc.id)}
/>
{/* ===== BLOQUE SUPERIOR IZQUIERDO (AULAS Y SALAS) ===== */}
{/* NÚMERO */}
<text
x={pc.x + pc.width / 2}
y={pc.y + pc.height / 2 + 5}
textAnchor="middle"
fontSize={12}
fill="#000"
pointerEvents="none"
>
{pc.id.replace('PC-', '')}
</text>
</g>
))}
</g>
<rect x="40" y="40" width="400" height="180"
fill="none" stroke="black" strokeWidth="2" />
<text x="240" y="30" textAnchor="middle" fontSize="18" fontWeight="bold">
AULA 5
</text>
<rect x="40" y="220" width="400" height="150"
fill="none" stroke="black" strokeWidth="2" />
<text x="240" y="210" textAnchor="middle" fontSize="16">
SALA USOS MÚLTIPLES
</text>
<rect x="40" y="370" width="400" height="150"
fill="none" stroke="black" strokeWidth="2" />
<text x="240" y="360" textAnchor="middle" fontSize="16">
SALA DE ENTRENAMIENTO
</text>
{/* ===== BLOQUE CENTRAL WINDOWS ===== */}
<rect x="460" y="40" width="700" height="450"
fill="none" stroke="black" strokeWidth="3" />
<text x="810" y="30" textAnchor="middle" fontSize="22" fontWeight="bold">
ÁREA WINDOWS
</text>
{/* División interna Windows */}
<line x1="460" y1="260" x2="1160" y2="260"
stroke="black" strokeWidth="2" />
{/* ===== BLOQUE DERECHO MAC ===== */}
<rect x="1200" y="40" width="350" height="300"
fill="none" stroke="black" strokeWidth="3" />
<text x="1375" y="30" textAnchor="middle" fontSize="22" fontWeight="bold">
ÁREA MAC
</text>
{/* ===== ROBÓTICA ===== */}
<rect x="1200" y="360" width="350" height="200"
fill="none" stroke="black" strokeWidth="3" />
<text x="1375" y="350" textAnchor="middle" fontSize="18" fontWeight="bold">
ROBÓTICA
</text>
{/* ===== ADMINISTRACIÓN Y OFICINAS ===== */}
<rect x="460" y="520" width="300" height="150"
fill="none" stroke="black" strokeWidth="2" />
<text x="610" y="510" textAnchor="middle" fontSize="16">
ADMINISTRACIÓN
</text>
<rect x="780" y="520" width="200" height="150"
fill="none" stroke="black" strokeWidth="2" />
<text x="880" y="510" textAnchor="middle" fontSize="16">
ASIGNACIÓN
</text>
<rect x="1000" y="520" width="200" height="150"
fill="none" stroke="black" strokeWidth="2" />
<text x="1100" y="510" textAnchor="middle" fontSize="16">
IMPRESIÓN
</text>
{/* ===== CUBÍCULOS ===== */}
{Array.from({ length: 8 }).map((_, i) => (
<rect
key={i}
x={460 + i * 90}
y={700}
width="80"
height="120"
fill="none"
stroke="black"
strokeWidth="2"
/>
))}
<text x="460" y="690" fontSize="14">
CUBÍCULOS 18
</text>
{/* ===== SANITARIOS ===== */}
<rect x="40" y="600" width="200" height="200"
fill="none" stroke="black" strokeWidth="2" />
<text x="140" y="590" textAnchor="middle" fontSize="16">
SANITARIO HOMBRES
</text>
<rect x="260" y="600" width="200" height="200"
fill="none" stroke="black" strokeWidth="2" />
<text x="360" y="590" textAnchor="middle" fontSize="16">
SANITARIO MUJERES
</text>
{/* ===== SALIDA EMERGENCIA ===== */}
<text
x="1530"
y="900"
transform="rotate(-90 1530,900)"
fontSize="16"
fontWeight="bold"
>
SALIDA DE EMERGENCIA
</text>
{/* ===== ENTRADA PRINCIPAL ===== */}
<text
x="800"
y="960"
textAnchor="middle"
fontSize="20"
fontWeight="bold"
>
ENTRADA Y SALIDA PRINCIPAL
</text>
</svg>
);
+31 -15
View File
@@ -2,6 +2,8 @@
import { useEffect, useState } from "react";
import styles from "./Page.module.css";
import axios from "axios";
import Cookies from "js-cookie";
type AreaUbicacion = {
id_area_ubicacion: number;
@@ -23,21 +25,36 @@ export default function MachineTable() {
const [selectedArea, setSelectedArea] = useState("Todo");
const [loading, setLoading] = useState(true);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const fetchMachines = async () => {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/equipo/disable`,
{ cache: "no-store" },
);
setMachines(await res.json());
setLoading(false);
try {
const res = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/equipo/disable`,
{ headers },
);
setMachines(res.data);
} catch (error) {
console.error("Error cargando equipos:", error);
} finally {
setLoading(false);
}
};
const fetchAreas = async () => {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
{ cache: "no-store" },
);
setAreas(await res.json());
try {
const res = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
{ headers },
);
setAreas(res.data);
} catch (error) {
console.error("Error cargando áreas:", error);
}
};
useEffect(() => {
@@ -53,10 +70,8 @@ export default function MachineTable() {
selectedArea === "Todo"
? machines
: machines.filter(
(machine) => machine.areaUbicacion.area === selectedArea,
);
if (loading) return <p>Cargando equipos...</p>;
(machine) => machine.areaUbicacion.area === selectedArea,
);
return (
<div className={styles.tableContainer}>
@@ -108,3 +123,4 @@ export default function MachineTable() {
</div>
);
}
//IO
+35 -17
View File
@@ -2,6 +2,8 @@
import { useEffect, useState } from "react";
import styles from "./Page.module.css";
import axios from "axios";
import Cookies from "js-cookie";
type AreaUbicacion = {
id_area_ubicacion: number;
@@ -14,31 +16,46 @@ type Equipo = {
ubicacion: string;
plataforma: string;
area: string;
ocupado:boolean;
ocupado: boolean;
};
export default function MachineTableActive() {
const [machines, setMachines] = useState<Equipo[]>([]);
const [areas, setAreas] = useState<AreaUbicacion[]>([]);
const [selectedArea, setSelectedArea] = useState("Todo");
const [loading, setLoading] = useState(true);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const fetchMachines = async () => {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/equipo/active`,
{ cache: "no-store" },
);
setMachines(await res.json());
setLoading(false);
try {
const res = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/equipo/active`,
{ headers },
);
setMachines(res.data);
} catch (error) {
console.error("Error cargando equipos:", error);
} finally {
setLoading(false);
}
};
const fetchAreas = async () => {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
{ cache: "no-store" },
);
setAreas(await res.json());
try {
const res = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
{ headers },
);
setAreas(res.data);
} catch (error) {
console.error("Error cargando áreas:", error);
}
};
useEffect(() => {
@@ -50,8 +67,8 @@ export default function MachineTableActive() {
selectedArea === "Todo"
? machines
: machines.filter(
(machine) => machine.area === selectedArea,
);
(machine) => machine.area === selectedArea,
);
if (loading) return <p>Cargando equipos...</p>;
@@ -66,7 +83,7 @@ export default function MachineTableActive() {
<th>Área</th>
<th>Disponible</th>
</tr>
<tr style={{position:"relative", zIndex:"0"}}>
<tr style={{ position: "relative", zIndex: "0" }}>
<th />
<th />
<th />
@@ -90,7 +107,7 @@ export default function MachineTableActive() {
<tbody>
{filteredMachines.map((machine) => (
<tr key={machine.id_equipo} className={!machine.ocupado ? '':styles.ocupado}>
<tr key={machine.id_equipo} className={!machine.ocupado ? '' : styles.ocupado}>
<td>{machine.ubicacion}</td>
<td>{machine.nombre_equipo}</td>
<td className={styles[machine.plataforma]}>
@@ -105,3 +122,4 @@ export default function MachineTableActive() {
</div>
);
}
//IO
+1 -1
View File
@@ -17,7 +17,7 @@
}
.ocupado {
background-color: rgb(173, 233, 253);
background-color: #54bbfc !important;
color: red;
font-weight: bold;
}
+66
View File
@@ -0,0 +1,66 @@
"use client";
import { useEffect, useState } from "react";
import styles from "./Page.module.css";
import axios from "axios";
import Cookies from "js-cookie";
type Mesa = {
id_mesa: number;
ocupado: number;
};
export default function TableTable() {
const [mesas, setMesas] = useState<Mesa[]>([]);
const [loading, setLoading] = useState(true);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const fetchMesas = async () => {
try {
const res = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/mesa/uso`,
{ headers },
);
setMesas(res.data);
} catch (error) {
console.error("Error cargando mesas:", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchMesas();
}, []);
if (loading) {
return <p>Cargando mesas...</p>;
}
return (
<div className={styles.tableContainer}>
<table className={styles.machineTable}>
<thead>
<tr>
<th>ID Mesa</th>
<th>Disponible</th>
</tr>
</thead>
<tbody>
{mesas.map((mesa) => (
<tr key={mesa.id_mesa} className={!mesa.ocupado ? '' : styles.ocupado}>
<td>{mesa.id_mesa}</td>
<td>
{mesa.ocupado === 1 ? "No" : "Si"}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
+15 -10
View File
@@ -1,29 +1,34 @@
import Toggle from "@/app/Components/Global/Toggle/Toggle";
import MachineTable from "./MachineTable";
import styles from "./Page.module.css";
import MachineTableActive from "./MachineTableActive";
import DSC from "./DSC";
import ToggleTable from "@/app/Components/Global/Toggle/ToggleTable";
import TableTable from "./TableTable";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Monitor",
};
export default async function Page(props: {
searchParams?: Promise<{ numAcount: string; key: string }>;
}) {
const params = await props.searchParams;
const numAcount = params?.numAcount ?? null;
const key = params?.key ?? "active";
const key = params?.key ?? "Equipos";
return (
<section className="containerSection">
<h2 className="title">MONITOR DE MÁQUINAS</h2>
<h2 className="title">MONITOR</h2>
<div className={styles.actions}>
<button className={styles.resetButton}>Actualizar información</button>
</div>
<Toggle
<ToggleTable
defaultView={key}
options={[
{
key: "active",
label: "Disponibles",
key: "Equipos",
label: "Equipos",
content: (
<>
<MachineTableActive />
@@ -31,11 +36,11 @@ export default async function Page(props: {
),
},
{
key: "disable",
label: "No Disponibles",
key: "Mesas",
label: "Mesas",
content: (
<>
<MachineTable />
<TableTable />
</>
),
},
+147
View File
@@ -0,0 +1,147 @@
"use client";
import { useState, useEffect } from "react";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import Cookies from "js-cookie";
import { Metadata } from "next";
interface TotalServicioPeriodo {
nombre_servicio: string;
periodo: string;
monto_total: number;
}
interface Servicio {
id_servicio: number;
servicio: string;
}
interface Periodo {
id_periodo: number;
semestre: string;
}
export default function ReporteTotalesPage() {
const [totales, setTotales] = useState<TotalServicioPeriodo[]>([]);
const [servicios, setServicios] = useState<Servicio[]>([]);
const [periodos, setPeriodos] = useState<Periodo[]>([]);
const [cargando, setCargando] = useState(true);
const [periodoInicio, setPeriodoInicio] = useState<string>("");
const [periodoFin, setPeriodoFin] = useState<string>("");
const [servicioId, setServicioId] = useState<string>("");
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
const fetchData = async () => {
try {
const [serviciosRes, periodosRes] = await Promise.all([
axios.get(`${envConfig.apiUrl}/servicio`, { headers }),
axios.get(`${envConfig.apiUrl}/periodo`, { headers }),
]);
setServicios(serviciosRes.data);
setPeriodos(periodosRes.data);
} catch (error) {
console.error("Error al cargar servicios o periodos", error);
}
};
fetchData();
}, []);
const obtenerTotales = async () => {
setCargando(true);
try {
// Construir query params
const params = new URLSearchParams();
if (periodoInicio) params.append("periodoInicio", periodoInicio);
if (periodoFin) params.append("periodoFin", periodoFin);
if (servicioId) params.append("servicioId", servicioId);
const url = `${envConfig.apiUrl}/detalle-servicio/totales-por-periodo?${params.toString()}`;
const res = await axios.get(url, { headers });
setTotales(res.data);
} catch (error) {
console.error("Error al obtener totales", error);
} finally {
setCargando(false);
}
};
const aplicarFiltros = () => {
obtenerTotales();
};
return (
<div>
<h1 style={{ color: "rgb(3, 1, 72)" }}>REPORTE DE TOTALES POR SERVICIO Y PERIODO</h1>
<div style={{ marginBottom: "20px", display: "flex", gap: "10px", flexWrap: "wrap" }}>
<select
value={periodoInicio}
onChange={(e) => setPeriodoInicio(e.target.value)}
>
<option value="">Periodo inicio (todos)</option>
{periodos.map((p) => (
<option key={p.id_periodo} value={p.id_periodo}>
{p.semestre}
</option>
))}
</select>
<select
value={periodoFin}
onChange={(e) => setPeriodoFin(e.target.value)}
>
<option value="">Periodo fin (todos)</option>
{periodos.map((p) => (
<option key={p.id_periodo} value={p.id_periodo}>
{p.semestre}
</option>
))}
</select>
<select
value={servicioId}
onChange={(e) => setServicioId(e.target.value)}
>
<option value="">Todos los servicios</option>
{servicios.map((s) => (
<option key={s.id_servicio} value={s.id_servicio}>
{s.servicio}
</option>
))}
</select>
<button onClick={aplicarFiltros}>Filtrar</button>
</div>
{cargando ? (
<p>Cargando...</p>
) : (
<div style={{ overflow: "auto", maxHeight: "500px" }}>
<table>
<thead>
<tr>
<th>Servicio</th>
<th>Periodo</th>
<th>Monto Total</th>
</tr>
</thead>
<tbody>
{totales.map((item, index) => (
<tr key={index}>
<td>{item.nombre_servicio}</td>
<td>{item.periodo}</td>
<td>${item.monto_total.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
+214
View File
@@ -0,0 +1,214 @@
"use client";
import { useState, useEffect } from "react";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import Cookies from "js-cookie";
import { Metadata } from "next";
interface Periodo {
id_periodo: number;
semestre: string;
fecha_inicio_servicio: string;
fecha_fin_servicio: string;
activo: {
type: string;
data: number[];
};
}
export default function PeriodoPage() {
const [periodos, setPeriodos] = useState<Periodo[]>([]);
const [semestre, setSemestre] = useState("");
const [fechaInicio, setFechaInicio] = useState("");
const [fechaFin, setFechaFin] = useState("");
const [modoEdicion, setModoEdicion] = useState(false);
const [idEditar, setIdEditar] = useState<number | null>(null);
const API = `${envConfig.apiUrl}/periodo`;
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const obtenerPeriodos = async () => {
try {
const res = await axios.get(API, { headers });
setPeriodos(res.data);
} catch (error) {
console.error("Error al obtener periodos", error);
}
};
useEffect(() => {
obtenerPeriodos();
}, []);
const handleSemestreChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const valor = e.target.value;
if (/^\d{0,6}$/.test(valor)) {
setSemestre(valor);
}
};
const crearPeriodo = async () => {
if (semestre.length !== 6) {
alert("El semestre debe tener exactamente 6 dígitos.");
return;
}
try {
await axios.post(
API,
{
semestre,
fecha_inicio_servicio: fechaInicio,
fecha_fin_servicio: fechaFin,
activo: true,
},
{ headers }
);
limpiarFormulario();
obtenerPeriodos();
} catch (error) {
console.error("Error al crear periodo", error);
}
};
const cargarPeriodo = (p: Periodo) => {
setModoEdicion(true);
setIdEditar(p.id_periodo);
setSemestre(p.semestre);
setFechaInicio(p.fecha_inicio_servicio);
setFechaFin(p.fecha_fin_servicio);
};
const modificarPeriodo = async () => {
if (semestre.length !== 6) {
alert("El semestre debe tener exactamente 6 dígitos.");
return;
}
try {
await axios.put(
`${API}/${idEditar}`,
{
semestre,
fecha_inicio_servicio: fechaInicio,
fecha_fin_servicio: fechaFin,
},
{ headers }
);
limpiarFormulario();
obtenerPeriodos();
} catch (error) {
console.error("Error al modificar periodo", error);
}
};
const limpiarFormulario = () => {
setSemestre("");
setFechaInicio("");
setFechaFin("");
setModoEdicion(false);
setIdEditar(null);
};
return (
<div>
<h1 style={{ color: "rgb(3, 1, 72)" }}>PERIODO ESCOLAR</h1>
<div style={{ marginBottom: "10px" }}>
<input
type="text"
placeholder="Periodo (6 dígitos)"
value={semestre}
maxLength={6}
onChange={handleSemestreChange}
/>
<label style={{ color: "rgb(3, 1, 72)" }}>
Elige la fecha de inicio de Periodo
</label>
<input
type="date"
value={fechaInicio}
onChange={(e) => setFechaInicio(e.target.value)}
/>
<label style={{ color: "rgb(3, 1, 72)" }}>
Elige la fecha de fin de Periodo
</label>
<input
type="date"
value={fechaFin}
onChange={(e) => setFechaFin(e.target.value)}
/>
{modoEdicion ? (
<>
<button
className="button buttonSearch"
onClick={modificarPeriodo}
>
Guardar Cambios
</button>
<button
className="button buttonCancel"
onClick={limpiarFormulario}
>
Cancelar
</button>
</>
) : (
<button
className="button buttonSearch"
onClick={crearPeriodo}
>
Crear Periodo
</button>
)}
</div>
<div style={{ overflow: "auto", maxHeight: "350px" }}>
<table>
<thead>
<tr>
<th>ID</th>
<th>PERIODO</th>
<th>Inicio</th>
<th>Fin</th>
<th>Activo</th>
<th>Acción</th>
</tr>
</thead>
<tbody>
{periodos.map((p) => (
<tr key={p.id_periodo}>
<td>{p.id_periodo}</td>
<td>{p.semestre}</td>
<td>{p.fecha_inicio_servicio}</td>
<td>{p.fecha_fin_servicio}</td>
<td>{p.activo.data[0] === 1 ? "Sí" : "No"}</td>
<td>
<button
className="button buttonSearch"
onClick={() => cargarPeriodo(p)}
>
Modificar
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
//IO
+116 -58
View File
@@ -1,8 +1,12 @@
"use client";
import { useState, useEffect } from "react";
import styleprograms from "./programas.module.css";
import { envConfig } from "@/app/lib/config";
import styleprograms from "./programas.module.css";
import axios from "axios";
import Swal from "sweetalert2";
import Cookies from "js-cookie";
import { Metadata } from "next";
interface Programa {
id_programa: number;
@@ -11,66 +15,93 @@ interface Programa {
export default function Page() {
const [modo, setModo] = useState("ver");
const [programaSeleccionado, setProgramaSeleccionado] = useState("");
const [programas, setProgramas] = useState<Programa[]>([]);
const [nombrePrograma, setNombrePrograma] = useState("");
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const cargarProgramas = async () => {
try {
const response = await axios.get(`${envConfig.apiUrl}/programa/`, { headers }
);
setProgramas(response.data);
} catch (error: any) {
console.error("Error al cargar programas:", error);
Swal.fire({
title: "Error al cargar programas",
text: error.response?.data?.message || error.message,
icon: "error",
});
}
};
useEffect(() => {
fetch(`${envConfig.apiUrl}/programa/`)
.then((res) => res.json())
.then((data) => setProgramas(data))
.catch((error) => console.error("Error al cargar programas:", error));
cargarProgramas();
}, []);
const handleInsertar = async () => {
if (!nombrePrograma) return;
if (!nombrePrograma) return;
await fetch(`${envConfig.apiUrl}/programa`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
programa: nombrePrograma,
}),
});
try {
await axios.post(`${envConfig.apiUrl}/programa`, {
programa: nombrePrograma,
}, { headers }
);
const res = await fetch(`${envConfig.apiUrl}/programa/`);
const data = await res.json();
setProgramas(data);
await cargarProgramas();
setNombrePrograma("");
setModo("ver");
};
setNombrePrograma("");
setModo("ver");
Swal.fire({
title: "Programa creado correctamente",
icon: "success",
});
} catch (error: any) {
Swal.fire({
title: "Error al insertar",
text: error.response?.data?.message || error.message,
icon: "error",
});
}
};
const handleGuardar = async () => {
if (!programaSeleccionado) return;
try {
await fetch(`${envConfig.apiUrl}/programa/${programaSeleccionado}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
programa: nombrePrograma,
}),
});
await axios.patch(
`${envConfig.apiUrl}/programa/${programaSeleccionado}`,
{
programa: nombrePrograma,
},
{ headers },
);
const res = await fetch(`${envConfig.apiUrl}/programa/`);
const data = await res.json();
setProgramas(data);
await cargarProgramas();
setModo("ver");
setModo("ver");
Swal.fire({
title: "Programa actualizado correctamente",
icon: "success",
});
} catch (error: any) {
Swal.fire({
title: "Error al actualizar",
text: error.response?.data?.message || error.message,
icon: "error",
});
}
};
const handleNuevo = () => setModo("nuevo");
const handleCancelar = () => setModo("ver");
const handleEditar = () => {
const programa = programas.find(
(p) => p.id_programa === Number(programaSeleccionado),
(p) => p.id_programa === Number(programaSeleccionado)
);
if (programa) {
@@ -79,13 +110,39 @@ export default function Page() {
}
};
const handleDelete = async () => {
if (!programaSeleccionado) return;
try {
await axios.delete(
`${envConfig.apiUrl}/programa/${programaSeleccionado}`,
{ headers },
);
await cargarProgramas();
setProgramaSeleccionado("");
Swal.fire({
title: "Programa eliminado exitosamente",
icon: "success",
});
} catch (error: any) {
Swal.fire({
title: "Error al eliminar",
text: error.response?.data?.message || error.message,
icon: "error",
});
}
};
return (
<section className="containerSection">
{}
{modo === "ver" && (
<section className="containersection">
<h3 className="title">PROGRAMAS</h3>
<label className="label">Selecciona el programa que quieres editar</label>
<label className="label">
Selecciona el programa que quieres editar
</label>
<select
value={programaSeleccionado}
@@ -106,7 +163,8 @@ export default function Page() {
</button>
<button
className={`button buttonSearch ${styleprograms.button}`}
className="button buttonCharge"
style={{ margin: "5px" }}
onClick={handleNuevo}
>
Nuevo
@@ -115,11 +173,9 @@ export default function Page() {
</section>
)}
{}
{modo === "editar" && (
<section className="containersection">
<h2 className="title">Editar Programa</h2>
<label className="label"> {programaSeleccionado}</label>
<input
type="text"
@@ -129,11 +185,9 @@ export default function Page() {
/>
<div className="margin">
<button className="button buttonSearch" onClick={handleGuardar}>
Guardar
</button>
<button className="button buttonSearch" onClick={handleGuardar}>
Guardar
</button>
<button
className={`button buttonCancel ${styleprograms.button}`}
@@ -141,29 +195,32 @@ export default function Page() {
>
Cancelar
</button>
<button
className={`button buttonCancel ${styleprograms.button}`}
onClick={handleDelete}
>
Eliminar
</button>
</div>
</section>
)}
{}
{modo === "nuevo" && (
<section className="containersection">
<h2 className="title">Nuevo Programa</h2>
<label className="label">Programa</label>
<input
type="text"
value={nombrePrograma}
onChange={(e) => setNombrePrograma(e.target.value)}
placeholder="Coloca el programa"
/>
type="text"
value={nombrePrograma}
onChange={(e) => setNombrePrograma(e.target.value)}
placeholder="Coloca el programa"
/>
<div className="margin">
<button className="button buttonSearch" onClick={handleInsertar}>
Insertar
</button>
Insertar
</button>
<button
className={`button buttonCancel ${styleprograms.button}`}
@@ -177,3 +234,4 @@ export default function Page() {
</section>
);
}
//IO
+1 -5
View File
@@ -6,8 +6,4 @@
}
.margin {
margin: 10px 10px;
}
.button {
margin-left: 20px;
margin-top: 20px;
}
}
+6 -3
View File
@@ -1,7 +1,10 @@
import SearchUser from "@/app/Components/Global/SearchUser/searchUser";
import ShowError from "@/app/Components/Global/ShowError";
import QuitarSancion from "@/app/Components/QuitarSancion/QuitarSancion";
import { GetSancionByStudent } from "@/app/lib/getStudent copy";
import { GetSancionByStudent } from "@/app/lib/getSancionByStudent";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "QuitarSancion",
};
export default async function Page(props: {
searchParams?: Promise<{
@@ -5,7 +5,6 @@ import Toggle from "@/app/Components/Global/Toggle/Toggle";
import SearchDateBetween from "@/app/Components/SearchDateBetween/SearchDateBetween";
import PorServicios from "@/app/Components/Reportes/porServicio";
import PorRecibos from "@/app/Components/Reportes/porRecibo";
import DownloadReporteXLSX from "@/app/Components/Dowload/Reporte";
export default function ReportesClient({
defaultKey,
+5
View File
@@ -1,5 +1,10 @@
import { Metadata } from 'next';
import ReportesClient from './ReportesClient';
export const metadata: Metadata = {
title: "Reportes",
};
export default async function Page(props: {
searchParams?: Promise<{
key: string;
+3 -1
View File
@@ -1,5 +1,7 @@
import { ReactNode } from "react";
import Logout from "../Components/auth/Logout/Logout";
import BackgroundRotator from "../Components/layout/BackgroundRotator/BackgroundRotator";
export default async function PrivateLayout({
children,
}: {
@@ -9,7 +11,7 @@ export default async function PrivateLayout({
<div className="mainContainer">
<div className="container">
<Logout />
<div className="img"></div>
<BackgroundRotator/>
{children}
</div>
</div>
+37 -10
View File
@@ -1,10 +1,12 @@
"use client";
import { useEffect, useState } from "react";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import Swal from "sweetalert2";
import Cookies from "js-cookie";
import "@/app/globals.css";
import axios from "axios";
import { envConfig } from "@/app/lib/config";
import toast from "react-hot-toast";
export default function Areas() {
const [areas, setAreas] = useState([]);
@@ -14,9 +16,15 @@ export default function Areas() {
"Mantenimiento",
);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
axios
.get(`${envConfig.apiUrl}/area-ubicacion`)
.get(`${envConfig.apiUrl}/area-ubicacion`,
{ headers }
)
.then((data) => {
setAreas(data.data);
})
@@ -29,12 +37,22 @@ export default function Areas() {
e.preventDefault();
if (!selectedArea) {
toast.error("Selecciona un área antes de actualizar");
Swal.fire({
title: "Selecciona un área antes de actualizar!",
icon: "error",
draggable: true,
});
return;
}
if (!modo) {
toast.error("Selecciona un modo");
Swal.fire({
title: "Selecciona un modo!",
icon: "error",
draggable: true,
});
return;
}
@@ -44,13 +62,23 @@ export default function Areas() {
{
id_area_ubicacion: Number(selectedArea),
activo: modo === "Activo",
}
}, { headers }
);
toast.success("Equipos actualizados correctamente");
Swal.fire({
title: "Equipos actualizados correctamente!",
icon: "success",
draggable: true,
});
} catch (error) {
console.error(error);
toast.error("Error al actualizar los equipos");
Swal.fire({
title: "Error al actualizar los equipos!",
icon: "error",
draggable: true,
});
}
};
@@ -111,4 +139,3 @@ export default function Areas() {
</form>
);
}
//IO
@@ -2,18 +2,33 @@
import { envConfig } from "@/app/lib/config";
import { useEffect, useState } from "react";
import axios from "axios";
import Cookies from "js-cookie";
function Equipos() {
export default function Equipos() {
const [cuenta, setCuenta] = useState("");
const [equipoId, setEquipoId] = useState("");
const [equipos, setEquipos] = useState<any[]>([]);
const [mensaje, setMensaje] = useState("");
useEffect(() => {
fetch(`${envConfig.apiUrl}/equipo`)
.then((res) => res.json())
.then((data) => setEquipos(data))
.catch(() => setMensaje("Error al cargar equipos"));
const fetchEquipos = async () => {
try {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const response = await axios.get(`${envConfig.apiUrl}/equipo`,
{ headers },
);
setEquipos(response.data);
} catch (error: any) {
setMensaje(
error.response?.data?.message || "Error al cargar equipos"
);
}
};
fetchEquipos();
}, []);
const handleBuscar = (e: React.FormEvent<HTMLFormElement>) => {
@@ -63,5 +78,4 @@ function Equipos() {
</div>
);
}
export default Equipos;
//IO
@@ -3,7 +3,7 @@ import { useState } from "react";
import "@/app/globals.css"
function Mesas() {
export default function Mesas() {
const [mesa, setMesa] = useState(""); // Mesa seleccionada
const [mantenimiento, setMantenimiento] = useState(false); // Checkbox Mantenimiento
const [mensaje, setMensaje] = useState(""); // Mensaje dinámico
@@ -18,11 +18,6 @@ function Mesas() {
const estado = mantenimiento ? "en mantenimiento" : "disponible";
setMensaje(`Mesa ${mesa} confirmada como ${estado}`);
console.log({
mesa,
mantenimiento,
});
};
return (
@@ -60,5 +55,4 @@ function Mesas() {
</form>
);
}
export default Mesas;
//IO
@@ -1,8 +1,10 @@
"use client";
import { useEffect, useState } from "react";
import axios from "axios";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import Swal from "sweetalert2";
import Cookies from "js-cookie";
interface Mesa {
id_mesa: number;
@@ -13,13 +15,16 @@ export default function MesasDisponibles() {
const [mesas, setMesas] = useState<Mesa[]>();
const [loading, setLoading] = useState(true);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
obtenerMesas();
}, []);
const obtenerMesas = async () => {
try {
const response = await axios.get(`${envConfig.apiUrl}/mesa`);
const response = await axios.get(`${envConfig.apiUrl}/mesa`,{ headers });
setMesas(response.data);
} catch (error) {
console.error("Error al obtener mesas:", error);
@@ -30,9 +35,12 @@ export default function MesasDisponibles() {
const toggleEquipo = async (id_mesa: number) => {
try {
await fetch(`${envConfig.apiUrl}/mesa/${id_mesa}/activo`, {
method: "PATCH",
});
await axios.patch(
`${envConfig.apiUrl}/mesa/${id_mesa}/activo`,
{},
{ headers },
);
setMesas((prev) =>
prev
@@ -42,12 +50,16 @@ export default function MesasDisponibles() {
...e,
activo: e.activo === 1 ? 0 : 1,
}
: e,
: e
)
: prev,
: prev
);
} catch (error) {
console.error("Error al cambiar estado del equipo", error);
} catch (error: any) {
Swal.fire({
title: "Error al cambiar estado",
text: error.response?.data?.message || error.message,
icon: "error",
});
}
};
-9
View File
@@ -1,9 +0,0 @@
import Swal from "sweetalert2";
function AlertSwet(){
Swal.fire("SweetAlert2 is working!");
}
export default AlertSwet
+15 -3
View File
@@ -3,8 +3,8 @@ import { useEffect, useState } from "react";
import apiClient from "@/app/lib/apiClient";
import "./registerAlta.css";
import toast from "react-hot-toast";
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
type Carrera = {
id_carrera: number;
@@ -72,10 +72,22 @@ export default function RegisterAlta(props: urlProp) {
await apiClient.post("/student", payload);
setSaved(true);
toast.success("Alumno registrado correctamente");
Swal.fire({
title: "Alumno registrado correctamente!",
icon: "success",
draggable: true,
});
} catch (error) {
console.error("Error al guardar alumno:", error);
toast.error("Error al guardar alumno");
Swal.fire({
title: "Error al guardar alumno:!",
icon: "error",
draggable: true,
});
}
};
+72 -41
View File
@@ -2,10 +2,13 @@
import { useEffect, useState } from "react";
import { envConfig } from "@/app/lib/config";
import toast from "react-hot-toast";
import { getEquipoByCount } from "../lib/getEquipoByCount";
import { getMesaByCount } from "../lib/getMesaByCount";
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
import axios from "axios";
import Cookies from "js-cookie";
type Props = {
inscripcion: any[];
@@ -19,6 +22,9 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
const [mesaSeleccionada, setMesaSeleccionada] = useState<any>(null);
const [tiempo, setTiempo] = useState<number>(15);
const [bitacora, setBitacora] = useState<[] | null>([]);
const [error, setError] = useState<string>();
const router = useRouter();
const fetchByCuenta = async (idCuenta: number) => {
const [result, resultMesa] = await Promise.all([
@@ -30,28 +36,35 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
setBitacora(null);
} else {
setBitacora([]);
if (!result?.error) toast.error("Ya cuentas con un equipo asignado");
if (!resultMesa?.error) toast.error("Ya cuentas con una mesa asignada");
if (!result?.error) {
setError("Equipo")
}
if (!resultMesa?.error) {
setError("Mesa")
}
}
};
useEffect(() => {
if (!idCuenta) return;
const Cuenta = idCuenta;
if (isNaN(Cuenta)) return;
fetchByCuenta(Cuenta);
if (!idCuenta || isNaN(idCuenta)) return;
fetchByCuenta(idCuenta);
}, [idCuenta]);
useEffect(() => {
if (!Array.isArray(inscripcion) || inscripcion.length === 0) return;
const todasAsistieron = inscripcion.every(
(ins) => ins.platica?.data?.[0] === 1,
(ins) => ins.platica?.data?.[0] === 1
);
if (!todasAsistieron) {
toast.error("El alumno no asistió a todas las pláticas");
Swal.fire({
title: "El alumno no asistió a todas las pláticas!",
icon: "error",
});
setPuedeContinuar(false);
return;
}
@@ -64,19 +77,23 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
const fetchMesas = async () => {
try {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
setLoadingMesas(true);
const res = await fetch(`${envConfig.apiUrl}/mesa/activo`, {
cache: "no-store",
const response = await axios.get(
`${envConfig.apiUrl}/mesa/activo`,
{ headers },
);
setMesas(response.data);
} catch (error: any) {
Swal.fire({
title: "Error al cargar mesas disponibles!",
text: error.response?.data?.message || error.message,
icon: "error",
});
if (!res.ok) {
throw new Error("No se pudieron cargar las mesas");
}
const data = await res.json();
setMesas(data);
} catch (error) {
toast.error("Error al cargar mesas disponibles");
} finally {
setLoadingMesas(false);
}
@@ -88,48 +105,60 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
const submit = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (!mesaSeleccionada) {
toast.error("Selecciona un mesa");
if (!mesaSeleccionada || Number(mesaSeleccionada) === 0) {
Swal.fire({
title: "Seleccione una mesa!",
icon: "error",
});
return;
}
try {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const body = {
tiempo_asignado: tiempo,
id_mesa: Number(mesaSeleccionada),
id_alumno_inscrito: inscripcion[0].id_alumno_inscrito,
};
const res = await fetch(`${envConfig.apiUrl}/bitacora-mesa`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("Error al asignar mesa");
}
await axios.post(
`${envConfig.apiUrl}/bitacora-mesa`,
body,
{ headers },
);
setBitacora([]);
toast.success("Mesa asignada correctamente");
router.refresh();
setError("Mesa")
Swal.fire({
title: "Mesa asignada correctamente!",
icon: "success",
draggable: true
});
} catch (error) {
toast.error("No se pudo asignar la mesa");
} catch (error: any) {
Swal.fire({
title: "No se pudo asignar la mesa!",
text: error.response?.data?.message || error.message,
icon: "error",
});
}
};
if (!puedeContinuar) return null;
if (bitacora) {
return <></>;
return error === "Mesa" ? (
<p style={{ margin: "1rem", color: "red" }}>
Ya cuentas con una mesa asignada
</p>
) : error === "Equipo" ? (
<p style={{ margin: "1rem", color: "red" }}>
Ya cuentas con un equipo asignado
</p>
) : null;
}
return (
<div className="containerForm">
<label style={{ marginTop: "1rem" }}>Seleccionar tiempo</label>
@@ -142,6 +171,8 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
<option value={30}>30 minutos</option>
<option value={40}>40 minutos</option>
<option value={60}>60 minutos</option>
<option value={90}>90 minutos</option>
<option value={120}>120 minutos</option>
</select>
<label style={{ marginTop: "1rem" }}>Seleccione una mesa</label>
@@ -158,6 +189,7 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
)}
<option value={0}>Selecciona una mesa</option>
{!loadingMesas &&
mesas.map((mesa) => (
<option key={mesa.id_mesa} value={mesa.id_mesa}>
@@ -173,7 +205,6 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
>
Asignar Mesa
</button>
</div>
</div>
);
@@ -0,0 +1,134 @@
"use client";
import { useEffect, useState } from "react";
import { envConfig } from "@/app/lib/config";
import { Alumno, AlumnoSancion, Sancion } from "@/app/types/sanction";
import { GetSancionByStudent } from "@/app/lib/getSancionByStudent";
import TableSanction from "./TableSanction";
import axios from "axios";
import Swal from "sweetalert2";
import Cookies from "js-cookie";
if (!envConfig.apiUrl) {
console.error("API URL is not defined in envConfig");
}
export default function ApplySanction({ idCuenta }: { idCuenta: number }) {
const [sanciones, setSanciones] = useState<Sancion[]>([]);
const [alumno, setAlumno] = useState<Alumno>();
const [alumnoSanciones, setAlumnoSanciones] = useState<AlumnoSancion[]>([]);
const [selectedSancion, setSelectedSancion] = useState("");
const [loading, setLoading] = useState(false);
const [loadingTable, setLoadingTable] = useState(false);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const fetchAlumnoSanciones = async () => {
const res = await GetSancionByStudent(idCuenta);
setAlumno(res.student ?? null);
setAlumnoSanciones(res.alusancion ?? []);
setLoadingTable(true)
if (res.error) {
setLoadingTable(false)
}
};
useEffect(() => {
if (!idCuenta) return;
fetchAlumnoSanciones();
axios
.get(`${envConfig.apiUrl}/sancion`, { headers })
.then((res) => setSanciones(res.data))
.catch(() =>
Swal.fire({
title: "Error al obtener el catálogo de sanciones!",
icon: "error",
draggable: true
})
);
}, [idCuenta]);
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (!selectedSancion) {
Swal.fire({
title: "Selecciona una sanción!",
icon: "error",
draggable: true
});
return;
}
try {
setLoading(true);
await axios.post(`${envConfig.apiUrl}/alumno-sancion/`, {
id_sancion: Number(selectedSancion),
id_cuenta: idCuenta,
}, { headers });
await fetchAlumnoSanciones();
setSelectedSancion("");
Swal.fire({
title: "Sanción aplicada correctamente!",
icon: "success",
draggable: true
});
} catch {
Swal.fire({
title: "Error al aplicar la sanción!",
icon: "error",
draggable: true
});
} finally {
setLoading(false);
}
};
return (
<>
<TableSanction alumnoSanciones={alumnoSanciones} alumno={alumno} />
{loadingTable && alumnoSanciones.length === 0 && (
<>
<form className="containerForm">
<div className="groupInput">
<select
value={selectedSancion}
onChange={(e) => setSelectedSancion(e.target.value)}
>
<option value="">-- Selecciona una sanción --</option>
{sanciones.map((sancion) => (
<option key={sancion.id_sancion} value={sancion.id_sancion}>
{sancion.sancion} ({sancion.duracion} semana/s)
</option>
))}
</select>
</div>
</form>
<button
className="button buttonSearch"
style={{ margin: "1rem 0" }}
onClick={handleButton}
disabled={loading}
>
{loading ? "Aplicando..." : "Aplicar sanción"}
</button>
</>
)}
</>
);
}
@@ -1,36 +1,73 @@
"use client";
import { useState } from "react";
import { useEffect, useState } from "react";
import axios from "axios";
import { envConfig } from "@/app/lib/config";
import Cookies from "js-cookie";
interface alumnos {
interface AlumnoBitacora {
tiempo_entrada: string;
tiempo_asignado: string;
Ubicacion_equipo: number;
tiempo_asignado: number;
equipo: {
ubicacion: string;
nombre_equipo: string;
};
}
function BitacoraAlumno() {
const [alumnos, setAlumnos] = useState<alumnos[]>([]);
interface BitacoraAlumnoProps {
numAcount: string | null;
date: string | null;
}
export default function BitacoraAlumno({ numAcount, date }: BitacoraAlumnoProps) {
const [alumnos, setAlumnos] = useState<AlumnoBitacora[]>([]);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
if (!numAcount) return;
if (!date) return;
axios.post(`${envConfig.apiUrl}/bitacora/equipos-por-dia`, {
numero_cuenta: numAcount,
fecha: date,
}, { headers })
.then(res => {
setAlumnos(res.data);
})
.catch(err => {
console.error(err);
});
}, [numAcount, date]);
return (
<>
<table>
<thead>
<tr>
<th>Tiempo Entrada</th>
<th>Tiempo Asignado</th>
<th>Ubicacion del equipo</th>
</tr>
</thead>
<tbody>
{alumnos.map((alumno, index) => (
<table>
<thead>
<tr>
<th>Tiempo Entrada</th>
<th>Tiempo Asignado</th>
<th>Ubicación del equipo</th>
</tr>
</thead>
<tbody>
{alumnos.length > 0 ? (
alumnos.map((alumno, index) => (
<tr key={index}>
<td>{alumno.tiempo_entrada}</td>
<td>{new Date(alumno.tiempo_entrada).toLocaleString()}</td>
<td>{alumno.tiempo_asignado}</td>
<td>{alumno.Ubicacion_equipo}</td>
<td>{alumno.equipo.ubicacion}</td>
</tr>
))}
</tbody>
</table>
</>
))
) : (
<tr>
<td colSpan={3} style={{ textAlign: "center" }}>
No tuvo equipos asignados
</td>
</tr>
)}
</tbody>
</table>
);
}
export default BitacoraAlumno;
//IO
@@ -1,7 +1,12 @@
"use client";
import SearchDate from "../SearchDate/SearchDate";
import { useState } from "react";
import { useEffect, useState } from "react";
import { envConfig } from "@/app/lib/config";
import SearchDate from "../SearchDate/SearchDate";
import axios from "axios";
import Cookies from "js-cookie";
import "../../(private)/AsignacionEquipo/asignacion.css"
interface equipos {
hora_entrada: string;
min_utilizados: number;
@@ -9,25 +14,101 @@ interface equipos {
cuenta_asociada: number;
}
function BitacoraEquipo() {
export default function BitacoraEquipo({ date }: { date: string | null }) {
const [equipo, setEquipo] = useState<equipos[]>([]);
const [ubicacion_equipo, setUbicacionEquipo] = useState("");
const [open, setOpen] = useState(false);
const [loadingEquipos, setLoadingEquipos] = useState(false);
const [equipoSeleccionado, setEquipoSeleccionado] = useState<any>(null);
const [equipos, setEquipos] = useState<any[]>([]);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
const fetchData = async () => {
setLoadingEquipos(true);
const response = await axios.get(`${envConfig.apiUrl}/equipo`, { headers },
);
setEquipos(response.data);
setLoadingEquipos(false);
};
fetchData();
}, []);
useEffect(() => {
if (!equipoSeleccionado?.id_equipo) return;
if (!date) return;
axios.post(`${envConfig.apiUrl}/bitacora/equipos-por-equipo`, {
id_equipo: equipoSeleccionado.id_equipo,
fecha: date,
}, { headers })
.then(res => {
setEquipo(res.data);
})
.catch(err => {
console.error(err);
});
}, [equipoSeleccionado, date]);
return (
<>
<form className="containerForm">
<label className="label">Ubicacion de equipo</label>
<label>Seleccione un equipo</label>
<div className="containerForm">
<div className="groupInput">
<select
value={ubicacion_equipo}
onChange={(e) => setUbicacionEquipo(e.target.value)}
<div
className={`customSelect ${open ? "open" : ""} ${loadingEquipos ? "disabled" : ""}`}
>
<option value="">-- Selecciona un equipo --</option>
<option value="255">Equipo 255</option>
</select>
<div
className={`selectHeader ${equipoSeleccionado && equipoSeleccionado.plataforma?.nombre?.toUpperCase()}`}
onClick={() => !loadingEquipos && setOpen(!open)}
>
{equipoSeleccionado ? (
<span
>
{equipoSeleccionado.ubicacion}{" "}
{equipoSeleccionado.nombre_equipo}
</span>
) : (
<span className="placeholder">
{loadingEquipos
? "Cargando equipos..."
: "Seleccione un equipo"}
</span>
)}
<span className="arrow">{open ? "▲" : "▼"}</span>
</div>
{open && (
<div className="options" style={{ bottom: "auto" }}>
{equipos.length === 0 && (
<div className="option disabled">
No hay equipos disponibles
</div>
)}
{equipos.map((eq) => (
<div
key={eq.id_equipo}
className={`option ${eq.plataforma?.nombre?.toUpperCase()}`}
onClick={() => {
setEquipoSeleccionado(eq);
setOpen(false);
}}
>
{eq.ubicacion} {eq.nombre_equipo}
</div>
))}
</div>
)}
</div>
</div>
</form>
<SearchDate />
<SearchDate />
</div>
<div>
<table>
<thead>
@@ -39,19 +120,31 @@ function BitacoraEquipo() {
</tr>
</thead>
<tbody>
{equipo.map((equipo, index) => (
<tr key={index}>
<td>{equipo.hora_entrada}</td>
<td>{equipo.min_utilizados}</td>
<td>{equipo.hora_salida}</td>
<td>{equipo.cuenta_asociada}</td>
{equipo.length > 0 ? (
equipo.map((registro, index) => (
<tr key={index}>
<td>{new Date(registro.hora_entrada).toLocaleString()}</td>
<td>{registro.min_utilizados}</td>
<td>
{registro.hora_salida
? new Date(registro.hora_salida).toLocaleString()
: "—"}
</td>
<td>{registro.cuenta_asociada}</td>
</tr>
))
) : (
<tr>
<td colSpan={4} style={{ textAlign: "center" }}>
Este equipo no tuvo asignaciones en esa fecha
</td>
</tr>
))}
)}
</tbody>
</table>
</div>
</>
);
}
export default BitacoraEquipo;
//IO
@@ -1,8 +1,13 @@
"use client";
import SearchDate from "../SearchDate/SearchDate";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import { envConfig } from "@/app/lib/config";
interface tables {
import SearchDate from "../SearchDate/SearchDate";
import axios from "axios";
import Cookies from "js-cookie";
interface Tables {
no_mesa: number;
no_cuenta: number;
hora_entrada: string;
@@ -10,12 +15,33 @@ interface tables {
hora_salida: string;
}
function BitacoraMesas() {
const [tables, setTables] = useState<tables[]>([]);
export default function BitacoraMesas() {
const [tables, setTables] = useState<Tables[]>([]);
const searchParams = useSearchParams();
const date = searchParams.get("date");
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
if (!date) return;
axios.post(`${envConfig.apiUrl}/bitacora-mesa/mesas-por-dia`, {
fecha: date,
}, { headers })
.then(res => {
setTables(res.data);
})
.catch(err => {
console.error(err);
});
}, [date]);
return (
<>
<SearchDate />
<div>
<table>
<thead>
@@ -28,20 +54,27 @@ function BitacoraMesas() {
</tr>
</thead>
<tbody>
{tables.map((table, index) => (
<tr key={index}>
<td>{table.no_mesa}</td>
<td>{table.no_cuenta}</td>
<td>{table.hora_entrada}</td>
<td>{table.tiempo_asignado}</td>
<td>{table.hora_salida}</td>
{tables.length === 0 ? (
<tr>
<td colSpan={5} style={{ textAlign: "center" }}>
No hubo mesas utilizadas en esta fecha
</td>
</tr>
))}
) : (
tables.map((table, index) => (
<tr key={index}>
<td>{table.no_mesa}</td>
<td>{table.no_cuenta}</td>
<td>{new Date(table.hora_entrada).toLocaleString()}</td>
<td>{table.tiempo_asignado}</td>
<td>{new Date(table.hora_salida).toLocaleString()}</td>
</tr>
))
)}
</tbody>
</table>
</div>
</>
);
}
export default BitacoraMesas;
//IO
@@ -1,6 +1,5 @@
import Information from "../Global/Information/information";
import SearchUser from "../Global/SearchUser/searchUser";
import TableSancion from "./TableSancion";
import ApplySanction from "./ApplySanction";
interface Student {
id_cuenta: string;
@@ -13,11 +12,10 @@ export default function Sanciones({ student }: { student?: Student }) {
return (
<>
<SearchUser value={idCuenta} />
{student && (
<>
<Information NoCuenta={student.id_cuenta} Nombre={student.nombre} />
<TableSancion idCuenta={parseInt(student.id_cuenta)} />
<ApplySanction idCuenta={parseInt(student.id_cuenta)} />
</>
)}
</>
@@ -1,129 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import axios from "axios";
import toast from "react-hot-toast";
import { envConfig } from "@/app/lib/config";
interface Alumno {
id_cuenta: number;
nombre: string;
credito: number;
}
interface Sancion {
id_sancion: number;
sancion: string;
duracion: number;
}
interface AlumnoSancion {
id_alumno_sancion: number;
fecha_inicio: string;
sancion: Sancion;
}
if (!envConfig.apiUrl) {
console.error("API URL is not defined in envConfig");
}
export default function TableSancion({ idCuenta }: { idCuenta: number }) {
const [sanciones, setSanciones] = useState<Sancion[]>([]);
const [alumno, setAlumno] = useState<Alumno | null>(null);
const [alumnoSanciones, setAlumnoSanciones] = useState<AlumnoSancion[]>([]);
const [selectedSancion, setSelectedSancion] = useState("");
useEffect(() => {
if (!idCuenta) return;
axios
.get(`${envConfig.apiUrl}/alumno-sancion/${idCuenta}`)
.then((res) => {
setAlumno(res.data.student);
setAlumnoSanciones(
res.data.alusancion?.length ? res.data.alusancion : [],
);
})
.catch((err) => {
const mensaje =
err.response?.data?.message ||
"Error al obtener las sanciones del alumno";
toast.error(mensaje);
});
// Obtener catálogo de sanciones
axios
.get(`${envConfig.apiUrl}/sancion`)
.then((res) => setSanciones(res.data))
.catch((err) =>
toast.error("Error al obtener el catálogo de sanciones:", err),
);
}, [idCuenta]);
const calcularFechaFin = (fechaInicio: string, duracionSemanas: number) => {
const fecha = new Date(fechaInicio);
fecha.setDate(fecha.getDate() + duracionSemanas * 7);
return fecha.toLocaleDateString();
};
return (
<>
<div style={{ margin: "1rem 0" }}>
<table>
<thead>
<tr>
<th>Cuenta</th>
<th>Motivo de la sanción</th>
<th>Duración (Semanas)</th>
<th>Fecha Sanción</th>
<th>Podrá utilizar el servicio hasta</th>
</tr>
</thead>
<tbody>
{alumnoSanciones.length > 0 ? (
alumnoSanciones.map((item) => (
<tr key={item.id_alumno_sancion}>
<td>{alumno?.id_cuenta}</td>
<td>{item.sancion.sancion}</td>
<td>{item.sancion.duracion}</td>
<td>{new Date(item.fecha_inicio).toLocaleDateString()}</td>
<td>
{calcularFechaFin(item.fecha_inicio, item.sancion.duracion)}
</td>
</tr>
))
) : (
<tr>
<td colSpan={5}>No hay sanciones registradas</td>
</tr>
)}
</tbody>
</table>
</div>
<form className="containerForm">
<div className="groupInput">
<select
value={selectedSancion}
onChange={(e) => setSelectedSancion(e.target.value)}
>
<option value="">-- Selecciona una sanción --</option>
{sanciones.map((sancion) => (
<option key={sancion.id_sancion} value={sancion.id_sancion}>
{sancion.sancion} ({sancion.duracion} semana/s)
</option>
))}
</select>
</div>
</form>
<button
className="button buttonSearch"
style={{ margin: "1rem 0" }}
>
Aplicar sanción
</button>
</>
);
}
@@ -0,0 +1,50 @@
import { Alumno, AlumnoSancion } from "@/app/types/sanction";
interface Props {
alumnoSanciones?: AlumnoSancion[] | null,
alumno?: Alumno
}
export default function TableSanction({ alumnoSanciones, alumno }: Props) {
const calcularFechaFin = (fechaInicio: string, duracionSemanas: number) => {
const fecha = new Date(fechaInicio);
fecha.setDate(fecha.getDate() + duracionSemanas * 7);
return fecha.toLocaleDateString();
};
return (
<div style={{ margin: "1rem 0", maxWidth:"730px"}}>
<table>
<thead>
<tr>
<th>Cuenta</th>
<th>Motivo de la sanción</th>
<th>Duración (Semanas)</th>
<th>Fecha Sanción</th>
<th>Podrá utilizar el servicio hasta</th>
</tr>
</thead>
<tbody>
{!(alumnoSanciones?.length === 0) ? (
alumnoSanciones?.map((item) => (
<tr key={item.id_alumno_sancion}>
<td>{alumno?.id_cuenta}</td>
<td>{item.sancion.sancion}</td>
<td>{item.sancion.duracion}</td>
<td>{new Date(item.fecha_inicio).toLocaleDateString()}</td>
<td>
{calcularFechaFin(item.fecha_inicio, item.sancion.duracion)}
</td>
</tr>
))
) : (
<tr>
<td colSpan={5}>No hay sanciones registradas</td>
</tr>
)}
</tbody>
</table>
</div>
)
}
+108 -53
View File
@@ -1,20 +1,25 @@
"use client";
import { useEffect, useState } from "react";
import { envConfig } from "../lib/config";
import { getEquipoByCount } from "../lib/getEquipoByCount";
import SearchUser from "./Global/SearchUser/searchUser";
import SearchEquipo from "./SearchEquipo";
import axios from "axios";
import Information from "./Global/Information/information";
import toast from "react-hot-toast";
import Swal from "sweetalert2";
import Cookies from "js-cookie";
async function getEquipoId(idEquipo: number) {
try {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const res = await axios.get(
`${envConfig.apiUrl}/bitacora/equipo/${idEquipo}`,
{ headers }
);
return res.data;
} catch (error: any) {
if (axios.isAxiosError(error)) {
@@ -22,7 +27,6 @@ async function getEquipoId(idEquipo: number) {
error: error.response?.data?.message || "Error al consultar equipo",
};
}
return { error: "Error desconocido" };
}
}
@@ -33,19 +37,31 @@ interface Props {
}
export default function CheckBoxEquipo({ numAcount, machine }: Props) {
const [modo, setModo] = useState<"Equipo" | "Cuenta" | null>("Cuenta");
const [bitacora, setBitacora] = useState<any>(null);
const [tiempoRestante, setTiempoRestante] = useState<string>("");
const [minutos, setMinutos] = useState<number>();
const [modo, setModo] = useState<"Equipo" | "Cuenta">("Equipo");
const [bitacoraCuenta, setBitacoraCuenta] = useState<any>(null);
const [bitacoraEquipo, setBitacoraEquipo] = useState<any>(null);
const [tiempoRestante, setTiempoRestante] = useState("");
const [minutos, setMinutos] = useState<number>(0);
const [errorCuenta, setErrorCuenta] = useState<string | null>(null);
const [errorEquipo, setErrorEquipo] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const bitacora =
modo === "Cuenta" ? bitacoraCuenta : bitacoraEquipo;
const fetchByCuenta = async (idCuenta: number) => {
const result = await getEquipoByCount(idCuenta);
if (result?.error) {
toast.error(result.error);
setBitacora(null);
setErrorCuenta(result.error);
setBitacoraCuenta(null);
} else {
setBitacora(result);
setErrorCuenta(null);
setBitacoraCuenta(result);
}
};
@@ -53,28 +69,27 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
const result = await getEquipoId(idEquipo);
if (result?.error) {
toast.error(result.error);
setBitacora(null);
setErrorEquipo(result.error);
setBitacoraEquipo(null);
} else {
setBitacora(result);
setErrorEquipo(null);
setBitacoraEquipo(result);
}
};
useEffect(() => {
if (!numAcount) return;
const idCuenta = parseInt(numAcount);
if (isNaN(idCuenta)) return;
if (modo !== "Cuenta" || !numAcount) return;
fetchByCuenta(idCuenta);
}, [numAcount]);
const idCuenta = parseInt(numAcount);
if (!isNaN(idCuenta)) fetchByCuenta(idCuenta);
}, [numAcount, modo]);
useEffect(() => {
if (!machine) return;
const idEquipo = parseInt(machine);
if (isNaN(idEquipo)) return;
if (modo !== "Equipo" || !machine) return;
fetchByEquipo(idEquipo);
}, [machine]);
const idEquipo = parseInt(machine);
if (!isNaN(idEquipo)) fetchByEquipo(idEquipo);
}, [machine, modo]);
useEffect(() => {
if (!bitacora) return;
@@ -82,19 +97,32 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
const entrada = new Date(bitacora.tiempo_entrada).getTime();
const asignadoMs = bitacora.tiempo_asignado * 60 * 1000;
const interval = setInterval(() => {
const updateTimer = () => {
const ahora = Date.now();
const restante = entrada + asignadoMs - ahora;
if (restante <= 0) {
setTiempoRestante("agotado");
clearInterval(interval);
} else {
const minutos = Math.floor(restante / 60000);
setMinutos(minutos + 1);
const segundos = Math.floor((restante % 60000) / 1000);
setTiempoRestante(`${minutos} minutos con ${segundos} segundos`);
setMinutos(0);
return false;
}
const minutosRestantes = Math.floor(restante / 60000);
const segundos = Math.floor((restante % 60000) / 1000);
setMinutos(minutosRestantes + 1);
setTiempoRestante(
`${minutosRestantes} minuto(s) con ${segundos} segundo(s)`
);
return true;
};
updateTimer();
const interval = setInterval(() => {
const sigue = updateTimer();
if (!sigue) clearInterval(interval);
}, 1000);
return () => clearInterval(interval);
@@ -102,25 +130,42 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (!bitacora) return;
await axios.patch(
`${envConfig.apiUrl}/bitacora/cancelar/${bitacora.id_bitacora}`,
{ tiempo_asignado: minutos },
);
toast.success("Tiempo cancelado");
Swal.fire({
title: "Tiempo cancelado!",
icon: "success",
draggable: true,
});
setLoading(true);
if (modo === "Cuenta" && numAcount) {
fetchByCuenta(parseInt(numAcount));
}
try {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
if (modo === "Equipo" && machine) {
fetchByEquipo(parseInt(machine));
await axios.patch(
`${envConfig.apiUrl}/bitacora/cancelar/${bitacora.id_bitacora}`,
{ tiempo_asignado: minutos },
{ headers }
);
Swal.fire({
title: "Tiempo cancelado correctamente!",
icon: "success",
});
if (modo === "Cuenta" && numAcount) {
fetchByCuenta(parseInt(numAcount));
setBitacoraEquipo(null);
}
if (modo === "Equipo" && machine) {
fetchByEquipo(parseInt(machine));
setBitacoraCuenta(null);
}
} catch (error) {
Swal.fire({
title: "Error al cancelar tiempo",
icon: "error",
});
} finally {
setLoading(false);
}
};
@@ -130,7 +175,6 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
<label className="radio-card">
<input
type="radio"
name="modo"
checked={modo === "Equipo"}
onChange={() => setModo("Equipo")}
/>
@@ -141,7 +185,6 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
<label className="radio-card">
<input
type="radio"
name="modo"
checked={modo === "Cuenta"}
onChange={() => setModo("Cuenta")}
/>
@@ -150,10 +193,21 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
</label>
</div>
{modo === "Cuenta" && <SearchUser value={numAcount ?? null} />}
{modo === "Equipo" && <SearchEquipo value={machine ?? null} />}
{modo === "Cuenta" && (
<>
<SearchUser value={numAcount ?? null} />
<h2>{errorCuenta}</h2>
</>
)}
{bitacora && (
{modo === "Equipo" && (
<>
<SearchEquipo value={machine ?? null} />
<h2>{errorEquipo}</h2>
</>
)}
{bitacora && tiempoRestante !== "agotado" && (
<>
<Information
NoCuenta={bitacora.alumno_inscrito.alumno.id_cuenta}
@@ -161,10 +215,12 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
Tiempo={tiempoRestante}
Equipo={bitacora.equipo.ubicacion}
/>
<button
className="button buttonSearch"
style={{ marginTop: "1rem" }}
onClick={handleButton}
disabled={loading || minutos === 0}
>
Cancelar tiempo
</button>
@@ -172,5 +228,4 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
)}
</>
);
}
//IO
}
+148 -61
View File
@@ -1,63 +1,80 @@
"use client";
import { useEffect, useState } from "react";
import { getTableByCount } from "../lib/getTableByCount";
import { envConfig } from "../lib/config";
import { getMesaByCount } from "../lib/getMesaByCount";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import SearchUser from "./Global/SearchUser/searchUser";
import Information from "./Global/Information/information";
import SearchMesa from "./SearchMesa";
import { getTableByCount } from "../lib/getTableByCount";
import toast from "react-hot-toast";
import axios from "axios";
import { envConfig } from "../lib/config";
import { getMesaByCount } from "../lib/getMesaByCount";
import Swal from "sweetalert2";
import Cookies from "js-cookie";
interface props {
numAcount: string | null;
table: number | null;
table: string | null;
}
export default function CheckBoxMesa({ numAcount, table }: props) {
const [modo, setModo] = useState<"Mesa" | "Cuenta" | null>("Cuenta");
const [bitacora, setBitacora] = useState<any>(null);
const [modo, setModo] = useState<"Mesa" | "Cuenta" | null>("Mesa");
const [bitacoraMesa, setBitacoraMesa] = useState<any>(null);
const [bitacoraCuenta, setBitacoraCuenta] = useState<any>(null);
const [tiempoRestante, setTiempoRestante] = useState<string>("");
const [minutos, setMinutos] = useState<number>();
const [errorMesa, setErrorMesa] = useState<string | null>()
const [errorCount, setErrorCount] = useState<string | null>()
const [loading, setLoading] = useState<boolean>(false)
const bitacora = modo === "Cuenta" ? bitacoraCuenta : bitacoraMesa;
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const params = new URLSearchParams(searchParams.toString());
const fetchByCuenta = async (idCuenta: number) => {
const result = await getMesaByCount(idCuenta);
if (result?.error) {
toast.error(result.error);
setBitacora(null);
setErrorCount(result.error);
setBitacoraCuenta(null);
} else {
setBitacora(result);
setBitacoraCuenta(result);
setErrorCount(null)
}
};
const fetchByEquipo = async (idEquipo: number) => {
const fetchByMesa = async (idEquipo: number) => {
const result = await getTableByCount(idEquipo);
if (result?.error) {
toast.error(result.error);
setBitacora(null);
setErrorMesa(result.error);
setBitacoraMesa(null);
} else {
setBitacora(result);
setBitacoraMesa(result);
setErrorMesa(null)
}
};
useEffect(() => {
if (!numAcount) return;
if (modo !== "Cuenta" || !numAcount) return;
const idCuenta = parseInt(numAcount);
if (isNaN(idCuenta)) return;
fetchByCuenta(idCuenta);
}, [numAcount]);
}, [numAcount, modo]);
useEffect(() => {
if (!table) return;
const idEquipo = table;
setBitacoraMesa(null);
params.delete("numAcount");
router.push(`${pathname}?${params.toString()}`);
if (modo !== "Mesa" || !table) return;
const idEquipo = parseInt(table);
if (isNaN(idEquipo)) return;
fetchByEquipo(idEquipo);
fetchByMesa(idEquipo);
}, [table]);
useEffect(() => {
@@ -66,19 +83,32 @@ export default function CheckBoxMesa({ numAcount, table }: props) {
const entrada = new Date(bitacora.tiempo_entrada).getTime();
const asignadoMs = bitacora.tiempo_asignado * 60 * 1000;
const interval = setInterval(() => {
const updateTimer = () => {
const ahora = Date.now();
const restante = entrada + asignadoMs - ahora;
if (restante <= 0) {
setTiempoRestante("agotado");
clearInterval(interval);
} else {
const minutos = Math.floor(restante / 60000);
setMinutos(minutos + 1);
const segundos = Math.floor((restante % 60000) / 1000);
setTiempoRestante(`${minutos} minutos con ${segundos} segundos`);
setMinutos(0);
return false;
}
const minutosRestantes = Math.floor(restante / 60000);
const segundos = Math.floor((restante % 60000) / 1000);
setMinutos(minutosRestantes + 1);
setTiempoRestante(
`${minutosRestantes} minuto(s) con ${segundos} segundo(s)`
);
return true;
};
updateTimer();
const interval = setInterval(() => {
const sigue = updateTimer();
if (!sigue) clearInterval(interval);
}, 1000);
return () => clearInterval(interval);
@@ -86,28 +116,52 @@ export default function CheckBoxMesa({ numAcount, table }: props) {
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setLoading(true);
await axios.patch(
`${envConfig.apiUrl}/bitacora-mesa/cancelar/${bitacora.id_bitacora_mesa}`,
{ tiempo_asignado: minutos },
);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
toast.success("Tiempo cancelado");
Swal.fire({
title: "Tiempo cancelado!",
icon: "success",
draggable: true,
});
try {
if (!bitacora) return;
if (modo === "Cuenta" && numAcount) {
fetchByCuenta(parseInt(numAcount));
}
await axios.patch(
`${envConfig.apiUrl}/bitacora-mesa/cancelar/${bitacora.id_bitacora_mesa}`,
{ tiempo_asignado: minutos ?? 0 },
{ headers }
);
if (modo === "Mesa" && table) {
fetchByEquipo(table);
Swal.fire({
title: "Tiempo cancelado correctamente!",
icon: "success",
});
if (modo === "Cuenta" && numAcount) {
fetchByCuenta(parseInt(numAcount));
params.delete("numAcount");
router.push(`${pathname}?${params.toString()}`);
}
if (modo === "Mesa" && table) {
fetchByMesa(parseInt(table));
params.delete("table");
router.push(`${pathname}?${params.toString()}`);
}
router.refresh();
setBitacoraMesa(null);
setBitacoraCuenta(null);
setErrorMesa('Alumno sin mesa');
} catch (error) {
Swal.fire({
title: "Error al cancelar tiempo",
icon: "error",
});
} finally {
setLoading(false);
}
};
return (
<>
<div className="radio-grid">
@@ -134,26 +188,59 @@ export default function CheckBoxMesa({ numAcount, table }: props) {
</label>
</div>
{modo === "Cuenta" && <SearchUser value={numAcount ?? null} />}
{modo === "Mesa" && <SearchMesa />}
{bitacora && (
{modo === "Cuenta" &&
<>
<Information
NoCuenta={bitacora.alumno_inscrito.alumno.id_cuenta}
Nombre={bitacora.alumno_inscrito.alumno.nombre}
Tiempo={tiempoRestante}
Mesa={bitacora.mesa.id_mesa}
/>
<button
className="button buttonSearch"
style={{ marginTop: "1rem" }}
onClick={handleButton}
>
Cancelar tiempo
</button>
<SearchUser value={numAcount ?? null} />
<h2 style={{ marginTop: "10px" }}>{errorCount}</h2>
{bitacoraCuenta && tiempoRestante !== "agotado" && (
<>
<Information
NoCuenta={bitacoraCuenta.alumno_inscrito.alumno.id_cuenta}
Nombre={bitacoraCuenta.alumno_inscrito.alumno.nombre}
Tiempo={tiempoRestante}
Mesa={bitacoraCuenta.mesa.id_mesa}
/>
<button
className="button buttonSearch"
style={{ marginTop: "1rem" }}
onClick={handleButton}
disabled={loading || !minutos}
>
Cancelar tiempo
</button>
</>
)}
</>
)}
}
{modo === "Mesa" &&
<>
<SearchMesa value={table ?? null} />
<h2 style={{ marginTop: "10px" }}>{errorMesa}</h2>
{bitacoraMesa && tiempoRestante !== "agotado" && (
<>
<Information
NoCuenta={bitacoraMesa.alumno_inscrito.alumno.id_cuenta}
Nombre={bitacoraMesa.alumno_inscrito.alumno.nombre}
Tiempo={tiempoRestante}
Mesa={bitacoraMesa.mesa.id_mesa}
/>
<button
className="button buttonSearch"
style={{ marginTop: "1rem" }}
onClick={handleButton}
disabled={loading || !minutos}
>
Cancelar tiempo
</button>
</>
)}
</>
}
</>
);
}
+63 -26
View File
@@ -1,43 +1,74 @@
"use client";
import styles from "./style.module.css";
import Cookies from "js-cookie";
import { useState } from "react";
import toast from "react-hot-toast";
import { saveAs } from "file-saver";
import ExcelJS from "exceljs";
export default function DownloadReporteXLSX() {
import styles from "./style.module.css";
interface Recibo {
id_recibo: number;
folio_recibo: string;
fecha_recibo: string;
fecha_registro: string;
monto: string;
user: {
usuario: string;
};
}
interface Columns{
header:string;
key:string;
width:number
}
interface Props {
data: Recibo[];
columns:Columns[];
}
export default function DownloadReporteXLSX({ data,columns }: Props) {
const [loading, setLoading] = useState(false);
const formatearFechaMX = (fechaISO: string) => {
return new Date(fechaISO).toLocaleString("es-MX", {
timeZone: "America/Mexico_City",
});
};
const handleDownload = async () => {
if (!data || data.length === 0) return;
setLoading(true);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/equipos/reporteXLSX`,//Modificar
{ headers }
);
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Recibos");
if (!response.ok) {
toast.error("Error al descargar el archivo");
return;
}
worksheet.columns = columns;
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
data.forEach((recibo) => {
worksheet.addRow({
folio: recibo.folio_recibo,
monto: Number(recibo.monto),
fechaRecibo: recibo.fecha_recibo,
fechaRegistro: formatearFechaMX(recibo.fecha_registro),
usuario: recibo.user.usuario,
});
});
const a = document.createElement("a");
a.href = url;
a.download = "Reporte Recibos.xlsx";
document.body.appendChild(a);
a.click();
a.remove();
worksheet.getColumn("monto").numFmt = '"$"#,##0.00';
window.URL.revokeObjectURL(url);
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type:
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
saveAs(blob, "Reporte_Recibos.xlsx");
} catch (error) {
console.error(error);
toast.error("Hubo un problema al descargar el archivo.");
console.error("Error generando Excel", error);
} finally {
setLoading(false);
}
@@ -52,7 +83,13 @@ export default function DownloadReporteXLSX() {
{loading ? (
<div className={styles.loader}></div>
) : (
<img src="/excel.svg" alt="Excel" className={styles.iconExcel} width={30} height={30}/>
<img
src="/excel.svg"
alt="Excel"
className={styles.iconExcel}
width={30}
height={30}
/>
)}
</button>
);
+1 -1
View File
@@ -12,7 +12,7 @@
padding: 5px 5px;
cursor: pointer;
border-radius: 10px 10px 0 0;
font-size: 14px;
font-size: 1.4rem;
font-weight: 600;
transition: background 0.2s ease;
min-width: 50px;
+76 -31
View File
@@ -1,8 +1,9 @@
"use client";
import axios from "axios";
import { envConfig } from "@/app/lib/config";
import { useState } from "react";
import toast from "react-hot-toast";
import Swal from "sweetalert2";
import axios from "axios";
import Cookies from "js-cookie";
interface Data {
nombre_equipo: string;
@@ -40,25 +41,43 @@ export default function Equipos() {
const [isEditing, setIsEditing] = useState(false);
const [isCreating, setIsCreating] = useState(false);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const [equipoRes, plataformaRes, areaRes] = await Promise.all([
axios.get(`${envConfig.apiUrl}/equipo/${id}`),
axios.get(`${envConfig.apiUrl}/alumno-inscrito/plataforma`),
axios.get(`${envConfig.apiUrl}/area-ubicacion`),
]);
try {
const equipo = equipoRes.data as Data;
const [equipoRes, plataformaRes, areaRes] = await Promise.all([
axios.get(`${envConfig.apiUrl}/equipo/${id}`, { headers },
),
axios.get(`${envConfig.apiUrl}/alumno-inscrito/plataforma`, { headers },
),
axios.get(`${envConfig.apiUrl}/area-ubicacion`, { headers },
),
]);
const equipo = equipoRes.data as Data;
setData(equipo);
setNombre(equipo.nombre_equipo);
setUbicacion(equipo.ubicacion);
setIdPlataforma(equipo.id_plataforma);
setIdArea(equipo.id_area_ubicacion);
setPlataforma(plataformaRes.data);
setArea(areaRes.data);
} catch (error) {
Swal.fire({
title: "Equipo no encontrado",
icon: "error",
draggable: true
});
}
setData(equipo);
setNombre(equipo.nombre_equipo);
setUbicacion(equipo.ubicacion);
setIdPlataforma(equipo.id_plataforma);
setIdArea(equipo.id_area_ubicacion);
setPlataforma(plataformaRes.data);
setArea(areaRes.data);
};
const handleSave = async () => {
@@ -72,7 +91,9 @@ export default function Equipos() {
id_plataforma: idPlataforma,
id_area_ubicacion: idArea,
ip: data.ip,
});
},
{ headers }
);
// Actualiza el estado local
setData({
@@ -84,18 +105,28 @@ export default function Equipos() {
});
setIsEditing(false);
toast.success("Equipo actualizado correctamente");
Swal.fire({
title: "Equipo actualizado correctamente!",
icon: "success",
draggable: true
});
} catch (error) {
console.error(error);
toast.error("Error al guardar el equipo");
Swal.fire({
title: "Error al guardar el equipo!",
icon: "error",
draggable: true
});
}
};
const handleNuevo = async () => {
try {
const [plataformaRes, areaRes] = await Promise.all([
axios.get(`${envConfig.apiUrl}/alumno-inscrito/plataforma`),
axios.get(`${envConfig.apiUrl}/area-ubicacion`),
axios.get(`${envConfig.apiUrl}/alumno-inscrito/plataforma`, { headers }),
axios.get(`${envConfig.apiUrl}/area-ubicacion`, { headers }),
]);
setPlataforma(plataformaRes.data);
@@ -118,7 +149,11 @@ export default function Equipos() {
setIsCreating(true);
setIsEditing(true);
} catch (error) {
toast.error("Error al preparar formulario");
Swal.fire({
title: "Error al preparar formulario!",
icon: "error",
draggable: true
});
}
};
@@ -130,9 +165,14 @@ export default function Equipos() {
id_plataforma: idPlataforma,
id_area_ubicacion: idArea,
ip: "0.0.0.0",
});
}, { headers });
toast.success("Equipo creado correctamente");
Swal.fire({
title: "Equipo creado correctamente!",
icon: "success",
draggable: true
});
setIsCreating(false);
setIsEditing(false);
@@ -140,7 +180,11 @@ export default function Equipos() {
} catch (error: any) {
const message =
error?.response?.data?.message || "Error al crear el equipo";
toast.error(message);
Swal.fire({
title: message,
icon: "error",
draggable: true,
});
}
};
@@ -230,13 +274,6 @@ export default function Equipos() {
</>
) : (
<>
<button
type="button"
className="button buttonCharge"
onClick={handleNuevo}
>
Nuevo
</button>
<button
type="button"
className="button buttonSearch"
@@ -249,6 +286,14 @@ export default function Equipos() {
</div>
</div>
)}
<button
type="button"
className="button buttonCharge"
onClick={handleNuevo}
>
Nuevo
</button>
</form>
);
}
//IO
+109 -25
View File
@@ -3,44 +3,99 @@
import { envConfig } from "@/app/lib/config";
import { useEffect, useState } from "react";
import axios from "axios";
import Cookies from "js-cookie";
import "./tableEquipos.css";
type AreaUbicacion = {
id_area_ubicacion: number;
area: string;
};
type Equipo = {
id_equipo: number;
nombre_equipo: string;
ubicacion: string;
plataforma: {
nombre: string
};
areaUbicacion: {area:string};
ocupado: boolean;
activo: {
data: number[]
}
};
export default function TableEquipos() {
const [equipos, setEquipos] = useState<any[]>([]);
const [machines, setMachines] = useState<Equipo[]>([]);
const [areas, setAreas] = useState<AreaUbicacion[]>([]);
const [selectedArea, setSelectedArea] = useState("Todo");
const [loading, setLoading] = useState(true);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
fetch(`${envConfig.apiUrl}/equipo`)
.then((res) => res.json())
.then((data) => {
setEquipos(data);
const fetchEquipos = async () => {
try {
const response = await axios.get(`${envConfig.apiUrl}/equipo`,
{ headers },
);
setMachines(response.data);
} catch (error: any) {
console.error("ERROR API:", error.response?.data || error.message);
} finally {
setLoading(false);
})
.catch((err) => {
console.error("ERROR API:", err);
setLoading(false);
});
}
};
fetchEquipos();
fetchAreas();
}, []);
const fetchAreas = async () => {
try {
const res = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
{ headers },
);
setAreas(res.data);
} catch (error) {
console.error("Error cargando áreas:", error);
}
};
const toggleEquipo = async (id_equipo: number) => {
try {
await fetch(`${envConfig.apiUrl}/mesa/${id_equipo}/activo`, {
method: "PATCH",
});
await axios.patch(
`${envConfig.apiUrl}/equipo/${id_equipo}/activo`,
{},
{ headers },
);
setEquipos((prev) =>
setMachines((prev) =>
prev.map((e) =>
e.id_equipo === id_equipo
? {
...e,
activo: {
data: [e.activo?.data?.[0] === 1 ? 0 : 1],
},
}
: e,
),
...e,
activo: {
data: [e.activo?.data?.[0] === 1 ? 0 : 1],
},
}
: e
)
);
} catch (error: any) {
console.error(
"Error al cambiar estado del equipo",
error.response?.data || error.message
);
} catch (error) {
console.error("Error al cambiar estado del equipo", error);
}
};
@@ -48,6 +103,14 @@ export default function TableEquipos() {
return <p>Cargando equipos...</p>;
}
const filteredMachines =
selectedArea === "Todo"
? machines
: machines.filter(
(machine) => machine.areaUbicacion.area === selectedArea,
);
return (
<div
style={{
@@ -65,9 +128,29 @@ export default function TableEquipos() {
<th>Área</th>
<th>Activo</th>
</tr>
<tr style={{ position: "relative", zIndex: "0" }}>
<th />
<th />
<th />
<th>
<select
style={{ minWidth: "50px" }}
value={selectedArea}
onChange={(e) => setSelectedArea(e.target.value)}
>
<option value="Todo">Todo</option>
{areas.map((area) => (
<option key={area.id_area_ubicacion} value={area.area}>
{area.area}
</option>
))}
</select>
</th>
<th />
</tr>
</thead>
<tbody>
{equipos.map((eq) => {
{filteredMachines.map((eq) => {
const isActivo = eq.activo?.data?.[0] === 1;
return (
@@ -77,7 +160,7 @@ export default function TableEquipos() {
<td className={eq.plataforma?.nombre}>
{eq.plataforma?.nombre}
</td>
<td>{eq.areaUbicacion?.nombre || eq.id_area_ubicacion}</td>
<td>{eq.areaUbicacion.area}</td>
<td
style={{ display: "flex", alignItems: "center", gap: "5px" }}
@@ -98,3 +181,4 @@ export default function TableEquipos() {
</div>
);
}
//IO
@@ -1,8 +1,9 @@
"use client";
import { useEffect, useState } from "react";
import axios from "axios";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import Cookies from "js-cookie";
import "../../(private)/AsignacionEquipo/asignacion.css";
@@ -39,14 +40,17 @@ const EnviarMensaje = ({ titulo }: EnviarMensajeProps) => {
const [customMsg, setCustomMsg] = useState("");
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
const fetchData = async () => {
try {
setLoadingMensajes(true);
const [equiposRes, mensajesRes] = await Promise.all([
axios.get(`${envConfig.apiUrl}/equipo/busy`),
axios.get(`${envConfig.apiUrl}/mensaje`)
axios.get(`${envConfig.apiUrl}/equipo/busy`, { headers }),
axios.get(`${envConfig.apiUrl}/mensaje`, { headers })
]);
setEquipos(equiposRes.data);
@@ -70,8 +74,6 @@ const EnviarMensaje = ({ titulo }: EnviarMensajeProps) => {
mensaje: customMsg || mensaje,
};
console.log(payload);
};
return (
@@ -1,8 +1,9 @@
"use client";
import { useEffect, useState } from "react";
import axios from "axios";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import Cookies from "js-cookie";
interface Area {
id_area_ubicacion: number;
@@ -20,11 +21,13 @@ const EnviarMensajeSala = ({ titulo }: EnviarMensajeSalaProps) => {
const [mensaje, setMensaje] = useState("");
const [customMsg, setCustomMsg] = useState("");
// 🔹 Traer salas desde API
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
const fetchSalas = async () => {
try {
const response = await axios.get(`${envConfig.apiUrl}/area-ubicacion`);
const response = await axios.get(`${envConfig.apiUrl}/area-ubicacion`, { headers });
setSalas(response.data);
} catch (error) {
console.error("Error cargando salas:", error);
@@ -42,14 +45,11 @@ const EnviarMensajeSala = ({ titulo }: EnviarMensajeSalaProps) => {
mensaje: customMsg || mensaje,
};
console.log(payload);
};
return (
<form className="containerForm" onSubmit={handleSubmit}>
<label className="label">{titulo}</label>
<div className="groupInput">
<select
@@ -66,7 +66,7 @@ const EnviarMensajeSala = ({ titulo }: EnviarMensajeSalaProps) => {
</select>
</div>
<label className="label">Seleccione el mensaje</label>
<div className="groupInput">
<select value={mensaje} onChange={(e) => setMensaje(e.target.value)}>
@@ -15,7 +15,7 @@ export default function Information(props: InformationProps) {
{entries.map(([key, value]) => (
<li key={key} className="informationItem">
<span className="informationKey">{key}</span>
<span className="informationValue">{value}</span>
<span className={`informationValue ${key}`}>{value}</span>
</li>
))}
</ul>
@@ -2,57 +2,77 @@
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import SearchDate from "../../SearchDate/SearchDate";
interface urlProp {
value: string | null;
}
function SearchUserWithDate(props: urlProp) {
const [numAcount, setnumAcount] = useState("");
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const todayISO = new Date().toLocaleDateString("en-CA");
const [numAcount, setNumAcount] = useState("");
const [date, setDate] = useState(todayISO);
useEffect(() => {
if (props.value) {
setnumAcount(props.value);
}
}, [props.value]);
const num = searchParams.get("numAcount");
const dateParam = searchParams.get("date");
if (num) setNumAcount(num);
if (dateParam) setDate(dateParam);
}, [searchParams]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const params = new URLSearchParams(searchParams.toString());
if (numAcount) {
params.set("numAcount", `${numAcount}`);
router.push(`${pathname}?${params.toString()}`);
params.set("numAcount", numAcount);
}
if (date) {
params.set("date", date);
}
router.push(`${pathname}?${params.toString()}`);
};
return (
<>
<form className="containerForm" onSubmit={handleSubmit}>
<label className="label">No.Cuenta</label>
<div className="groupInput">
<input
type="text"
value={numAcount}
onChange={(e) => {
const value = e.target.value;
if (/^\d*$/.test(value) && value.length <= 9) {
setnumAcount(value);
}
}}
placeholder="Coloca un número de cuenta..."
inputMode="numeric"
pattern="[0-9]*"
/>
</div>
</form>
<SearchDate />
</>
<form className="containerForm" onSubmit={handleSubmit}>
<label className="label">No.Cuenta</label>
<div className="groupInput">
<input
type="text"
value={numAcount}
onChange={(e) => {
const value = e.target.value;
if (/^\d*$/.test(value) && value.length <= 9) {
setNumAcount(value);
}
}}
placeholder="Coloca un número de cuenta..."
/>
</div>
<label className="label">Fecha</label>
<div className="groupInput">
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
<button className="button buttonSearch" type="submit">
Buscar
</button>
</div>
</form>
);
}
export default SearchUserWithDate;
//IO
@@ -8,7 +8,7 @@ interface urlProp {
}
function SearchUser(props: urlProp) {
const [numAcount, setnumAcount] = useState("");
const [numAcount, setnumAcount] = useState<string>("");
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
@@ -17,6 +17,10 @@ function SearchUser(props: urlProp) {
if (props.value) {
setnumAcount(props.value);
}
if(props.value===null){
setnumAcount("")
}
}, [props.value]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
@@ -2,6 +2,7 @@
import { useEffect, useState } from "react";
import axios from "axios";
import Cookies from "js-cookie";
interface SearchUserProps {
value?: string | null;
@@ -19,7 +20,9 @@ export default function SearchUserTable({ value }: SearchUserProps) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Si el prop cambia, actualizar el input
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
if (value) setNumAcount(value);
}, [value]);
@@ -37,8 +40,8 @@ export default function SearchUserTable({ value }: SearchUserProps) {
setAlumno(null);
try {
// Llamada a la API con Axios
const { data } = await axios.get<Alumno>(`/api/alumnos/${numAcount}`);
const { data } = await axios.get<Alumno>(`/api/alumnos/${numAcount}`, { headers },
);
setAlumno(data);
} catch (err: any) {
setAlumno(null);
+6 -2
View File
@@ -1,12 +1,16 @@
"use client";
import { useEffect } from "react";
import toast from "react-hot-toast";
import Swal from "sweetalert2";
export default function ShowError({ message }: { message: string }) {
useEffect(() => {
if (message) {
toast.error(message);
Swal.fire({
title: message,
icon: "error",
draggable: true,
});
}
}, [message]);
+10 -2
View File
@@ -7,7 +7,7 @@ import { useState, ReactNode } from "react";
interface ToggleOption {
key: string;
label: string;
content: ReactNode;
content: ReactNode | (() => ReactNode);
}
interface ToggleProps {
@@ -46,7 +46,15 @@ export default function Toggle({ options, defaultView }: ToggleProps) {
</div>
<div className="padding toggleContent">
{options.find((opt) => opt.key === view)?.content}
{(() => {
const active = options.find((opt) => opt.key === view)?.content;
if (typeof active === "function") {
return active();
}
return active;
})()}
</div>
</section>
);
-31
View File
@@ -1,31 +0,0 @@
import apiClient from "@/app/lib/apiClient";
import { useEffect, useState } from "react";
export default function selectArea() {
const [area, setArea] = useState([]);
useEffect(() => {
const fetchArea = async () => {
const response = await apiClient.get("/area-ubicacion");
setArea(response.data);
};
fetchArea();
});
return (
<>
<label className="label">Seleccione el área</label>
<select>
{area.map((area) => (
<option>{area}</option>
))}
<option value="0">windows</option>
<option value="1">ADOBE CREATIVE SUITE</option>
<option value="2">mmmmm</option>
<option value="3">mmmmm</option>
<option value="4">mmmmm</option>
<option value="5">mmmmm</option>
</select>
</>
);
}
+14 -1
View File
@@ -1,5 +1,5 @@
.tableContainer {
margin-top: 1rem;
margin-top: 2rem;
width: 100%;
max-width: 450px;
overflow-x: hidden;
@@ -43,4 +43,17 @@
background-repeat: no-repeat;
margin-right: 6px;
vertical-align: middle;
}
@media(max-height:800px) {
.tableContainer {
margin-top: 1.5rem;
max-width: 320px;
}
}
@media(max-width:800px) {
.tableContainer {
margin-top: 0px;
}
}
+33 -15
View File
@@ -4,7 +4,6 @@ import Cookies from "js-cookie";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { PostImpressions } from "@/app/lib/postImpressions";
import toast from "react-hot-toast";
import Swal from "sweetalert2";
interface CostOption {
@@ -19,10 +18,11 @@ interface ImpressionsProps {
function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) {
const [pages, setPages] = useState("");
const [cost, setCost] = useState("");
const [cost, setCost] = useState(String(costs[0].value));
const total = Number(cost) * Number(pages);
const router = useRouter();
const handleLogout = () => {
Cookies.remove("token");
Cookies.remove("session");
@@ -30,25 +30,40 @@ function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) {
};
const handlePayment = async () => {
if (!numAcount) {
toast.error("busca de nuevo al estudiante");
if (numAcount == null) {
Swal.fire({
title: "busca de nuevo al estudiante!",
icon: "error",
draggable: true
});
return;
}
if (!pages) {
toast.error("Ingresa el numero de hojas a imprimir");
Swal.fire({
title: "Ingresa el numero de hojas a imprimir!",
icon: "error",
draggable: true
});
return;
}
if (!cost) {
toast.error("Selecciona un costo");
Swal.fire({
title: "Selecciona un costo!",
icon: "error",
draggable: true
});
return;
}
const result = await PostImpressions({
id_cuenta: numAcount,
numero_hojas: parseInt(pages),
monto: parseInt(cost) * parseInt(pages),
monto: total,
id_servicio,
});
@@ -56,16 +71,20 @@ function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) {
if (result.message === "Token inválido") {
handleLogout();
} else {
toast.error(result.message);
Swal.fire({
title: result.message,
icon: "error",
draggable: true
});
}
return;
}
setPages("");
setCost("");
toast.success("Impresion cobrada correctamente");
setCost(String(costs[0].value));
Swal.fire({
title: "Cobro exitoso!",
title: "Impresion cobrada correctamente!",
icon: "success",
draggable: true
});
@@ -110,14 +129,13 @@ function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) {
</div>
<div
className="groupInput"
style={{ height: "35px", flexWrap: "nowrap" }}
className="groupInput total"
>
<label className="label">Total:</label>
<label
style={{ width: "100%", minWidth: "200px", maxWidth: "500px" }}
>
{pages && cost && `$${parseInt(cost) * parseInt(pages)}.00`}
{pages && cost && `$${total}.00`}
</label>
</div>
@@ -2,17 +2,8 @@
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
interface Equipos {
id_equipo: number;
id_plataforma: number;
id_area_ubicacion: number;
nombre_equipo: string;
ubicacion: string;
activo: boolean;
ip: string;
}
import Swal from "sweetalert2";
import Cookies from "js-cookie";
interface Programa {
id_programa: number;
@@ -26,16 +17,17 @@ export default function ProgramSelector({
ubicacion?: string | null;
id?: number | null;
}) {
const [equipoSelection, setEquipoSelection] = useState<string>("");
const [roomSelection, setRoomSelection] = useState<number>();
const [programas, setProgramas] = useState<Programa[]>([]);
const [checkboxes, setCheckboxes] = useState<Record<number, boolean>>({});
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
const fetchData = async () => {
const [programasRes] = await Promise.all([
axios.get(`${envConfig.apiUrl}/programa`),
]);
axios.get(`${envConfig.apiUrl}/programa`, { headers },
)]);
setProgramas(programasRes.data);
@@ -70,25 +62,49 @@ export default function ProgramSelector({
{
programas: programasSeleccionados,
},
{ headers },
);
toast.success("Programas actualizados en la máquina");
Swal.fire({
title: "Programas actualizados en la máquina!",
icon: "success",
draggable: true
});
return;
}
if (id) {
await axios.patch(`${envConfig.apiUrl}/programa-equipo/sala`, {
sala: id,
programas: programasSeleccionados,
});
await axios.patch(`${envConfig.apiUrl}/programa-equipo/sala`,
{
sala: id,
programas: programasSeleccionados,
},
{ headers },
);
toast.success("Programas actualizados en la sala");
Swal.fire({
title: "Programas actualizados en la sala",
icon: "success",
draggable: true
});
return;
}
toast.error("No se pudo determinar el destino");
Swal.fire({
title: "No se pudo determinar el destino!",
icon: "error",
draggable: true
});
} catch (error) {
toast.error("Error al guardar cambios");
Swal.fire({
title: "Error al guardar cambios!",
icon: "error",
draggable: true,
});
}
};
@@ -108,12 +124,15 @@ export default function ProgramSelector({
if (ubicacion && ubicacion !== "0") {
res = await axios.get(
`${envConfig.apiUrl}/programa-equipo/${ubicacion}`,
{ headers },
);
}
if (id) {
res = await axios.get(
`${envConfig.apiUrl}/area-ubicacion/programs/${id}`,
{ headers },
);
}
@@ -123,12 +142,28 @@ export default function ProgramSelector({
} catch (error: any) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 404) {
toast.error("Sin programas asignados");
Swal.fire({
title: "Sin programas asignados!",
icon: "error",
draggable: true
});
} else {
toast.error("Error al cargar programas");
Swal.fire({
title: "Error al cargar programas!",
icon: "error",
draggable: true
});
}
} else {
toast.error("Error inesperado");
Swal.fire({
title: "Error inesperado!",
icon: "error",
draggable: true
});
}
}
@@ -140,7 +175,7 @@ export default function ProgramSelector({
return (
<form className="form-container" onSubmit={handleSubmit}>
<div className="checkbox-grid">
<div className="checkbox-grid" style={{ gridAutoFlow: "column" }}>
{programas.map((prog) => (
<label key={prog.id_programa}>
<input
@@ -159,4 +194,4 @@ export default function ProgramSelector({
</form>
);
}
//IO
//IO
@@ -1,8 +1,10 @@
"use client";
import { envConfig } from "@/app/lib/config";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import axios from "axios";
import Cookies from "js-cookie";
import "../../(private)/AsignacionEquipo/asignacion.css";
@@ -33,10 +35,15 @@ export default function SelectorEquipo({ onSearch }: Props) {
const [loadingEquipos, setLoadingEquipos] = useState(false);
const [open, setOpen] = useState(false);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
const fetchData = async () => {
setLoadingEquipos(true);
const response = await axios.get(`${envConfig.apiUrl}/equipo`);
const response = await axios.get(`${envConfig.apiUrl}/equipo`, { headers },
);
setEquipos(response.data);
setLoadingEquipos(false);
};
@@ -85,7 +92,7 @@ export default function SelectorEquipo({ onSearch }: Props) {
</div>
{open && (
<div className="options" style={{ bottom: "auto"}}>
<div className="options" style={{ bottom: "auto" }}>
{equipos.length === 0 && (
<div className="option disabled">
No hay equipos disponibles
@@ -3,6 +3,7 @@ import { envConfig } from "@/app/lib/config";
import { useEffect, useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import axios from "axios";
import Cookies from "js-cookie";
interface Area {
id_area_ubicacion: number;
@@ -45,8 +46,13 @@ export default function SelectorSala({ onSearch }: Props) {
};
useEffect(() => {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const fetchData = async () => {
const response = await axios.get(`${envConfig.apiUrl}/area-ubicacion`);
const response = await axios.get(`${envConfig.apiUrl}/area-ubicacion`, { headers },
);
setSala(response.data);
};
+50 -61
View File
@@ -1,75 +1,64 @@
interface Alumno {
id_cuenta: number;
nombre: string;
credito: number;
}
"use client";
interface Sancion {
id_sancion: number;
sancion: string;
duracion: number;
}
import { envConfig } from "@/app/lib/config";
import { useEffect, useState } from "react";
import { GetSancionByStudent } from "@/app/lib/getSancionByStudent";
import { Alumno, AlumnoSancion } from "@/app/types/sanction";
import TableSanction from "../BitacoraSanciones/TableSanction";
import axios from "axios";
import Cookies from "js-cookie";
interface AlumnoSancion {
id_alumno_sancion: number;
fecha_inicio: string;
sancion: Sancion;
}
function QuitarSancion({ id_cuenta }: { id_cuenta: number }) {
const [alumno, setAlumno] = useState<Alumno>();
const [alumnoSanciones, setAlumnoSanciones] = useState<AlumnoSancion[]>([]);
const [loading, setLoading] = useState<boolean>(false)
interface DataProps {
student: Alumno;
alusancion: AlumnoSancion[];
}
const fetchAlumnoSanciones = async () => {
try {
interface Props {
data: DataProps | null;
}
const res = await GetSancionByStudent(id_cuenta);
function QuitarSancion({ data }: Props) {
const calcularFechaFin = (fechaInicio: string, duracionSemanas: number) => {
const fecha = new Date(fechaInicio);
fecha.setDate(fecha.getDate() + duracionSemanas * 7);
return fecha.toLocaleDateString();
setAlumno(res.student ?? null);
setAlumnoSanciones(res.alusancion ?? []);
} catch {
}
};
useEffect(() => {
if (!id_cuenta) return;
fetchAlumnoSanciones();
}, [id_cuenta]);
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
setLoading(true)
await axios.delete(`${envConfig.apiUrl}/alumno-sancion/${id_cuenta}`, { headers })
setAlumnoSanciones([])
setLoading(false)
}
return (
<section className="containerSection">
<div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Motivo Sanción</th>
<th>Duración (semanas)</th>
<th>Fecha Sanción</th>
<th>Podra utilizar el servicio hasta</th>
</tr>
</thead>
<tbody>
{data && data.alusancion.length !== 0 ? (
data.alusancion.map((item) => (
<tr key={item.id_alumno_sancion}>
<td>{item.id_alumno_sancion}</td>
<td>{data.student.nombre}</td>
<td>{item.sancion.sancion}</td>
<td>{item.sancion.duracion}</td>
<td>{new Date(item.fecha_inicio).toLocaleDateString()}</td>
<td>
{calcularFechaFin(item.fecha_inicio, item.sancion.duracion)}
</td>
</tr>
))
) : (
<tr>
<td colSpan={6}>No hay sanciones registradas</td>
</tr>
)}
</tbody>
</table>
</div>
<TableSanction alumnoSanciones={alumnoSanciones} alumno={alumno} />
{(alumnoSanciones?.length > 0) &&
<button
className="button buttonSearch"
style={{ margin: "1rem 0" }}
onClick={handleButton}
disabled={loading}
>
Quitar sancion
</button>
}
</section>
);
}
export default QuitarSancion;
//IO
+121 -44
View File
@@ -1,16 +1,15 @@
"use client";
import toast from "react-hot-toast";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { envConfig } from "@/app/lib/config";
import Selection from "../Selection/Selection";
import { envConfig } from "@/app/lib/config";
import Cookies from "js-cookie";
import axios from "axios";
import Swal from "sweetalert2";
import "./Receipt.css";
import Swal from "sweetalert2";
interface ReceiptsProps {
numAcount: number | null;
@@ -33,11 +32,13 @@ export default function Inscripcion({
>(null);
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [folio, setFolio] = useState("");
const [amount, setAmount] = useState("");
const [lock, setLock] = useState<boolean>(true);
const todayISO = new Date().toISOString().split("T")[0];
const todayISO = new Date().toLocaleDateString("en-CA");
const [date, setDate] = useState(todayISO);
//restrict this month//
@@ -46,33 +47,63 @@ export default function Inscripcion({
const month = day.getMonth();
const today = day.getDate();
const minFecha = new Date(year, month, 1).toISOString().split("T")[0];
let minMount = month
if(!lock){
minMount = month-1
}
const minFecha = new Date(year, minMount, 1).toISOString().split("T")[0];
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
//restrict this month//
const handleSaveReceipt = async () => {
if (!numAcount) {
toast.error("busca de nuevo al estudiante");
if (numAcount == null) {
Swal.fire({
title: "busca de nuevo al estudiante!",
icon: "error",
draggable: true
});
return;
}
if (!folio) {
toast.error("Ingresa el folio del ticket");
Swal.fire({
title: "Ingresa el folio del ticket!",
icon: "error",
draggable: true
});
return;
}
if (!amount) {
toast.error("Coloca el monto a depositar");
Swal.fire({
title: "Coloca el monto a depositar!",
icon: "error",
draggable: true
});
return;
}
if (!date) {
toast.error("Coloca la fecha");
Swal.fire({
title: "Coloca la fecha!",
icon: "error",
draggable: true
});
return;
}
if (!plataformaSeleccionada) {
toast.error("Selecciona una plataforma");
Swal.fire({
title: "Selecciona una plataforma!",
icon: "error",
draggable: true
});
return;
}
@@ -95,58 +126,88 @@ export default function Inscripcion({
{ headers },
);
toast.success("Alumno con pago");
Swal.fire("Alumno con pago");
Swal.fire({
title: "Alumno inscrito con pago",
icon: "success",
draggable: true
});
setFolio("");
setAmount("");
setDate(todayISO);
router.refresh();
} catch (err: any) {
toast.error(String(err));
const params = new URLSearchParams(searchParams.toString());
params.delete("numAcount");
router.push(`${pathname}?${params.toString()}`);
} catch (error: any) {
const msg =
error.response?.data?.message ||
error.message ||
"Error desconocido al crear recibo";
Swal.fire({
title: msg,
icon: "error",
draggable: true
});
}
};
const handleInscripcionSinPago = async () => {
if (!numAcount) {
toast.error("busca de nuevo al estudiante");
if (numAcount == null) {
Swal.fire({
title: "busca de nuevo al estudiante!",
icon: "error",
draggable: true
});
return;
}
if (!plataformaSeleccionada) {
toast.error("Selecciona una plataforma");
Swal.fire({
title: "Selecciona una plataforma!",
icon: "error",
draggable: true
});
return;
}
const id_plataforma = PLATAFORMA_MAP[plataformaSeleccionada];
try {
const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
await axios.post(
`${envConfig.apiUrl}/alumno-inscrito`,
{
id_cuenta: numAcount,
id_plataforma,
realizo_pago: false,
}),
});
},
{ headers },
);
const data = await res.json();
if (!res.ok) {
Swal.fire("Error");
throw new Error(data.message || "Error al inscribir");
}
toast.success("Alumno inscrito sin pago");
Swal.fire({
title: "Alumno inscrito sin pago!",
icon: "success",
draggable: true
draggable: true,
});
router.refresh();
} catch (err: any) {
toast.error(err.message);
const message =
err.response?.data?.message || "Error al inscribir";
Swal.fire({
title: message,
icon: "error",
draggable: true,
});
}
};
@@ -161,7 +222,6 @@ export default function Inscripcion({
value={conPago}
onChange={(e) => setConPago(e.target.value as "con" | "sin")}
style={{
marginBottom: "1rem",
maxWidth: "120px",
minWidth: "120px",
}}
@@ -206,7 +266,25 @@ export default function Inscripcion({
const numericValue = parseFloat(value);
if (lock && numericValue > 1000) {
toast.error("El monto no puede superar $1000.00");
Swal.fire({
title:
"El monto no puede superar $1000.00 Desbloquea el candado !",
icon: "error",
draggable: true,
});
return;
}
if (numericValue > 10000) {
Swal.fire({
title:
"El monto no puede superar $10000.00 pesos",
icon: "error",
draggable: true,
});
return;
}
@@ -229,16 +307,16 @@ export default function Inscripcion({
/>
</div>
<div className="containerButton" style={{ position: 'relative' }}>
<button className="button buttonSearch">Inscribir</button>
<div className="containerButton" style={{ position: "relative" }}>
<button className="button buttonSearch" disabled={!plataformaSeleccionada}
>Inscribir</button>
<button
type="button"
className={`button buttonCancel ${lock ? "buttonLock" : "buttonOpenLock"
}`}
onClick={() => {
setLock((prev) => !prev)
setAmount('');
setLock((prev) => !prev);
setAmount("");
}}
/>
</div>
@@ -246,7 +324,6 @@ export default function Inscripcion({
</form>
)}
{conPago === "sin" && (
<div className="containerButton">
<button
className="button buttonSearch"
+11 -5
View File
@@ -23,21 +23,21 @@
background-color: #cfcfcf;
}
.buttonLock{
.buttonLock {
padding: 0.8rem;
position: absolute;
right: 0;
right: 100px;
background-color: #d32f2f;
}
.buttonOpenLock{
.buttonOpenLock {
padding: 0.8rem;
position: absolute;
right: 0;
right: 100px;
background-color: #8cd32f;
}
.buttonOpenLock:hover{
.buttonOpenLock:hover {
background-color: #78b32c;
}
@@ -61,4 +61,10 @@
filter: invert(1);
background-size: contain;
background-repeat: no-repeat;
}
@media(max-height:800px) {
.buttonLock {
padding: 0.5rem;
}
}
+59 -16
View File
@@ -1,24 +1,23 @@
"use client";
import toast from "react-hot-toast";
import { useState } from "react";
import { PostReceipt } from "@/app/lib/postReceipt";
import { useRouter } from "next/navigation";
import "./Receipt.css";
import Swal from "sweetalert2";
import "./Receipt.css";
interface ReceiptsProps {
numAcount: number | null;
}
function Receipt({ numAcount }: ReceiptsProps) {
export default function Receipt({ numAcount }: ReceiptsProps) {
const router = useRouter();
const [folio, setFolio] = useState("");
const [amount, setAmount] = useState("");
const [lock, setLock] = useState<boolean>(true);
const todayISO = new Date().toISOString().split("T")[0];
const todayISO = new Date().toLocaleDateString("en-CA");
const [date, setDate] = useState(todayISO);
//restrict this month//
@@ -27,28 +26,53 @@ function Receipt({ numAcount }: ReceiptsProps) {
const month = day.getMonth();
const today = day.getDate();
const minFecha = new Date(year, month, 1).toISOString().split("T")[0];
let minMount = month
if(!lock){
minMount = month-1
}
const minFecha = new Date(year, minMount, 1).toISOString().split("T")[0];
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
//restrict this month//
const handleSaveReceipt = async () => {
if (!numAcount) {
toast.error("busca de nuevo al estudiante");
if (numAcount == null) {
Swal.fire({
title: "busca de nuevo al estudiante!",
icon: "error",
draggable: true
});
return;
}
if (!folio) {
toast.error("Ingresa el folio del ticket");
Swal.fire({
title: "Ingresa el folio del ticket!",
icon: "error",
draggable: true
});
return;
}
if (!amount) {
toast.error("Coloca el monto a depositar");
Swal.fire({
title: "Coloca el monto a depositar!",
icon: "error",
draggable: true
});
return;
}
if (!date) {
toast.error("Coloca la fecha");
Swal.fire({
title: "Coloca la fecha!",
icon: "error",
draggable: true
});
return;
}
@@ -64,16 +88,20 @@ function Receipt({ numAcount }: ReceiptsProps) {
setAmount("");
setDate(todayISO);
toast.success("Recibo guardado");
Swal.fire({
title: "Recibo Guardado!",
title: "Recibo Guardado Correctamente!",
icon: "success",
draggable: true
});
router.refresh();
} catch (err: any) {
toast.error(String(err));
Swal.fire({
title: err,
icon: "error",
draggable: false
});
}
};
@@ -114,7 +142,24 @@ function Receipt({ numAcount }: ReceiptsProps) {
const numericValue = parseFloat(value);
if (lock && numericValue > 1000) {
toast.error("El monto no puede superar $1000.00");
Swal.fire({
title:
"El monto no puede superar $1000.00 Desbloquea el candado !",
icon: "error",
draggable: true,
});
return;
}
if (numericValue > 10000) {
Swal.fire({
title:
"El monto no puede superar $10000.00 pesos",
icon: "error",
draggable: true,
});
return;
}
@@ -154,6 +199,4 @@ function Receipt({ numAcount }: ReceiptsProps) {
</form>
);
}
export default Receipt;
//IO
+18 -5
View File
@@ -3,6 +3,7 @@
import { useEffect, useState } from "react";
import axios from "axios";
import DownloadReporteXLSX from "../Dowload/Reporte";
import Cookies from "js-cookie";
interface Recibo {
id_recibo: number;
@@ -20,19 +21,31 @@ interface Props {
hasta: string | null;
}
function PorRecibos({ desde, hasta }: Props) {
export default function PorRecibos({ desde, hasta }: Props) {
const [recibos, setRecibos] = useState<Recibo[]>([]);
const [loading, setLoading] = useState(false);
const columns = [
{ header: "Folio", key: "folio", width: 15 },
{ header: "Monto", key: "monto", width: 15 },
{ header: "Fecha Recibo", key: "fechaRecibo", width: 18 },
{ header: "Fecha Registro", key: "fechaRegistro", width: 22 },
{ header: "Usuario", key: "usuario", width: 20 },
];
useEffect(() => {
if (!desde || !hasta) return;
const fetchRecibos = async () => {
setLoading(true);
try {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const res = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/recibo/rango`,
{ desde, hasta }
{ desde, hasta },
{ headers },
);
setRecibos(res.data);
@@ -61,7 +74,8 @@ function PorRecibos({ desde, hasta }: Props) {
return (
<div style={{ position: "relative" }}>
{recibos.length > 0 && <DownloadReporteXLSX />}
{recibos.length > 0 && <DownloadReporteXLSX data={recibos} columns={columns} />}
<div
style={{
overflow: "auto",
@@ -105,5 +119,4 @@ function PorRecibos({ desde, hasta }: Props) {
</div>
);
}
export default PorRecibos;
//IO
+8 -7
View File
@@ -2,8 +2,7 @@
import { useEffect, useState } from "react";
import axios from "axios";
import DownloadReporteXLSX from "../Dowload/Reporte";
import Cookies from "js-cookie";
interface ServicioReporte {
id_servicio: number;
@@ -16,7 +15,7 @@ interface Props {
hasta: string | null;
}
function PorServicio({ desde, hasta }: Props) {
export default function PorServicio({ desde, hasta }: Props) {
const [servicios, setServicios] = useState<ServicioReporte[]>([]);
const [loading, setLoading] = useState(false);
@@ -26,9 +25,13 @@ function PorServicio({ desde, hasta }: Props) {
const fetchServicios = async () => {
setLoading(true);
try {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
const res = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/detalle-servicio/rango`,
{ desde, hasta }
{ desde, hasta },
{ headers },
);
setServicios(res.data);
@@ -44,7 +47,6 @@ function PorServicio({ desde, hasta }: Props) {
return (
<div style={{ position: "relative" }}>
{servicios.length > 0 && <DownloadReporteXLSX />}
<div
style={{
@@ -82,5 +84,4 @@ function PorServicio({ desde, hasta }: Props) {
</div>
);
}
export default PorServicio;
//IO
+43 -22
View File
@@ -1,29 +1,50 @@
"use client";
import { useState } from "react";
function SearchDate() {
const [date, setDate] = useState("");
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
export default function SearchDate() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const todayISO = new Date().toLocaleDateString("en-CA");
const [date, setDate] = useState(todayISO);
useEffect(() => {
const dateParam = searchParams.get("date");
if (dateParam) {
setDate(dateParam);
}
}, [searchParams]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const params = new URLSearchParams(searchParams.toString());
if (date) {
params.set("date", date);
}
router.push(`${pathname}?${params.toString()}`);
};
return (
<>
<form className="containerForm">
<label className="label">Fecha</label>
<div className="groupInput">
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
placeholder="Selecciona una fecha..."
/>
<button className="button buttonSearch" type="submit">
Buscar
</button>
</div>
</form>
</>
<form className="containerForm" onSubmit={handleSubmit}>
<label className="label">Fecha</label>
<div className="groupInput">
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
<button className="button buttonSearch" type="submit">
Buscar
</button>
</div>
</form>
);
}
export default SearchDate;
//IO
//By Tyrannuss
@@ -7,9 +7,10 @@ interface Props {
onSearch: (desde: string, hasta: string) => void;
}
function SearchDateBetween({ onSearch }: Props) {
export default function SearchDateBetween({ onSearch }: Props) {
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const todayISO = new Date().toLocaleDateString("en-CA");
const [endDate, setEndDate] = useState(todayISO);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@@ -54,5 +55,4 @@ function SearchDateBetween({ onSearch }: Props) {
</form>
);
}
export default SearchDateBetween;
//IO
+15 -50
View File
@@ -1,63 +1,28 @@
"use client";
import axios from "axios";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
interface Mesa {
id_mesa: number;
nombre?: string;
interface urlProp {
value: string | null;
}
interface Props {
id_cuenta: number;
}
function Mesas({ id_cuenta }: Props) {
const [mesas, setMesas] = useState<Mesa[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!id_cuenta) return;
setLoading(true);
axios
.get(`/cuenta/${id_cuenta}`)
.then((res) => {
setMesas(res.data);
})
.catch((err) => console.error(err))
.finally(() => setLoading(false));
}, [id_cuenta]);
if (loading) return <p>Cargando mesas...</p>;
return (
<div>
<h3>Mesas asignadas</h3>
{mesas.length === 0 ? (
<p>No hay mesas</p>
) : (
<ul>
{mesas.map((mesa) => (
<li key={mesa.id_mesa}>
Mesa {mesa.id_mesa} {mesa.nombre && `- ${mesa.nombre}`}
</li>
))}
</ul>
)}
</div>
);
}
export default function SearchMesa() {
export default function SearchMesa(props: urlProp) {
const [inputValue, setInputValue] = useState("");
const [idCuenta, setIdCuenta] = useState<number | null>(null);
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (props.value) {
setInputValue(props.value);
}
if(props.value===null){
setInputValue("")
}
}, [props.value]);
const buscar = (e: React.FormEvent) => {
e.preventDefault();
const params = new URLSearchParams(searchParams.toString());
@@ -74,7 +39,7 @@ export default function SearchMesa() {
<div className="groupInput">
<input
type="number"
type="text"
placeholder="Coloca el numero de mesa"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
+8 -2
View File
@@ -1,7 +1,8 @@
"use client";
import axios from "axios";
import { useEffect, useState } from "react";
import { envConfig } from "../lib/config";
import axios from "axios";
import Cookies from "js-cookie";
if (!envConfig.apiUrl) {
console.error("API URL is not defined in envConfig");
@@ -15,9 +16,14 @@ export default function SelectAreas(props: proms) {
const [areas, setAreas] = useState([]);
const [selectedArea, setSelectedArea] = useState("");
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
useEffect(() => {
axios
.get(`${envConfig.apiUrl}/area-ubicacion`)
.get(`${envConfig.apiUrl}/area-ubicacion`, { headers }
)
.then((data) => {
setAreas(data.data);
})
+40
View File
@@ -4,6 +4,7 @@
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin-bottom: 1rem;
}
.selectionItem {
@@ -14,6 +15,10 @@
}
.buttonSelection {
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: 50px;
height: 50px;
border-radius: 100%;
@@ -23,6 +28,13 @@
padding: 5px;
}
.imageSelection {
width: 30px;
height: 30px;
position: relative;
object-fit: contain;
}
.buttonSelection:hover {
transform: scale(1.1);
background-color: #d0e1ff;
@@ -40,3 +52,31 @@
color: #333;
text-align: center;
}
@media(max-height:550px) {
.buttonLabel {
margin-top: 2px;
font-size: 1rem;
}
}
@media(max-height:550px) {
.selection {
margin-bottom: 0rem;
gap: 2rem;
}
.buttonSelection {
width: 40px;
height: 40px;
padding: 0px;
}
.imageSelection {
width: 20px;
height: 20px;
position: relative;
object-fit: contain;
}
}
+14 -14
View File
@@ -1,9 +1,10 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import "./Selection.css";
function Selection({
export default function Selection({
plataformasInscritas = [],
onSelect,
}: {
@@ -24,11 +25,11 @@ function Selection({
},
{
name: "MACINTOSH",
img: "apple.png",
img: "/apple.png",
},
{
name: "PROFESORES",
img: "teacher.png",
img: "/teacher.png",
},
];
@@ -45,16 +46,16 @@ function Selection({
onClick={() => handleSelect(option.name)}
>
<button
className={`buttonSelection ${
selected === option.name ? "active" : ""
}`}
className={`buttonSelection ${selected === option.name ? "active" : ""
}`}
>
<img
src={option.img}
alt={option.name.toLowerCase()}
height={30}
width={30}
/>
<div className="imageSelection">
<Image
src={option.img}
alt={option.name.toLowerCase()}
fill
/>
</div>
</button>
<span className="buttonLabel">{option.name}</span>
</div>
@@ -68,5 +69,4 @@ function Selection({
</section>
);
}
export default Selection;
//IO
+14 -14
View File
@@ -2,8 +2,9 @@
import { useState } from "react";
import "./Selection.css";
import Image from "next/image";
function SelectionCo({
export default function SelectionCo({
plataformasInscritas = [],
onSelect,
}: {
@@ -19,11 +20,11 @@ function SelectionCo({
},
{
name: "MACINTOSH",
img: "apple.png",
img: "/apple.png",
},
{
name: "PROFESORES",
img: "teacher.png",
img: "/teacher.png",
},
];
@@ -46,16 +47,16 @@ function SelectionCo({
onClick={() => handleSelect(option.name)}
>
<button
className={`buttonSelection ${
selected === option.name ? "active" : ""
}`}
className={`buttonSelection ${selected === option.name ? "active" : ""
}`}
>
<img
src={option.img}
alt={option.name.toLowerCase()}
height={30}
width={30}
/>
<div className="imageSelection">
<Image
src={option.img}
alt={option.name.toLowerCase()}
fill
/>
</div>
</button>
<span className="buttonLabel">{option.name}</span>
</div>
@@ -69,5 +70,4 @@ function SelectionCo({
</section>
);
}
export default SelectionCo;
//IO
@@ -1,7 +1,7 @@
"use client";
import apiClient from "@/app/lib/apiClient";
import { useState } from "react";
import toast from "react-hot-toast";
import Swal from "sweetalert2";
export default function ChangePassword() {
const [password, setPass] = useState("");
@@ -12,16 +12,26 @@ export default function ChangePassword() {
e.preventDefault();
const data = { password, newPassword };
try{
try {
const response = await apiClient.post("/user/changePassword", data);
toast.success( `${response.data.message}`)
Swal.fire({
title: response.data.message,
icon: "success",
draggable: true,
});
setPass('');
setNewPass('');
setconfirmNewPass('');
}catch{
toast.error( `No es la contraseña actual`)
} catch {
Swal.fire({
title: "No es tu contraseña actual!",
icon: "error",
draggable: true,
});
}
};
return (
@@ -85,4 +95,3 @@ export default function ChangePassword() {
</section>
);
}
//IO
+25 -10
View File
@@ -1,12 +1,13 @@
"use client";
import { useState } from "react";
import { loginUser } from "@/app/lib/login";
import { useRouter } from "next/navigation";
import toast from "react-hot-toast";
import Swal from "sweetalert2";
import "./Login.css";
function Login() {
export default function Login() {
const [user, setUser] = useState("");
const [password, setPassword] = useState("");
@@ -16,30 +17,46 @@ function Login() {
e.preventDefault();
if (!user) {
toast.error("Coloca un usuario");
Swal.fire({
title: "Coloca un usuario!",
icon: "error",
draggable: true,
});
return;
}
if (!password) {
toast.error("Coloca una contraseña");
Swal.fire({
title: "Coloca una contraseña!",
icon: "error",
draggable: true,
});
return;
}
const data = await loginUser(user, password);
if ("error" in data) {
toast.error(data.error);
Swal.fire({
title: data.error,
icon: "error",
draggable: true,
});
} else {
const token = data.access_token;
const payload = JSON.parse(atob(token.split(".")[1]));
const usuario = payload.usuario;
const role = payload.role;
document.cookie = `token=${token}; path=/; SameSite=Strict`;
document.cookie = `user=${usuario}; path=/; SameSite=Strict`;
document.cookie = `role=${role}; path=/; SameSite=Strict`;
toast.success("Inicio de sesión exitoso");
Swal.fire({
title: "Inicio de sesión exitoso!",
icon: "success",
draggable: true,
});
router.push("/Impresiones");
router.refresh();
}
@@ -85,6 +102,4 @@ function Login() {
</section>
);
}
export default Login;
//IO
+15 -6
View File
@@ -10,7 +10,7 @@
background-color: #d12020;
}
.session{
.session {
position: absolute;
top: 10px;
right: 10px;
@@ -19,11 +19,19 @@
gap: 2rem;
}
.userName{
.userName {
background-color: white;
border-radius: 6px;
padding: 6px 12px;
z-index: 1;
outline: 1px solid #cfcfcf;
}
@media(max-height: 800px) {
.session {
top: 5px;
right: 5px;
}
}
@media (max-width: 800px) {
@@ -57,16 +65,17 @@
transform: translate(0, -50%);
}
.userName{
.userName {
position: absolute;
top: 0px;
right: 50px;
}
}
@media(max-width:450px){
.userName{
@media(max-width:450px) {
.userName {
position: absolute;
top: 50px;
right: 0px;
}
}
}
@@ -0,0 +1,68 @@
.backgroundContainer {
position: absolute;
top: 0;
right: 0;
width: 725px;
height: 615px;
z-index: -1;
overflow: hidden;
}
.bgImage {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
transition: opacity 2s ease-in-out;
opacity: 1;
z-index: 1;
}
.bgImage.fade {
z-index: 0;
opacity: 0;
}
.bgImage.fade.active {
opacity: 1;
z-index: 1;
}
.bgImage::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to right, #f9f9f9, rgba(128, 128, 128, 0));
}
@media (max-width: 800px) {
.backgroundContainer {
background-image: url("/rock.jpg");
background-size: cover;
background-position: center;
width: 600px;
height: 650px;
right: 50%;
transform: translateX(50%);
}
.bgImage {
display: none;
visibility: hidden;
}
.backgroundContainer::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to right, #f9f9f9, rgba(128, 128, 128, 0));
}
.backgroundContainer::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to left, #f9f9f9, rgba(128, 128, 128, 0));
}
}
@@ -0,0 +1,45 @@
"use client";
import { useEffect, useState } from "react";
import "./Background.css";
const images = [
"/rock2.jpg",
"/CEDETEC.jpg",
"/FES-ACATLAN.jpg",
"/DSC.jpeg",
];
export default function BackgroundRotator() {
const [current, setCurrent] = useState(0);
const [next, setNext] = useState(1);
const [isTransitioning, setIsTransitioning] = useState(false);
useEffect(() => {
const interval = setInterval(() => {
setIsTransitioning(true);
setTimeout(() => {
setCurrent(next);
setNext((next + 1) % images.length);
setIsTransitioning(false);
}, 2000);
}, 20000);
return () => clearInterval(interval);
}, [next]);
return (
<div className="backgroundContainer">
<div
className="bgImage"
style={{ backgroundImage: `url(${images[current]})` }}
/>
<div
className={`bgImage fade ${isTransitioning ? "active" : ""}`}
style={{ backgroundImage: `url(${images[next]})` }}
/>
</div>
);
}
@@ -24,7 +24,7 @@
transition:
background-color 0.3s ease,
border 0.3s ease;
font-size: 1.5rem;
font-size: 1.4rem;
}
.barNavigation li:hover {
@@ -107,12 +107,12 @@
align-items: center;
justify-content: center;
color: #545454;
font-size: 1.5rem;
font-size: 1.4rem;
padding: 1rem;
}
.subMenu:hover > span,
.subMenu > a:hover > span {
.subMenu:hover>span,
.subMenu>a:hover>span {
color: black;
}
@@ -129,7 +129,7 @@
color: white;
width: 100%;
height: 100%;
font-size: 1.5rem;
font-size: 1.4rem;
}
thead,
@@ -137,6 +137,12 @@ tbody {
font-size: 1.25rem;
}
@media(max-height:550px) {
.barNavigation {
max-height: 35px;
}
}
@media (min-width: 1001px) {
.subMenu:hover ul {
opacity: 1;
@@ -158,7 +164,7 @@ tbody {
}
.barNavigation {
font-size: 15px;
font-size: 1.4rem;
justify-content: end;
}
@@ -166,6 +172,7 @@ tbody {
position: absolute;
top: 76px;
left: 0;
top: 100%;
width: 100%;
background-color: #fff;
flex-direction: column;
@@ -235,13 +242,13 @@ tbody {
transform: translateY(0);
}
.subMenu.open ul a{
.subMenu.open ul a {
display: flex;
pointer-events:auto;
pointer-events: auto;
}
thead,
tbody {
font-size: 1rem;
}
}
}
@@ -123,7 +123,7 @@ export default function BarNavigation() {
>
<li>Activos y en Mantenimiento</li>
</Link>
<Link
{/* <Link
href="/Mensajes"
className="links"
onClick={(e) => {
@@ -132,7 +132,7 @@ export default function BarNavigation() {
}}
>
<li>Mensajes</li>
</Link>
</Link> */}
<Link
href="/Programas"
className="links"
@@ -100,7 +100,7 @@ export default function BarNavigationServicio() {
</ul>
</li>
<li className="subMenu" onClick={toggleMenu}>
{/* <li className="subMenu" onClick={toggleMenu}>
<Link
href="/Mensajes"
className="links"
@@ -111,7 +111,21 @@ export default function BarNavigationServicio() {
>
<span>Mensajes</span>
</Link>
</li> */}
<li className="subMenu" onClick={toggleMenu}>
<Link
href="/ActivosMantenimiento"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<span>Activos y en Mantenimiento</span>
</Link>
</li>
</ul>
</nav>
);
+44 -1
View File
@@ -1,3 +1,9 @@
.link {
display: flex;
align-items: center;
margin: 10px 5px;
}
.logo {
position: relative;
z-index: 3;
@@ -86,15 +92,50 @@
max-height: 60%;
}
@media (max-height: 550px) {
.containerBarNav {
max-height: 70%;
}
.yellowPart {
height: 15px;
}
.logoContainer {
min-width: 150px;
aspect-ratio: 250/70;
}
.link {
display: flex;
align-items: center;
margin: 5px 2px;
}
.header::after {
left: -60px;
min-width: 300px;
}
.header::before {
left: -40px;
width: 10%;
min-width: 300px;
}
}
@media (max-width: 800px) {
.yellowPart {
left: 0;
background: #bd8c01;
transform: skew(0deg);
}
.header::after {
left: -100px;
}
.header::before {
left: -80px;
}
@@ -105,11 +146,13 @@
min-width: 100%;
transform: skew(25deg);
}
.header::before {
min-width: 100%;
transform: skew(25deg);
}
.containerBarNav {
padding-left: 0;
}
}
}
+19 -11
View File
@@ -1,21 +1,33 @@
import Image from "next/image";
import Link from "next/link";
import BarNavigation from "../BarNavigation/BarNavigation";
import header from "./Header.module.css";
import { cookies } from "next/headers";
import BarNavigationServicio from "../BarNavigation/BarNavigationServicio";
import { jwtVerify } from "jose";
import HeaderNavigation from "./HeaderNavigation";
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
export default async function Header() {
const cookieStore = await cookies();
const role = cookieStore.get("role")?.value || "";
const cookieStore = cookies();
const token = (await cookieStore).get("token")?.value;
let role: number = 5;
if (token) {
try {
const { payload } = await jwtVerify(token, secret);
role = payload.role as number;
} catch (error) {
role = 5;
}
}
return (
<header className={header.header}>
<Link
href="https://www.unam.mx/"
target="_blank"
className={header.center}
style={{ display: "flex", alignItems: "center", margin: "10px 5px" }}
className={`${header.center} ${header.link}`}
>
<div className={header.logoContainer}>
<Image
@@ -29,11 +41,7 @@ export default async function Header() {
</Link>
<div className={header.yellowPart}></div>
<div className={header.containerBarNav}>
{role === "ADMINIS.TRADOR" && <BarNavigation />}
{role === "SUPER ADMINISTRADOR (quita sanciones)" && <BarNavigation />}
{role === "ADMINISTRADOR" && <BarNavigation/>}
{role === "ATENCION A USUARIO" && <BarNavigation/>}
{role === "SERVICIO SOCIAL" && <BarNavigationServicio/>}
<HeaderNavigation role={role} />
</div>
</header>
);
@@ -0,0 +1,22 @@
"use client";
import { usePathname } from "next/navigation";
import BarNavigation from "../BarNavigation/BarNavigation";
import BarNavigationServicio from "../BarNavigation/BarNavigationServicio";
export default function HeaderNavigation({ role }: { role: number }) {
const pathname = usePathname();
if (pathname === "/") {
return null;
}
return (
<>
{role === 1 && <BarNavigation />}
{role === 4 && <BarNavigation />}
{role === 2 && <BarNavigationServicio />}
{role === 3 && <BarNavigationServicio />}
</>
);
}
-45
View File
@@ -1,45 +0,0 @@
"use client";
import { Toaster } from "react-hot-toast";
import React from "react";
export function ToastProvider({ children }: { children: React.ReactNode }) {
return (
<>
{children}
<Toaster
toastOptions={{
style: {
padding: "16px",
fontSize: "16px",
maxWidth: "400px",
},
error: {
duration: 6000,
style: {
fontSize: "1.5rem",
backgroundColor: "#fee2e2dd",
color: "#000000ff",
fontWeight: "600",
padding: "1.5rem",
},
},
success: {
duration: 6000,
style: {
fontSize: "1.5rem",
backgroundColor: "#d1fae5dd",
color: "#000000ff",
fontWeight: "600",
padding: "1.5rem",
},
},
}}
position="top-left"
reverseOrder={false}
/>
</>
);
}
+26
View File
@@ -0,0 +1,26 @@
import Image from "next/image";
import "./creadores.css";
interface props{
title:string;
img:string;
width:number;
height:number;
}
export default function CreatorCard({title,img,width,height}:props) {
return (
<div className="card">
<div className="imageWrapper">
<Image
src={img}
alt="IO"
width={width}
height={height}
className="creators"
/>
</div>
<h1 className="titles">{title}</h1>
</div>
)
}
+61
View File
@@ -0,0 +1,61 @@
.imageWrapper {
position: relative;
animation: slideIn 1s ease-out forwards;
transform: translateX(-120px) rotate(12deg);
opacity: 0;
}
.card {
display: flex;
flex-direction: column;
align-items: center;
animation: slideIn 1s ease-out forwards;
opacity: 0;
}
.card:hover .creators {
transform: rotateY(360deg) rotate(12deg) scale(1.05);
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.35);
}
.creators {
object-fit: cover;
border-radius: 16px;
background-color: rgba(255, 255, 255, 0.6);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.25);
transition: transform 1s ease, box-shadow 0.4s ease;
}
.imageWrapper:hover .creators {
transform: rotateY(180deg) rotate(24deg) scale(1.05);
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.35);
}
@keyframes slideIn {
to {
transform: translateX(0) rotate(12deg);
opacity: 1;
}
}
.titles {
margin-left: 40px;
font-size: 3rem;
font-weight: 600;
opacity: 0;
animation: fadeIn 1s ease forwards;
animation-delay: 0.5s;
color: #1E3A8A;
}
.card:hover .title {
transform: translateY(-5px);
color: #163172;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
+19
View File
@@ -0,0 +1,19 @@
import BackgroundRotator from "../Components/layout/BackgroundRotator/BackgroundRotator";
import CreatorCard from "./CreatorCard";
import "./creadores.css";
export default function Page() {
return (
<div className="mainContainer">
<div className="container" style={{ display: "flex", flexDirection: "row", justifyContent: "center", gap: "3rem", flexWrap: "wrap" }}>
<BackgroundRotator />
<CreatorCard title="IO" img="/IO.png" width={150} height={200} />
<CreatorCard title="CarloSpak" img="/SPAK.png" width={160} height={100} />
<CreatorCard title="Tyrannuss" img="/Tyrannuss.png" width={140} height={140} />
</div>
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More