From 7af87fa42495b1d39f3680c4886a3acf1af6b514 Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Fri, 23 Jan 2026 12:35:35 -0600 Subject: [PATCH 01/20] fixed addTime and added new BarNavigation --- app/(private)/AgregarTiempo/Addtime.tsx | 30 +++-- app/(private)/AgregarTiempo/addTime.css | 21 ++++ app/(private)/AgregarTiempo/page.tsx | 66 +++++----- app/(private)/BitacoraSanciones/page.tsx | 20 ++- app/(private)/Impresiones/page.tsx | 4 + app/(private)/QuitarSancion/page.tsx | 5 +- app/Components/Impressions/impressions.tsx | 9 +- app/Components/Receipt/Inscripcion.tsx | 25 ++++ .../layout/BarNavigation/BarNavigation.tsx | 20 +-- .../BarNavigation/BarNavigationServicio.tsx | 119 ++++++++++++++++++ app/Components/layout/Header/Header.tsx | 6 +- tsconfig.json | 2 +- 12 files changed, 254 insertions(+), 73 deletions(-) create mode 100644 app/Components/layout/BarNavigation/BarNavigationServicio.tsx diff --git a/app/(private)/AgregarTiempo/Addtime.tsx b/app/(private)/AgregarTiempo/Addtime.tsx index e4b6aa0..1e26f05 100644 --- a/app/(private)/AgregarTiempo/Addtime.tsx +++ b/app/(private)/AgregarTiempo/Addtime.tsx @@ -10,6 +10,7 @@ import "./addTime.css"; import SelectionCo from "@/app/Components/Selection/SelectionCo"; import axios from "axios"; import { envConfig } from "@/app/lib/config"; +import Cookies from "js-cookie"; const PLATAFORMA_MAP: Record = { WINDOWS: 1, @@ -36,8 +37,9 @@ export default function AddTime({ // 🔹 datos del recibo const [folio, setFolio] = useState(""); const [amount, setAmount] = useState(""); - const [date, setDate] = useState(""); - + const todayISO = new Date().toISOString().split("T")[0]; + const [date, setDate] = useState(todayISO); + // 🔹 restricción de fechas const day = new Date(); const year = day.getFullYear(); @@ -77,23 +79,27 @@ export default function AddTime({ const id_plataforma = PLATAFORMA_MAP[plataformaSeleccionada]; try { - await PostReceipt({ - id_cuenta: numAcount, - folio_recibo: folio, - monto: Number(amount), - fecha_recibo: date, - }); + const token = Cookies.get("token"); + const headers = { Authorization: `Bearer ${token}` }; - await axios.post(`${envConfig.apiUrl}/alumno-inscrito/tiempo`, { - idCuenta: numAcount, - idPlataforma: id_plataforma, - }); + await axios.post( + `${envConfig.apiUrl}/operations/time`, + { + monto: Number(amount), + id_cuenta: numAcount, + idPlataforma: id_plataforma, + folio_recibo: folio, + fecha_recibo: date, + }, + { headers }, + ); toast.success("Tiempo Guardado"); setFolio(""); setAmount(""); setDate(""); + router.refresh(); } catch (err: any) { toast.error(err.message || "Error al guardar"); } diff --git a/app/(private)/AgregarTiempo/addTime.css b/app/(private)/AgregarTiempo/addTime.css index 1fe80b1..1a94679 100644 --- a/app/(private)/AgregarTiempo/addTime.css +++ b/app/(private)/AgregarTiempo/addTime.css @@ -9,3 +9,24 @@ flex-direction: column; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } + +.firstPartInformationTime { + display: flex; + flex-direction: column; + width: 100%; + max-width: 450px; +} + +.containeInformationTime { + display: flex; + gap: 10px; + width: 100%; +} + +@media (max-width: 800px) { + .containeInformationTime { + flex-direction: column; + justify-content: center; + align-items: center; + } +} \ No newline at end of file diff --git a/app/(private)/AgregarTiempo/page.tsx b/app/(private)/AgregarTiempo/page.tsx index f8cc99c..c8204d3 100644 --- a/app/(private)/AgregarTiempo/page.tsx +++ b/app/(private)/AgregarTiempo/page.tsx @@ -3,25 +3,12 @@ import Information from "@/app/Components/Global/Information/information"; import ShowError from "@/app/Components/Global/ShowError"; import { GetRegisterStudent } from "@/app/lib/getRegisterStudent"; -import { envConfig } from "@/app/lib/config"; import "./addTime.css"; import AddTime from "./Addtime"; +import Table from "@/app/Components/Global/table"; -async function getInscripcion(idCuenta: number) { - try { - const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito/${idCuenta}`, { - headers: { "Content-Type": "application/json" }, - cache: "no-store", - }); - - if (!res.ok) throw new Error("No se pudo cargar inscripción"); - - return await res.json(); - } catch { - return []; - } -} +const headers = ["Inscrito", "Tiempo", "Confirmó"]; export default async function Page(props: { searchParams?: Promise<{ numAcount: string }>; @@ -41,13 +28,21 @@ export default async function Page(props: { errorMessage = result.error; } else { student = result[0]?.alumno; - inscripcion = await getInscripcion(idCuenta); + inscripcion = result; } } - const plataformasInscritas = inscripcion.map( - (ins) => ins.plataforma?.nombre - ); + 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", + })) + : []; + + const plataformasInscritas = inscripcion.map((ins) => ins.plataforma?.nombre); return (
@@ -55,22 +50,27 @@ export default async function Page(props: {

AGREGAR TIEMPO

- +
+
+ + {student && ( + <> + - {student && ( - <> - + + + )} +
- - - )} + {student && } + ); } -//IO \ No newline at end of file +//IO diff --git a/app/(private)/BitacoraSanciones/page.tsx b/app/(private)/BitacoraSanciones/page.tsx index 78cb26d..56f4cc9 100644 --- a/app/(private)/BitacoraSanciones/page.tsx +++ b/app/(private)/BitacoraSanciones/page.tsx @@ -2,9 +2,11 @@ import BitacoraAlumno from "@/app/Components/BitacoraSanciones/BitacoraAlumno"; import BitacoraEquipo from "@/app/Components/BitacoraSanciones/BitacoraEquipo"; import BitacoraMesas from "@/app/Components/BitacoraSanciones/BitacoraMesas"; import Sanciones from "@/app/Components/BitacoraSanciones/Sanciones"; +import SearchUser from "@/app/Components/Global/SearchUser/searchUser"; import SearchUserWithDate from "@/app/Components/Global/SearchUser/SearchUserWithDate"; 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"; @@ -42,7 +44,7 @@ export default async function Page(props: { options={[ { key: "Equipo", - label: "Bitacora equipo", + label: "Bitácora equipo", content: ( <> @@ -51,7 +53,7 @@ export default async function Page(props: { }, { key: "Alumno", - label: "Bitacora alumno", + label: "Bitácora alumno", content: ( <> @@ -61,7 +63,7 @@ export default async function Page(props: { }, { key: "Mesas", - label: "Bitacora mesas", + label: "Bitácora mesas", content: ( <> @@ -77,6 +79,18 @@ export default async function Page(props: { ), }, + { + key: "EliminarSanciones", + label: "Eliminar Sanciones", + content: ( + <> +
+ + {/* */} +
+ + ), + }, ]} /> diff --git a/app/(private)/Impresiones/page.tsx b/app/(private)/Impresiones/page.tsx index 48e43df..3be6b65 100644 --- a/app/(private)/Impresiones/page.tsx +++ b/app/(private)/Impresiones/page.tsx @@ -64,6 +64,7 @@ export default async function Page(props: { key={1} costs={[{ value: 1 }, { value: 2 }]} numAcount={student.id_cuenta} + id_servicio={1} /> ), }, @@ -84,6 +85,7 @@ export default async function Page(props: { { value: 14 }, ]} numAcount={student.id_cuenta} + id_servicio={2} /> ), }, @@ -113,6 +115,7 @@ export default async function Page(props: { { value: 200 }, ]} numAcount={student.id_cuenta} + id_servicio={3} /> ), }, @@ -124,6 +127,7 @@ export default async function Page(props: { key={4} costs={[{ value: 1 }, { value: 2 }, { value: 5 }]} numAcount={student.id_cuenta} + id_servicio={6} /> ), }, diff --git a/app/(private)/QuitarSancion/page.tsx b/app/(private)/QuitarSancion/page.tsx index 83c7dcf..42dc6ae 100644 --- a/app/(private)/QuitarSancion/page.tsx +++ b/app/(private)/QuitarSancion/page.tsx @@ -27,10 +27,7 @@ export default async function Page(props: { {errorMessage && }

Quitar Sanciones

-
- - -
+ ); } diff --git a/app/Components/Impressions/impressions.tsx b/app/Components/Impressions/impressions.tsx index b0e9029..e05e8a5 100644 --- a/app/Components/Impressions/impressions.tsx +++ b/app/Components/Impressions/impressions.tsx @@ -13,9 +13,10 @@ interface CostOption { interface ImpressionsProps { costs: CostOption[]; numAcount: number | null; + id_servicio: number; } -function Impressions({ costs, numAcount }: ImpressionsProps) { +function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) { const [pages, setPages] = useState(""); const [cost, setCost] = useState(""); @@ -47,6 +48,7 @@ function Impressions({ costs, numAcount }: ImpressionsProps) { id_cuenta: numAcount, numero_hojas: parseInt(pages), monto: parseInt(cost) * parseInt(pages), + id_servicio, }); if (result.error) { @@ -101,7 +103,10 @@ function Impressions({ costs, numAcount }: ImpressionsProps) { /> -
+
@@ -203,6 +206,8 @@ export default function Inscripcion({ } } }} + placeholder="Monto recibido..." + inputMode="numeric" />
From fb68ddc6360645fe5eca671b5f9c4886c69fbfe3 Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Fri, 23 Jan 2026 13:46:46 -0600 Subject: [PATCH 03/20] add registration whit pago --- app/Components/Receipt/Inscripcion.tsx | 38 +++++++++++--------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/app/Components/Receipt/Inscripcion.tsx b/app/Components/Receipt/Inscripcion.tsx index 2e6784c..995f5c8 100644 --- a/app/Components/Receipt/Inscripcion.tsx +++ b/app/Components/Receipt/Inscripcion.tsx @@ -2,12 +2,14 @@ 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 Selection from "../Selection/Selection"; import { envConfig } from "@/app/lib/config"; +import Cookies from "js-cookie"; +import axios from "axios"; + +import "./Receipt.css"; interface ReceiptsProps { numAcount: number | null; @@ -75,28 +77,21 @@ export default function Inscripcion({ const id_plataforma = PLATAFORMA_MAP[plataformaSeleccionada]; try { - await PostReceipt({ - id_cuenta: numAcount, - folio_recibo: folio, - monto: Number(amount), - fecha_recibo: date, - }); + const token = Cookies.get("token"); + const headers = { Authorization: `Bearer ${token}` }; - const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ + await axios.post( + `${envConfig.apiUrl}/operations/registration`, + { + monto: Number(amount), id_cuenta: numAcount, id_plataforma, - realizo_pago: false, - }), - }); - - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.message || "Error al inscribir"); - } + folio_recibo: folio, + fecha_recibo: date, + realizo_pago: true, + }, + { headers }, + ); toast.success("Alumno con pago"); @@ -104,7 +99,6 @@ export default function Inscripcion({ setAmount(""); setDate(""); - toast.success("Recibo guardado"); router.refresh(); } catch (err: any) { toast.error(String(err)); From 42168efe659751106ed5ecf918acc8e438068af1 Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Fri, 23 Jan 2026 14:57:17 -0600 Subject: [PATCH 04/20] added autocomplete --- app/(private)/Alta/page.tsx | 10 ++++++++-- app/Components/Alta/registerAlta.tsx | 30 ++++++++++++++++++---------- app/Components/Receipt/Receipt.tsx | 3 ++- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/app/(private)/Alta/page.tsx b/app/(private)/Alta/page.tsx index a90117a..0ad2646 100644 --- a/app/(private)/Alta/page.tsx +++ b/app/(private)/Alta/page.tsx @@ -2,13 +2,19 @@ import RegisterAlta from "@/app/Components/Alta/registerAlta"; import "./style.css"; -export default async function Page(props: { searchParams?: Promise<{}> }) { +export default async function Page(props: { + searchParams?: Promise<{ + numAcount: string; + }>; +}) { const params = await props.searchParams; + const numAcount = params?.numAcount ? params.numAcount : null; + return (

ALTA USUARIO

- +
); } diff --git a/app/Components/Alta/registerAlta.tsx b/app/Components/Alta/registerAlta.tsx index b1796ca..b15cd5d 100644 --- a/app/Components/Alta/registerAlta.tsx +++ b/app/Components/Alta/registerAlta.tsx @@ -4,23 +4,29 @@ import apiClient from "@/app/lib/apiClient"; import "./registerAlta.css"; import toast from "react-hot-toast"; +import { useRouter } from "next/navigation"; type Carrera = { id_carrera: number; carrera: string; }; -export default function RegisterAlta() { +interface urlProp { + numCount: string | null; +} + +export default function RegisterAlta(props: urlProp) { + const router = useRouter(); const [listMajor, setListMajor] = useState([]); const [saved, setSaved] = useState(false); const [form, setForm] = useState({ - cuenta: "", + cuenta: String(props.numCount), nombre: "", apellidoP: "", apellidoM: "", fecha: "", - email: "", + email: `${props.numCount}@pcpuma.acatlan.unam.mx`, genero: "", carrera: "", }); @@ -52,7 +58,6 @@ export default function RegisterAlta() { (value) => value.trim() !== "", ); - const handleSave = async (e: React.MouseEvent) => { e.preventDefault(); @@ -68,7 +73,7 @@ export default function RegisterAlta() { const handleInscripcion = (e: React.MouseEvent) => { e.preventDefault(); - toast.success("Continuar con inscripción"); + router.push("/Inscripcion"); }; return ( @@ -81,6 +86,7 @@ export default function RegisterAlta() { value={form.cuenta} onChange={handleChange} placeholder="Coloca un número de cuenta..." + required /> @@ -92,6 +98,7 @@ export default function RegisterAlta() { value={form.nombre} onChange={handleChange} placeholder="Coloca el nombre" + required /> @@ -103,6 +110,7 @@ export default function RegisterAlta() { value={form.apellidoP} onChange={handleChange} placeholder="Coloca el apellido paterno" + required /> @@ -114,6 +122,7 @@ export default function RegisterAlta() { value={form.apellidoM} onChange={handleChange} placeholder="Coloca el apellido materno" + required /> @@ -124,6 +133,7 @@ export default function RegisterAlta() { name="fecha" value={form.fecha} onChange={handleChange} + required /> @@ -140,17 +150,17 @@ export default function RegisterAlta() {
- - - - + + +
- {listMajor.map((c) => (
+ + + + + + + + + + + + {machines.map((machine) => { + const disponible = machine.activo.data[0] === 1; + + return ( + + + + + + + + ); + })} + +
UbicaciónNombre EquipoPlataformaÁreaDisponible
{machine.ubicacion}{machine.nombre_equipo} + {machine.plataforma.nombre} + {machine.areaUbicacion.area}{disponible ? "Sí" : "No"}
+
+ ); +} diff --git a/app/(private)/Monitor/Page.module.css b/app/(private)/Monitor/Page.module.css index 7ea84a2..d2d66f1 100644 --- a/app/(private)/Monitor/Page.module.css +++ b/app/(private)/Monitor/Page.module.css @@ -39,3 +39,39 @@ .resetButton:hover { background-color: #cc0000; } + +.WINDOWS::before { + content: ""; + display: inline-block; + width: 20px; + height: 20px; + background-image: url("/windows.png"); + background-size: contain; + background-repeat: no-repeat; + margin-right: 6px; + vertical-align: middle; +} + +.MACINTOSH::before { + content: ""; + display: inline-block; + width: 20px; + height: 20px; + background-image: url("/apple.png"); + background-size: contain; + background-repeat: no-repeat; + margin-right: 6px; + vertical-align: middle; +} + +.PROFESORES::before { + content: ""; + display: inline-block; + width: 20px; + height: 20px; + background-image: url("/teacher.png"); + background-size: contain; + background-repeat: no-repeat; + margin-right: 6px; + vertical-align: middle; +} \ No newline at end of file diff --git a/app/(private)/Monitor/page.tsx b/app/(private)/Monitor/page.tsx index f81c840..d78236b 100644 --- a/app/(private)/Monitor/page.tsx +++ b/app/(private)/Monitor/page.tsx @@ -1,23 +1,46 @@ +import Toggle from "@/app/Components/Global/Toggle/Toggle"; import MachineTable from "./MachineTable"; import styles from "./Page.module.css"; +import ToggleTable from "@/app/Components/Global/Toggle/ToggleTable"; +import MachineTableActive from "./MachineTableActive"; export default async function Page(props: { - searchParams?: Promise<{ numAcount: string }>; + searchParams?: Promise<{ numAcount: string; key: string }>; }) { const params = await props.searchParams; const numAcount = params?.numAcount ?? null; + const key = params?.key ?? "active"; return (
-

MONITOR DE MÁQUINAS DISPONIBLES

+

MONITOR DE MÁQUINAS

- +
- - + + + + ), + }, + { + key: "disable", + label: "No Disponibles", + content: ( + <> + + + ), + }, + ]} + />
); } diff --git a/app/Components/Alta/registerAlta.css b/app/Components/Alta/registerAlta.css index f81c1d8..a14c892 100644 --- a/app/Components/Alta/registerAlta.css +++ b/app/Components/Alta/registerAlta.css @@ -8,6 +8,11 @@ color: #ffffff; } +.buttonInscription{ + background-color: #007a5b; + color: white; +} + .buttonSave:not(:disabled):hover { background-color: #001f5c; } diff --git a/app/Components/Alta/registerAlta.tsx b/app/Components/Alta/registerAlta.tsx index b15cd5d..54578b9 100644 --- a/app/Components/Alta/registerAlta.tsx +++ b/app/Components/Alta/registerAlta.tsx @@ -62,9 +62,19 @@ export default function RegisterAlta(props: urlProp) { e.preventDefault(); try { - await apiClient.post("/alumno", form); + const payload = { + id_cuenta: Number(form.cuenta), + nombre: `${form.apellidoP} ${form.apellidoM} ${form.nombre}`.trim(), + fecha_nacimiento: form.fecha.replaceAll("-", ""), + correo: form.email || undefined, + id_carrera: Number(form.carrera), + genero: form.genero || undefined, + }; + + await apiClient.post("/student", payload); + setSaved(true); - toast.error("Alumno registrado correctamente"); + toast.success("Alumno registrado correctamente"); } catch (error) { console.error("Error al guardar alumno:", error); toast.error("Error al guardar alumno"); @@ -73,7 +83,7 @@ export default function RegisterAlta(props: urlProp) { const handleInscripcion = (e: React.MouseEvent) => { e.preventDefault(); - router.push("/Inscripcion"); + router.push(`/Inscripcion?numAcount=${form.cuenta}`); }; return ( @@ -150,7 +160,12 @@ export default function RegisterAlta(props: urlProp) {
- @@ -160,7 +175,12 @@ export default function RegisterAlta(props: urlProp) {
- {listMajor.map((c) => (
+ +
+ {options.find((opt) => opt.key === view)?.content} +
+
+ ); +} +//IO diff --git a/app/Components/Selection/Selection.tsx b/app/Components/Selection/Selection.tsx index 27630bc..26370d8 100644 --- a/app/Components/Selection/Selection.tsx +++ b/app/Components/Selection/Selection.tsx @@ -20,15 +20,15 @@ function Selection({ const options = [ { name: "WINDOWS", - img: "https://images.icon-icons.com/2235/PNG/512/windows_os_logo_icon_134678.png", + img: "/windows.png", }, { name: "MACINTOSH", - img: "https://upload.wikimedia.org/wikipedia/commons/f/fa/Apple_logo_black.svg", + img: "apple.png", }, { name: "PROFESORES", - img: "https://cdn-icons-png.flaticon.com/512/3135/3135715.png", + img: "teacher.png", }, ]; diff --git a/app/Components/Selection/SelectionCo.tsx b/app/Components/Selection/SelectionCo.tsx index ba51d12..7393ec8 100644 --- a/app/Components/Selection/SelectionCo.tsx +++ b/app/Components/Selection/SelectionCo.tsx @@ -15,19 +15,18 @@ function SelectionCo({ const options = [ { name: "WINDOWS", - img: "https://images.icon-icons.com/2235/PNG/512/windows_os_logo_icon_134678.png", + img: "/windows.png", }, { name: "MACINTOSH", - img: "https://upload.wikimedia.org/wikipedia/commons/f/fa/Apple_logo_black.svg", + img: "apple.png", }, { name: "PROFESORES", - img: "https://cdn-icons-png.flaticon.com/512/3135/3135715.png", + img: "teacher.png", }, ]; - // ✅ SOLO las inscritas const opcionesInscritas = options.filter((option) => plataformasInscritas.includes(option.name), ); diff --git a/public/apple.png b/public/apple.png new file mode 100644 index 0000000000000000000000000000000000000000..af00ef5b26abdcc881ecdbd8668914bebfac146d GIT binary patch literal 5893 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ΠqB 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 oICjts;KkrBmH$7#C=bvOeEWAot8~�QmJpSzD=6!Rqz@0lF>Rd;kCd literal 0 HcmV?d00001 diff --git a/public/windows.png b/public/windows.png new file mode 100644 index 0000000000000000000000000000000000000000..50d3d77bae187cd5f023f255e262a6af78eb2518 GIT binary patch literal 6027 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 Date: Fri, 23 Jan 2026 20:09:10 -0600 Subject: [PATCH 06/20] tring to do mesa --- app/(private)/AsignacionMesas/page.tsx | 19 +++++++------------ app/(private)/Monitor/MachineTable.tsx | 11 +++++------ app/(private)/Monitor/page.tsx | 2 +- app/Components/CheckBoxMesa.tsx | 22 +++++++++++++++++----- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/app/(private)/AsignacionMesas/page.tsx b/app/(private)/AsignacionMesas/page.tsx index e624d29..d9ce25e 100644 --- a/app/(private)/AsignacionMesas/page.tsx +++ b/app/(private)/AsignacionMesas/page.tsx @@ -8,13 +8,13 @@ import Table from "@/app/Components/Global/table"; import { envConfig } from "@/app/lib/config"; import { GetRegisterStudent } from "@/app/lib/getRegisterStudent"; +import axios from "axios"; async function getInscripcion(idCuenta: number) { try { - const res = await fetch( - `${envConfig.apiUrl}/alumno-inscrito/${idCuenta}`, - { cache: "no-store" } - ); + const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito/${idCuenta}`, { + cache: "no-store", + }); if (!res.ok) throw new Error("Error al cargar inscripción"); return await res.json(); @@ -42,13 +42,8 @@ export default async function Page(props: { if (numAcount) { const idCuenta = parseInt(numAcount); - const result = await GetRegisterStudent(idCuenta); - - if (result.error) { - errorMessage = result.error; - } else { - student = result[0]?.alumno; - } + const result = await axios.get(`${envConfig.apiUrl}/bitacora-mesa/cuenta/${idCuenta}`); + student = result.data const inscResult = await getInscripcion(idCuenta); if (!inscResult.error && Array.isArray(inscResult)) { @@ -104,7 +99,7 @@ export default async function Page(props: { { key: "Liberar", label: "Cancelar mesa", - content: , + content: , }, ]} /> diff --git a/app/(private)/Monitor/MachineTable.tsx b/app/(private)/Monitor/MachineTable.tsx index 5045256..8f69da3 100644 --- a/app/(private)/Monitor/MachineTable.tsx +++ b/app/(private)/Monitor/MachineTable.tsx @@ -26,7 +26,7 @@ export default function MachineTable() { try { const res = await fetch( `${process.env.NEXT_PUBLIC_API_URL}/equipo/disable`, - { cache: "no-store" } + { cache: "no-store" }, ); const data = await res.json(); setMachines(data); @@ -66,12 +66,11 @@ export default function MachineTable() { {machine.ubicacion} {machine.nombre_equipo} - {machine.plataforma.nombre} - {machine.areaUbicacion.area} - - {disponible ? "Sí" : "No"} + + {machine.plataforma.nombre} + {machine.areaUbicacion.area} + {disponible ? "Sí" : "No"} ); })} diff --git a/app/(private)/Monitor/page.tsx b/app/(private)/Monitor/page.tsx index d78236b..b05be2e 100644 --- a/app/(private)/Monitor/page.tsx +++ b/app/(private)/Monitor/page.tsx @@ -18,7 +18,7 @@ export default async function Page(props: {
- (null); return ( @@ -39,6 +47,10 @@ export default function CheckBoxMesa({ {modo === "Cuenta" && } {modo === "Mesa" && } + + {student && ( + + )} ); } From 9e0fad5c34f07539e279612c1bd079db5cb94618 Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Tue, 27 Jan 2026 12:27:03 -0600 Subject: [PATCH 07/20] get equipos by student and ubicacion --- app/(private)/AsignacionEquipo/page.tsx | 2 +- app/Components/CheckBoxEquipo.tsx | 130 ++++++++++++++++++++++-- app/Components/SearchEquipo.tsx | 61 +++++++++-- 3 files changed, 175 insertions(+), 18 deletions(-) diff --git a/app/(private)/AsignacionEquipo/page.tsx b/app/(private)/AsignacionEquipo/page.tsx index cc1fe9f..013836d 100644 --- a/app/(private)/AsignacionEquipo/page.tsx +++ b/app/(private)/AsignacionEquipo/page.tsx @@ -124,7 +124,7 @@ export default async function Page(props: { label: "Cancelar tiempo", content: ( <> - + ), }, diff --git a/app/Components/CheckBoxEquipo.tsx b/app/Components/CheckBoxEquipo.tsx index 375d6f2..0e67e30 100644 --- a/app/Components/CheckBoxEquipo.tsx +++ b/app/Components/CheckBoxEquipo.tsx @@ -1,15 +1,118 @@ "use client"; - -import { useState } from "react"; +import { useEffect, useState } from "react"; import SearchUser from "./Global/SearchUser/searchUser"; -import SearchBoxEquipo from "./SearchEquipo"; +import SearchEquipo from "./SearchEquipo"; +import { envConfig } from "../lib/config"; +import axios from "axios"; +import Information from "./Global/Information/information"; +import toast from "react-hot-toast"; -export default function CheckBoxEquipo({ - numAcount, -}: { +async function getEquipoByCount(idCuenta: number) { + try { + const res = await axios.get( + `${envConfig.apiUrl}/bitacora/cuenta/${idCuenta}`, + ); + return res.data; + } catch (error: any) { + if (axios.isAxiosError(error)) { + return { + error: error.response?.data?.message || "Error al consultar equipo", + }; + } + + return { error: "Error desconocido" }; + } +} + +async function getEquipoId(idEquipo: number) { + try { + const res = await axios.get( + `${envConfig.apiUrl}/bitacora/equipo/${idEquipo}`, + ); + return res.data; + } catch (error: any) { + if (axios.isAxiosError(error)) { + return { + error: error.response?.data?.message || "Error al consultar equipo", + }; + } + + return { error: "Error desconocido" }; + } +} + +interface Props { numAcount?: string | null; -}) { - const [modo, setModo] = useState<"Equipo" | "Cuenta" | null>(null); + machine?: string | null; +} + +export default function CheckBoxEquipo({ numAcount, machine }: Props) { + const [modo, setModo] = useState<"Equipo" | "Cuenta" | null>("Cuenta"); + const [bitacora, setBitacora] = useState(null); + const [tiempoRestante, setTiempoRestante] = useState(""); + + useEffect(() => { + if (!numAcount) return; + + const idCuenta = parseInt(numAcount); + if (isNaN(idCuenta)) return; + + const fetchData = async () => { + const result = await getEquipoByCount(idCuenta); + + if (result?.error) { + toast.error(result.error); + setBitacora(null); + } else { + setBitacora(result); + } + }; + + fetchData(); + }, [numAcount]); + + useEffect(() => { + if (!machine) return; + + const idCuenta = parseInt(machine); + if (isNaN(idCuenta)) return; + + const fetchData = async () => { + const result = await getEquipoId(idCuenta); + + if (result?.error) { + toast.error(result.error); + setBitacora(null); + } else { + setBitacora(result); + } + }; + + fetchData(); + }, [machine]); + + useEffect(() => { + if (!bitacora) return; + + const entrada = new Date(bitacora.tiempo_entrada).getTime(); + const asignadoMs = bitacora.tiempo_asignado * 60 * 1000; + + const interval = setInterval(() => { + const ahora = Date.now(); + const restante = entrada + asignadoMs - ahora; + + if (restante <= 0) { + setTiempoRestante("agotado"); + clearInterval(interval); + } else { + const minutos = Math.floor(restante / 60000); + const segundos = Math.floor((restante % 60000) / 1000); + setTiempoRestante(`${minutos} minutos con ${segundos} segundos`); + } + }, 1000); + + return () => clearInterval(interval); + }, [bitacora]); return ( <> @@ -38,7 +141,16 @@ export default function CheckBoxEquipo({ {modo === "Cuenta" && } - {modo === "Equipo" && } + {modo === "Equipo" && } + + {bitacora && ( + + )} ); } diff --git a/app/Components/SearchEquipo.tsx b/app/Components/SearchEquipo.tsx index 80384a7..60c42c7 100644 --- a/app/Components/SearchEquipo.tsx +++ b/app/Components/SearchEquipo.tsx @@ -1,12 +1,57 @@ "use client"; -export default function SearchEquipo() { + +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; + +interface urlProp { + value: string | null; +} + +export default function SearchEquipo(props: urlProp) { + const [value, setValue] = useState(""); + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + useEffect(() => { + if (props.value) { + setValue(props.value); + } + }, [props.value]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const params = new URLSearchParams(searchParams.toString()); + if (value) { + params.set("machine", `${value}`); + router.push(`${pathname}?${params.toString()}`); + } + }; + return ( -
- -
- - -
-
+ <> +
+ +
+ { + const value = e.target.value; + if (/^\d*$/.test(value) && value.length <= 9) { + setValue(value); + } + }} + placeholder="Coloca un número de cuenta..." + inputMode="numeric" + pattern="[0-9]*" + /> + +
+
+ ); } +//IO \ No newline at end of file From fd97c21740d34ddf793d326c5b969f1fabfe33cd Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Tue, 27 Jan 2026 13:47:38 -0600 Subject: [PATCH 08/20] added handleButton to cancel the machine --- .../AsignacionEquipo/PlaticaGate.tsx | 1 - app/Components/AsignacionMesas.tsx | 1 - app/Components/CheckBoxEquipo.tsx | 101 ++++++++++++------ app/Components/Global/Toggle/Toggle.tsx | 1 + 4 files changed, 67 insertions(+), 37 deletions(-) diff --git a/app/(private)/AsignacionEquipo/PlaticaGate.tsx b/app/(private)/AsignacionEquipo/PlaticaGate.tsx index e9ea405..b85f876 100644 --- a/app/(private)/AsignacionEquipo/PlaticaGate.tsx +++ b/app/(private)/AsignacionEquipo/PlaticaGate.tsx @@ -69,7 +69,6 @@ export default function PlaticaGate({ inscripcion, idCuenta }: Props) { - diff --git a/app/Components/CheckBoxEquipo.tsx b/app/Components/CheckBoxEquipo.tsx index 0e67e30..be09b19 100644 --- a/app/Components/CheckBoxEquipo.tsx +++ b/app/Components/CheckBoxEquipo.tsx @@ -6,6 +6,7 @@ import { envConfig } from "../lib/config"; import axios from "axios"; import Information from "./Global/Information/information"; import toast from "react-hot-toast"; +import { useRouter } from "next/navigation"; async function getEquipoByCount(idCuenta: number) { try { @@ -50,45 +51,46 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) { const [modo, setModo] = useState<"Equipo" | "Cuenta" | null>("Cuenta"); const [bitacora, setBitacora] = useState(null); const [tiempoRestante, setTiempoRestante] = useState(""); + const [minutos, setMinutos] = useState(); + + const router = useRouter(); + + const fetchByCuenta = async (idCuenta: number) => { + const result = await getEquipoByCount(idCuenta); + + if (result?.error) { + toast.error(result.error); + setBitacora(null); + } else { + setBitacora(result); + } + }; + + const fetchByEquipo = async (idEquipo: number) => { + const result = await getEquipoId(idEquipo); + + if (result?.error) { + toast.error(result.error); + setBitacora(null); + } else { + setBitacora(result); + } + }; useEffect(() => { if (!numAcount) return; - const idCuenta = parseInt(numAcount); if (isNaN(idCuenta)) return; - const fetchData = async () => { - const result = await getEquipoByCount(idCuenta); - - if (result?.error) { - toast.error(result.error); - setBitacora(null); - } else { - setBitacora(result); - } - }; - - fetchData(); + fetchByCuenta(idCuenta); }, [numAcount]); useEffect(() => { if (!machine) return; + const idEquipo = parseInt(machine); + if (isNaN(idEquipo)) return; - const idCuenta = parseInt(machine); - if (isNaN(idCuenta)) return; - - const fetchData = async () => { - const result = await getEquipoId(idCuenta); - - if (result?.error) { - toast.error(result.error); - setBitacora(null); - } else { - setBitacora(result); - } - }; - - fetchData(); + fetchByEquipo(idEquipo); }, [machine]); useEffect(() => { @@ -106,6 +108,7 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) { 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`); } @@ -114,6 +117,25 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) { return () => clearInterval(interval); }, [bitacora]); + const handleButton = async (e: React.MouseEvent) => { + e.preventDefault(); + + await axios.patch( + `${envConfig.apiUrl}/bitacora/cancelar/${bitacora.id_bitacora}`, + { tiempo_asignado: minutos }, + ); + + toast.success("Tiempo cancelado"); + + if (modo === "Cuenta" && numAcount) { + fetchByCuenta(parseInt(numAcount)); + } + + if (modo === "Equipo" && machine) { + fetchByEquipo(parseInt(machine)); + } + }; + return ( <>
@@ -141,15 +163,24 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
{modo === "Cuenta" && } - {modo === "Equipo" && } + {modo === "Equipo" && } {bitacora && ( - + <> + + + )} ); diff --git a/app/Components/Global/Toggle/Toggle.tsx b/app/Components/Global/Toggle/Toggle.tsx index 6c3a237..8183867 100644 --- a/app/Components/Global/Toggle/Toggle.tsx +++ b/app/Components/Global/Toggle/Toggle.tsx @@ -26,6 +26,7 @@ export default function Toggle({ options, defaultView }: ToggleProps) { const params = new URLSearchParams(searchParams.toString()); params.set("key", key); + params.delete("machine"); router.replace(`${pathname}?${params.toString()}`); }; From 608ce4b11aa24df289938dd387ff144c1c7734bb Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Tue, 27 Jan 2026 16:11:17 -0600 Subject: [PATCH 09/20] modified machine --- .../AsignacionEquipo/PlaticaGate.tsx | 166 ++++++++++++++---- app/(private)/AsignacionEquipo/asignacion.css | 93 ++++++++++ app/(private)/AsignacionEquipo/page.tsx | 2 +- app/Components/CheckBoxEquipo.tsx | 18 +- app/lib/getEquipoByCount.ts | 19 ++ 5 files changed, 246 insertions(+), 52 deletions(-) create mode 100644 app/(private)/AsignacionEquipo/asignacion.css create mode 100644 app/lib/getEquipoByCount.ts diff --git a/app/(private)/AsignacionEquipo/PlaticaGate.tsx b/app/(private)/AsignacionEquipo/PlaticaGate.tsx index b85f876..7ce0813 100644 --- a/app/(private)/AsignacionEquipo/PlaticaGate.tsx +++ b/app/(private)/AsignacionEquipo/PlaticaGate.tsx @@ -4,21 +4,48 @@ import { useEffect, useState } from "react"; import { envConfig } from "@/app/lib/config"; import toast from "react-hot-toast"; +import "./asignacion.css"; +import { getEquipoByCount } from "@/app/lib/getEquipoByCount"; + type Props = { inscripcion: any[]; - idCuenta: number; + numAcount: number; }; -export default function PlaticaGate({ inscripcion, idCuenta }: Props) { +export default function PlaticaGate({ inscripcion, numAcount }: Props) { const [puedeContinuar, setPuedeContinuar] = useState(false); const [equipos, setEquipos] = useState([]); const [loadingEquipos, setLoadingEquipos] = useState(false); + const [equipoSeleccionado, setEquipoSeleccionado] = useState(null); + const [open, setOpen] = useState(false); + const [tiempo, setTiempo] = useState(15); + const [bitacora, setBitacora] = useState(null); + + const fetchByCuenta = async (idCuenta: number) => { + const result = await getEquipoByCount(idCuenta); + + if (result?.error) { + toast.error(result.error); + setBitacora(null); + } else { + setBitacora(result); + toast.error("Ya cuentas con un equipo asignado"); + } + }; + + useEffect(() => { + if (!numAcount) return; + const idCuenta = numAcount; + if (isNaN(idCuenta)) return; + + fetchByCuenta(idCuenta); + }, [numAcount]); 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) { @@ -31,17 +58,17 @@ export default function PlaticaGate({ inscripcion, idCuenta }: Props) { }, [inscripcion]); useEffect(() => { - if (!puedeContinuar || !idCuenta) return; + if (!puedeContinuar || !numAcount) return; const fetchEquipos = async () => { try { setLoadingEquipos(true); const res = await fetch( - `${envConfig.apiUrl}/equipo/student/${idCuenta}`, + `${envConfig.apiUrl}/equipo/student/${numAcount}`, { cache: "no-store", - } + }, ); if (!res.ok) { @@ -58,49 +85,120 @@ export default function PlaticaGate({ inscripcion, idCuenta }: Props) { }; fetchEquipos(); - }, [puedeContinuar, idCuenta]); + }, [puedeContinuar, numAcount]); if (!puedeContinuar) return null; + const submit = async (e: React.MouseEvent) => { + e.preventDefault(); + + if (!equipoSeleccionado) { + toast.error("Selecciona un equipo"); + return; + } + + try { + const body = { + tiempo_asignado: tiempo, + ubicacion: equipoSeleccionado.ubicacion ?? null, + id_equipo: equipoSeleccionado.id_equipo, + 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), + }); + + if (!res.ok) { + throw new Error("Error al asignar equipo"); + } + + toast.success("Equipo asignado correctamente"); + } catch (error) { + toast.error("No se pudo asignar el equipo"); + } + }; + + if (bitacora) { + return (<> + ); +} + return (
- + - setTiempo(Number(e.target.value))} + > + + + + + - +
- + {open && ( +
+ {equipos.length === 0 && ( +
+ No hay equipos disponibles +
+ )} + + {equipos.map((eq) => ( +
{ + setEquipoSeleccionado(eq); + setOpen(false); + }} + > + {eq.ubicacion} {eq.nombre_equipo} +
+ ))} +
+ )} +
diff --git a/app/(private)/AsignacionEquipo/asignacion.css b/app/(private)/AsignacionEquipo/asignacion.css new file mode 100644 index 0000000..997a905 --- /dev/null +++ b/app/(private)/AsignacionEquipo/asignacion.css @@ -0,0 +1,93 @@ +.customSelect { + border: 1px solid #ccc; + border-radius: 6px; + position: relative; + flex: 1; + width: 100%; + font-size: 14px; +} + +.customSelect.disabled { + opacity: 0.6; + pointer-events: none; +} + +.option { + padding: 10px 12px; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + background: #fff; +} + +.option:not(.disabled):hover { + background-color: #f0f0f0; +} + +.option.disabled { + cursor: not-allowed; + color: #999; +} + +.option.active { + background-color: #e6f0ff; + font-weight: 600; +} + +.option:hover { + background-color: #f0f0f0; +} + +/* ICONOS */ +.WINDOWS::before { + content: ""; + width: 20px; + height: 20px; + background: url("/windows.png") no-repeat center / contain; +} + +.MACINTOSH::before { + content: ""; + width: 20px; + height: 20px; + background: url("/apple.png") no-repeat center / contain; +} + +.PROFESORES::before { + content: ""; + width: 20px; + height: 20px; + background: url("/teacher.png") no-repeat center / contain; +} + +.selectHeader { + border-radius: 6px; + padding: 8px 12px; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + background: #fff; +} + +.placeholder { + color: #999; +} + +.arrow { + font-size: 12px; +} + +.options { + position: absolute; + bottom: 105%; + left: 0; + right: 0; + border: 1px solid #ccc; + border-radius: 6px; + background: #fff; + z-index: 10; + max-height: 250px; + overflow-y: auto; +} \ No newline at end of file diff --git a/app/(private)/AsignacionEquipo/page.tsx b/app/(private)/AsignacionEquipo/page.tsx index 013836d..e7975ae 100644 --- a/app/(private)/AsignacionEquipo/page.tsx +++ b/app/(private)/AsignacionEquipo/page.tsx @@ -112,7 +112,7 @@ export default async function Page(props: { )} diff --git a/app/Components/CheckBoxEquipo.tsx b/app/Components/CheckBoxEquipo.tsx index be09b19..38ae9ca 100644 --- a/app/Components/CheckBoxEquipo.tsx +++ b/app/Components/CheckBoxEquipo.tsx @@ -7,23 +7,7 @@ import axios from "axios"; import Information from "./Global/Information/information"; import toast from "react-hot-toast"; import { useRouter } from "next/navigation"; - -async function getEquipoByCount(idCuenta: number) { - try { - const res = await axios.get( - `${envConfig.apiUrl}/bitacora/cuenta/${idCuenta}`, - ); - return res.data; - } catch (error: any) { - if (axios.isAxiosError(error)) { - return { - error: error.response?.data?.message || "Error al consultar equipo", - }; - } - - return { error: "Error desconocido" }; - } -} +import { getEquipoByCount } from "../lib/getEquipoByCount"; async function getEquipoId(idEquipo: number) { try { diff --git a/app/lib/getEquipoByCount.ts b/app/lib/getEquipoByCount.ts new file mode 100644 index 0000000..a041689 --- /dev/null +++ b/app/lib/getEquipoByCount.ts @@ -0,0 +1,19 @@ +import axios from "axios"; +import { envConfig } from "./config"; + +export async function getEquipoByCount(idCuenta: number) { + try { + const res = await axios.get( + `${envConfig.apiUrl}/bitacora/cuenta/${idCuenta}`, + ); + return res.data; + } catch (error: any) { + if (axios.isAxiosError(error)) { + return { + error: error.response?.data?.message || "Error al consultar equipo", + }; + } + + return { error: "Error desconocido" }; + } +} \ No newline at end of file From 5f33ecd5aafd86c51f67620529c0d23754b322cb Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Tue, 27 Jan 2026 16:28:34 -0600 Subject: [PATCH 10/20] added filter --- app/(private)/Monitor/MachineTable.tsx | 101 +++++++++++------- app/(private)/Monitor/MachineTableActive.tsx | 105 ++++++++++++------- 2 files changed, 130 insertions(+), 76 deletions(-) diff --git a/app/(private)/Monitor/MachineTable.tsx b/app/(private)/Monitor/MachineTable.tsx index 8f69da3..fb4518a 100644 --- a/app/(private)/Monitor/MachineTable.tsx +++ b/app/(private)/Monitor/MachineTable.tsx @@ -3,48 +3,61 @@ import { useEffect, useState } from "react"; import styles from "./Page.module.css"; +type AreaUbicacion = { + id_area_ubicacion: number; + area: string; +}; + type Equipo = { id_equipo: number; nombre_equipo: string; ubicacion: string; - activo: { - data: number[]; - }; - plataforma: { - nombre: string; - }; - areaUbicacion: { - area: string; - }; + activo: { data: number[] }; + plataforma: { nombre: string }; + areaUbicacion: { area: string }; }; export default function MachineTable() { const [machines, setMachines] = useState([]); + const [areas, setAreas] = useState([]); + const [selectedArea, setSelectedArea] = useState("Todo"); const [loading, setLoading] = useState(true); const fetchMachines = async () => { - try { - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/equipo/disable`, - { cache: "no-store" }, - ); - const data = await res.json(); - setMachines(data); - } catch (error) { - console.error("Error al cargar equipos", error); - } finally { - setLoading(false); - } + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/equipo/disable`, + { cache: "no-store" }, + ); + setMachines(await res.json()); + setLoading(false); + }; + + const fetchAreas = async () => { + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`, + { cache: "no-store" }, + ); + setAreas(await res.json()); }; useEffect(() => { fetchMachines(); + fetchAreas(); }, []); if (loading) { return

Cargando equipos...

; } + const filteredMachines = + selectedArea === "Todo" + ? machines + : machines.filter( + (machine) => machine.areaUbicacion.area === selectedArea, + ); + + if (loading) return

Cargando equipos...

; + return (
@@ -56,24 +69,40 @@ export default function MachineTable() { + + + - {machines.map((machine) => { - const disponible = machine.activo.data[0] === 1; - - return ( - - - - - - - - ); - })} + {filteredMachines.map((machine) => ( + + + + + + + + ))}
Área Disponible
+ + + + + +
{machine.ubicacion}{machine.nombre_equipo} - {machine.plataforma.nombre} - {machine.areaUbicacion.area}{disponible ? "Sí" : "No"}
{machine.ubicacion}{machine.nombre_equipo} + {machine.plataforma.nombre} + {machine.areaUbicacion.area}{machine.activo.data[0] === 1 ? "Sí" : "No"}
diff --git a/app/(private)/Monitor/MachineTableActive.tsx b/app/(private)/Monitor/MachineTableActive.tsx index 29169ed..32ae172 100644 --- a/app/(private)/Monitor/MachineTableActive.tsx +++ b/app/(private)/Monitor/MachineTableActive.tsx @@ -3,77 +3,102 @@ import { useEffect, useState } from "react"; import styles from "./Page.module.css"; +type AreaUbicacion = { + id_area_ubicacion: number; + area: string; +}; + type Equipo = { id_equipo: number; nombre_equipo: string; ubicacion: string; - activo: { - data: number[]; - }; - plataforma: { - nombre: string; - }; - areaUbicacion: { - area: string; - }; + activo: { data: number[] }; + plataforma: { nombre: string }; + areaUbicacion: { area: string }; }; export default function MachineTableActive() { const [machines, setMachines] = useState([]); + const [areas, setAreas] = useState([]); + const [selectedArea, setSelectedArea] = useState("Todo"); const [loading, setLoading] = useState(true); const fetchMachines = async () => { - try { - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/equipo/active`, - { cache: "no-store" }, - ); - const data = await res.json(); - setMachines(data); - } catch (error) { - console.error("Error al cargar equipos", error); - } finally { - setLoading(false); - } + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/equipo/active`, + { cache: "no-store" }, + ); + setMachines(await res.json()); + setLoading(false); + }; + + const fetchAreas = async () => { + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`, + { cache: "no-store" }, + ); + setAreas(await res.json()); }; useEffect(() => { fetchMachines(); + fetchAreas(); }, []); - if (loading) { - return

Cargando equipos...

; - } + const filteredMachines = + selectedArea === "Todo" + ? machines + : machines.filter( + (machine) => machine.areaUbicacion.area === selectedArea, + ); + + if (loading) return

Cargando equipos...

; return (
- + + + + - {machines.map((machine) => { - const disponible = machine.activo.data[0] === 1; - - return ( - - - - - - - - ); - })} + {filteredMachines.map((machine) => ( + + + + + + + + ))}
Ubicación Nombre Equipo Plataforma Área Disponible
+ + + + + +
{machine.ubicacion}{machine.nombre_equipo} - {machine.plataforma.nombre} - {machine.areaUbicacion.area}{disponible ? "Sí" : "No"}
{machine.ubicacion}{machine.nombre_equipo} + {machine.plataforma.nombre} + {machine.areaUbicacion.area}{machine.activo.data[0] === 1 ? "Sí" : "No"}
From 856716fd605382fbc5b7fa1d2474872d40e316f7 Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Tue, 27 Jan 2026 16:56:35 -0600 Subject: [PATCH 11/20] added filter --- app/(private)/Monitor/MachineTableActive.tsx | 19 ++++++++++--------- app/(private)/Monitor/Page.module.css | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/(private)/Monitor/MachineTableActive.tsx b/app/(private)/Monitor/MachineTableActive.tsx index 32ae172..b8990ba 100644 --- a/app/(private)/Monitor/MachineTableActive.tsx +++ b/app/(private)/Monitor/MachineTableActive.tsx @@ -12,11 +12,12 @@ type Equipo = { id_equipo: number; nombre_equipo: string; ubicacion: string; - activo: { data: number[] }; - plataforma: { nombre: string }; - areaUbicacion: { area: string }; + plataforma: string; + area: string; + ocupado:boolean; }; + export default function MachineTableActive() { const [machines, setMachines] = useState([]); const [areas, setAreas] = useState([]); @@ -49,7 +50,7 @@ export default function MachineTableActive() { selectedArea === "Todo" ? machines : machines.filter( - (machine) => machine.areaUbicacion.area === selectedArea, + (machine) => machine.area === selectedArea, ); if (loading) return

Cargando equipos...

; @@ -89,14 +90,14 @@ export default function MachineTableActive() { {filteredMachines.map((machine) => ( - + {machine.ubicacion} {machine.nombre_equipo} - - {machine.plataforma.nombre} + + {machine.plataforma} - {machine.areaUbicacion.area} - {machine.activo.data[0] === 1 ? "Sí" : "No"} + {machine.area} + {!machine.ocupado ? "Sí" : "No"} ))} diff --git a/app/(private)/Monitor/Page.module.css b/app/(private)/Monitor/Page.module.css index d2d66f1..29c8352 100644 --- a/app/(private)/Monitor/Page.module.css +++ b/app/(private)/Monitor/Page.module.css @@ -17,6 +17,7 @@ } .ocupado { + background-color: rgb(173, 233, 253); color: red; font-weight: bold; } From 2df46f6b1938bb634abd3c68279cee541130023c Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Tue, 27 Jan 2026 17:32:42 -0600 Subject: [PATCH 12/20] now this can edit --- app/Components/Equipos/equipos.tsx | 79 +++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/app/Components/Equipos/equipos.tsx b/app/Components/Equipos/equipos.tsx index f77917f..f1a5d22 100644 --- a/app/Components/Equipos/equipos.tsx +++ b/app/Components/Equipos/equipos.tsx @@ -2,6 +2,7 @@ import axios from "axios"; import { envConfig } from "@/app/lib/config"; import { useState } from "react"; +import toast from "react-hot-toast"; interface Data { nombre_equipo: string; @@ -9,6 +10,7 @@ interface Data { id_plataforma: number; id_area_ubicacion: number; ubicacion: string; + ip:string } interface Plataforma { @@ -35,14 +37,15 @@ export default function Equipos() { const [idPlataforma, setIdPlataforma] = useState(""); const [idArea, setIdArea] = useState(""); + const [isEditing, setIsEditing] = useState(false); + 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`) + axios.get(`${envConfig.apiUrl}/area-ubicacion`), ]); const equipo = equipoRes.data as Data; @@ -57,6 +60,36 @@ export default function Equipos() { setArea(areaRes.data); }; + const handleSave = async () => { + if (!data) return; + + try { + await axios.patch(`${envConfig.apiUrl}/equipo`, { + id_equipo: data.id_equipo, + nombre_equipo: nombre, + ubicacion: ubicacion, + id_plataforma: idPlataforma, + id_area_ubicacion: idArea, + ip:data.ip + }); + + // Actualiza el estado local + setData({ + ...data, + nombre_equipo: nombre, + ubicacion, + id_plataforma: idPlataforma as number, + id_area_ubicacion: idArea as number, + }); + + setIsEditing(false); + toast.success("Equipo actualizado correctamente"); + } catch (error) { + console.error(error); + toast.error("Error al guardar el equipo"); + } + }; + return (
@@ -77,6 +110,7 @@ export default function Equipos() { setUbicacion(e.target.value)} /> @@ -84,12 +118,14 @@ export default function Equipos() { setNombre(e.target.value)} /> setIdArea(Number(e.target.value))} > @@ -114,12 +151,38 @@ export default function Equipos() {
- - + {isEditing ? ( + <> + + + + + ) : ( + <> + + + + )}
)} From d4b960b68a7e5ffdd85d13f236a0c5368c1d2813 Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Tue, 27 Jan 2026 17:51:44 -0600 Subject: [PATCH 13/20] create and update machine --- app/Components/Equipos/equipos.tsx | 95 +++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/app/Components/Equipos/equipos.tsx b/app/Components/Equipos/equipos.tsx index f1a5d22..073f906 100644 --- a/app/Components/Equipos/equipos.tsx +++ b/app/Components/Equipos/equipos.tsx @@ -10,7 +10,7 @@ interface Data { id_plataforma: number; id_area_ubicacion: number; ubicacion: string; - ip:string + ip: string; } interface Plataforma { @@ -38,6 +38,7 @@ export default function Equipos() { const [idArea, setIdArea] = useState(""); const [isEditing, setIsEditing] = useState(false); + const [isCreating, setIsCreating] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -70,7 +71,7 @@ export default function Equipos() { ubicacion: ubicacion, id_plataforma: idPlataforma, id_area_ubicacion: idArea, - ip:data.ip + ip: data.ip, }); // Actualiza el estado local @@ -90,19 +91,73 @@ export default function Equipos() { } }; + const handleNuevo = async () => { + try { + const [plataformaRes, areaRes] = await Promise.all([ + axios.get(`${envConfig.apiUrl}/alumno-inscrito/plataforma`), + axios.get(`${envConfig.apiUrl}/area-ubicacion`), + ]); + + setPlataforma(plataformaRes.data); + setArea(areaRes.data); + + setData({ + id_equipo: 0, + nombre_equipo: "", + ubicacion: "", + id_plataforma: 0, + id_area_ubicacion: 0, + ip: "", + }); + + setNombre(""); + setUbicacion(""); + setIdPlataforma(""); + setIdArea(""); + + setIsCreating(true); + setIsEditing(true); + } catch (error) { + toast.error("Error al preparar formulario"); + } + }; + + const handleCreate = async () => { + try { + await axios.post(`${envConfig.apiUrl}/equipo`, { + nombre_equipo: nombre, + ubicacion, + id_plataforma: idPlataforma, + id_area_ubicacion: idArea, + ip: "0.0.0.0", + }); + + toast.success("Equipo creado correctamente"); + + setIsCreating(false); + setIsEditing(false); + setData(null); + } catch (error: any) { + const message = + error?.response?.data?.message || "Error al crear el equipo"; + toast.error(message); + } + }; + return ( -
- setId(e.target.value)} - /> - - -
+ {!isCreating && ( +
+ setId(e.target.value)} + /> + +
+ )} {data && (
@@ -156,7 +211,7 @@ export default function Equipos() { @@ -164,14 +219,22 @@ export default function Equipos() { ) : ( <> -