merge Lino

This commit is contained in:
2026-01-28 17:31:36 -06:00
44 changed files with 1381 additions and 307 deletions
+19 -16
View File
@@ -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<string, number> = {
WINDOWS: 1,
@@ -28,17 +29,15 @@ export default function AddTime({
}: AddTimeProps) {
const router = useRouter();
// 🔹 selección plataforma
const [plataformaSeleccionada, setPlataformaSeleccionada] = useState<
string | null
>(null);
// 🔹 datos del recibo
const [folio, setFolio] = useState("");
const [amount, setAmount] = useState("");
const [date, setDate] = useState("");
// 🔹 restricción de fechas
const todayISO = new Date().toISOString().split("T")[0];
const [date, setDate] = useState(todayISO);
const day = new Date();
const year = day.getFullYear();
const month = day.getMonth();
@@ -47,7 +46,6 @@ export default function AddTime({
const minFecha = new Date(year, month, 1).toISOString().split("T")[0];
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
// 🔹 lógica central (AQUÍ crecerá después)
const handleSaveReceipt = async () => {
if (!numAcount) {
toast.error("Busca de nuevo al estudiante");
@@ -77,23 +75,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");
}
@@ -171,3 +173,4 @@ export default function AddTime({
</div>
);
}
//IO
+21
View File
@@ -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;
}
}
+33 -33
View File
@@ -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 (
<section className="containerSection">
@@ -55,22 +50,27 @@ export default async function Page(props: {
<h2 className="title">AGREGAR TIEMPO</h2>
<SearchUser value={numAcount} />
<div className="containeInformationTime">
<div className="firstPartInformationTime">
<SearchUser value={numAcount} />
{student && (
<>
<Information
NoCuenta={student.id_cuenta}
Nombre={student.nombre}
/>
{student && (
<>
<Information
NoCuenta={student.id_cuenta}
Nombre={student.nombre}
/>
<AddTime
numAcount={student.id_cuenta}
plataformasInscritas={plataformasInscritas}
/>
</>
)}
</div>
<AddTime
numAcount={student.id_cuenta}
plataformasInscritas={plataformasInscritas}
/>
</>
)}
{student && <Table headers={headers} data={tableData} />}
</div>
</section>
);
}
//IO
//IO
+8 -2
View File
@@ -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 (
<section className="containerSection">
<h1 className="title">ALTA USUARIO</h1>
<RegisterAlta />
<RegisterAlta numCount={numAcount}/>
</section>
);
}
+132 -35
View File
@@ -4,21 +4,48 @@ import { useEffect, useState } from "react";
import { envConfig } from "@/app/lib/config";
import toast from "react-hot-toast";
import { getEquipoByCount } from "@/app/lib/getEquipoByCount";
import "./asignacion.css";
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<any[]>([]);
const [loadingEquipos, setLoadingEquipos] = useState(false);
const [equipoSeleccionado, setEquipoSeleccionado] = useState<any>(null);
const [open, setOpen] = useState(false);
const [tiempo, setTiempo] = useState<number>(15);
const [bitacora, setBitacora] = useState<[]|null>(null);
const fetchByCuenta = async (idCuenta: number) => {
const result = await getEquipoByCount(idCuenta);
if (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,50 +85,120 @@ export default function PlaticaGate({ inscripcion, idCuenta }: Props) {
};
fetchEquipos();
}, [puedeContinuar, idCuenta]);
}, [puedeContinuar, numAcount]);
if (!puedeContinuar) return null;
const submit = async (e: React.MouseEvent<HTMLButtonElement>) => {
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");
}
setBitacora([])
toast.success("Equipo asignado correctamente");
} catch (error) {
toast.error("No se pudo asignar el equipo");
}
};
if (bitacora) {
return <></>;
}
return (
<div className="containerForm">
<label style={{ marginTop: "1rem" }}>
Seleccionar tiempo
</label>
<label>Seleccionar tiempo</label>
<select>
<option>6 minutos</option>
<option>15 minutos</option>
<option>30 minutos</option>
<option>40 minutos</option>
<option>60 minutos</option>
<option>90 minutos</option>
<select
value={tiempo}
onChange={(e) => setTiempo(Number(e.target.value))}
>
<option value={15}>15 minutos</option>
<option value={30}>30 minutos</option>
<option value={40}>40 minutos</option>
<option value={60}>60 minutos</option>
<option value={90}>90 minutos</option>
</select>
<label style={{ marginTop: "1rem" }}>
Seleccione un equipo
</label>
<label style={{ marginTop: "1rem" }}>Seleccione un equipo</label>
<div className="groupInput">
<select disabled={loadingEquipos || equipos.length === 0}>
{loadingEquipos && (
<option>Cargando equipos...</option>
)}
<div
className={`customSelect ${open ? "open" : ""} ${loadingEquipos ? "disabled" : ""}`}
>
<div
className={`selectHeader ${equipoSeleccionado && equipoSeleccionado.plataforma?.nombre?.toUpperCase()}`}
onClick={() => !loadingEquipos && setOpen(!open)}
>
{equipoSeleccionado ? (
<span
className={equipoSeleccionado?.plataforma?.nombre?.toUpperCase()}
>
{equipoSeleccionado.ubicacion}{" "}
{equipoSeleccionado.nombre_equipo}
</span>
) : (
<span className="placeholder">
{loadingEquipos
? "Cargando equipos..."
: "Seleccione un equipo"}
</span>
)}
{!loadingEquipos && equipos.length === 0 && (
<option>No hay equipos disponibles</option>
)}
<span className="arrow">{open ? "▲" : "▼"}</span>
</div>
{!loadingEquipos &&
equipos.map((eq) => (
<option key={eq.id_equipo} value={eq.id_equipo}>
{eq.ubicacion} {eq.nombre_equipo} ({eq.plataforma?.nombre})
</option>
))}
</select>
{open && (
<div className="options">
{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>
<button
className="button buttonSearch"
disabled={loadingEquipos || equipos.length === 0}
disabled={!equipoSeleccionado || loadingEquipos}
onClick={submit}
>
Asignar Equipo
</button>
@@ -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;
}
+2 -2
View File
@@ -112,7 +112,7 @@ export default async function Page(props: {
<PlaticaGate
inscripcion={inscripcion}
idCuenta={student.id_cuenta}
numAcount={student.id_cuenta}
/>
</>
)}
@@ -124,7 +124,7 @@ export default async function Page(props: {
label: "Cancelar tiempo",
content: (
<>
<CheckBoxEquipo numAcount={params?.numAcount} />
<CheckBoxEquipo numAcount={params?.numAcount} machine={params?.machine} />
</>
),
},
+8 -13
View File
@@ -7,13 +7,13 @@ import Toggle from "@/app/Components/Global/Toggle/Toggle";
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();
@@ -39,13 +39,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)) {
@@ -101,8 +96,8 @@ export default async function Page(props: {
},
{
key: "Liberar",
label: "Liberar mesa",
content: <CheckBoxMesa numAcount={numAcount} />,
label: "Cancelar mesa",
content: <CheckBoxMesa numAcount={numAcount} student={student} />,
},
]}
/>
+17 -3
View File
@@ -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: (
<>
<BitacoraEquipo />
@@ -51,7 +53,7 @@ export default async function Page(props: {
},
{
key: "Alumno",
label: "Bitacora alumno",
label: "Bitácora alumno",
content: (
<>
<SearchUserWithDate value={numAcount} />
@@ -61,7 +63,7 @@ export default async function Page(props: {
},
{
key: "Mesas",
label: "Bitacora mesas",
label: "Bitácora mesas",
content: (
<>
<BitacoraMesas />
@@ -77,6 +79,18 @@ export default async function Page(props: {
</>
),
},
{
key: "EliminarSanciones",
label: "Eliminar Sanciones",
content: (
<>
<div className="containerSection">
<SearchUser value={numAcount} />
{/* <QuitarSancion data={student} /> */}
</div>
</>
),
},
]}
/>
</section>
+4
View File
@@ -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}
/>
),
},
@@ -16,6 +16,7 @@ export default function InformacionEquipo({
defaultKey?: string;
}) {
const [id, setId] = useState<number | null>(null);
const [ubicacion, setUbicacion] = useState<string | null>(null);
return (
<section className="containerSection">
@@ -36,10 +37,10 @@ export default function InformacionEquipo({
<>
<SelectorEquipo
onSearch={(sala) => {
setId(sala);
setUbicacion(sala);
}}
/>
<ProgramSelector />,
<ProgramSelector key={1} ubicacion={ubicacion} />,
</>
),
},
@@ -53,7 +54,7 @@ export default function InformacionEquipo({
setId(sala);
}}
/>
<ProgramSelector />
<ProgramSelector key={2} id={id} />
</>
),
},
@@ -12,11 +12,15 @@
flex-direction: column;
align-items: start;
}
.form-container {
gap: 10px;
}
.form-container label {
font-size: 14px;
margin-right: 10px; /* espacio entre "Salas" y la caja */
display: inline-block; /* lo hace estar en la misma línea */
vertical-align: middle; /* alinea verticalmente con el select */
display: inline-block;
vertical-align: middle;
}
.form-container select {
@@ -25,8 +29,9 @@
border: 1px solid #ccc;
border-radius: 6px;
outline: none;
vertical-align: middle; /* asegura alineación */
vertical-align: middle;
transition: all 0.2s ease-in-out;
margin-bottom: 1rem;
}
.form-container select:hover {
@@ -43,9 +48,9 @@
.checkbox-grid {
display: grid;
grid-template-columns: repeat(3, 240px); /* ancho fijo por columna */
gap: 6px 20px;
gap: 10px 20px;
flex-wrap: wrap;
margin-right: 2px;
justify-content: start;
}
.checkbox-grid label {
@@ -54,6 +59,7 @@
font-size: 13px;
cursor: pointer;
white-space: nowrap;
width: fit-content;
}
.checkbox-grid input[type="checkbox"] {
+65 -37
View File
@@ -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<Equipo[]>([]);
const [areas, setAreas] = useState<AreaUbicacion[]>([]);
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`,
{ 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 <p>Cargando equipos...</p>;
}
const filteredMachines =
selectedArea === "Todo"
? machines
: machines.filter(
(machine) => machine.areaUbicacion.area === selectedArea,
);
if (loading) return <p>Cargando equipos...</p>;
return (
<div className={styles.tableContainer}>
<table className={styles.machineTable}>
@@ -56,25 +69,40 @@ export default function MachineTable() {
<th>Área</th>
<th>Disponible</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>
{machines.map((machine) => {
const disponible = machine.activo.data[0] === 1;
return (
<tr key={machine.id_equipo}>
<td>{machine.ubicacion}</td>
<td>{machine.nombre_equipo}</td>
<td>{machine.plataforma.nombre}</td>
<td>{machine.areaUbicacion.area}</td>
<td
>
{disponible ? "Sí" : "No"}
</td>
</tr>
);
})}
{filteredMachines.map((machine) => (
<tr key={machine.id_equipo}>
<td>{machine.ubicacion}</td>
<td>{machine.nombre_equipo}</td>
<td className={styles[machine.plataforma.nombre]}>
{machine.plataforma.nombre}
</td>
<td>{machine.areaUbicacion.area}</td>
<td>{machine.activo.data[0] === 1 ? "Sí" : "No"}</td>
</tr>
))}
</tbody>
</table>
</div>
@@ -0,0 +1,107 @@
"use client";
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;
plataforma: string;
area: string;
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 fetchMachines = async () => {
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();
}, []);
const filteredMachines =
selectedArea === "Todo"
? machines
: machines.filter(
(machine) => machine.area === selectedArea,
);
if (loading) return <p>Cargando equipos...</p>;
return (
<div className={styles.tableContainer}>
<table className={styles.machineTable}>
<thead>
<tr>
<th>Ubicación</th>
<th>Nombre Equipo</th>
<th>Plataforma</th>
<th>Área</th>
<th>Disponible</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>
{filteredMachines.map((machine) => (
<tr key={machine.id_equipo} className={!machine.ocupado ? '':styles.ocupado}>
<td>{machine.ubicacion}</td>
<td>{machine.nombre_equipo}</td>
<td className={styles[machine.plataforma]}>
{machine.plataforma}
</td>
<td>{machine.area}</td>
<td>{!machine.ocupado ? "Sí" : "No"}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
+37
View File
@@ -17,6 +17,7 @@
}
.ocupado {
background-color: rgb(173, 233, 253);
color: red;
font-weight: bold;
}
@@ -39,3 +40,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;
}
+30 -7
View File
@@ -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 (
<section className="containerSection">
<h2 className="title">MONITOR DE MÁQUINAS DISPONIBLES</h2>
<h2 className="title">MONITOR DE MÁQUINAS</h2>
<div className={styles.actions}>
<button className={styles.resetButton}>
Actualizar información
</button>
<button className={styles.resetButton}>Actualizar información</button>
</div>
<MachineTable />
<Toggle
defaultView={key}
options={[
{
key: "active",
label: "Disponibles",
content: (
<>
<MachineTableActive />
</>
),
},
{
key: "disable",
label: "No Disponibles",
content: (
<>
<MachineTable />
</>
),
},
]}
/>
</section>
);
}
+1 -4
View File
@@ -27,10 +27,7 @@ export default async function Page(props: {
{errorMessage && <ShowError key={Date.now()} message={errorMessage} />}
<h2 className="title"> Quitar Sanciones </h2>
<div className="containerSection">
<SearchUser value={numAcount} />
<QuitarSancion data={student} />
</div>
</>
);
}
+5
View File
@@ -8,6 +8,11 @@
color: #ffffff;
}
.buttonInscription{
background-color: #007a5b;
color: white;
}
.buttonSave:not(:disabled):hover {
background-color: #001f5c;
}
+43 -13
View File
@@ -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<Carrera[]>([]);
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,14 +58,23 @@ export default function RegisterAlta() {
(value) => value.trim() !== "",
);
const handleSave = async (e: React.MouseEvent) => {
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");
@@ -68,7 +83,7 @@ export default function RegisterAlta() {
const handleInscripcion = (e: React.MouseEvent) => {
e.preventDefault();
toast.success("Continuar con inscripción");
router.push(`/Inscripcion?numAcount=${form.cuenta}`);
};
return (
@@ -81,6 +96,7 @@ export default function RegisterAlta() {
value={form.cuenta}
onChange={handleChange}
placeholder="Coloca un número de cuenta..."
required
/>
</div>
@@ -92,6 +108,7 @@ export default function RegisterAlta() {
value={form.nombre}
onChange={handleChange}
placeholder="Coloca el nombre"
required
/>
</div>
@@ -103,6 +120,7 @@ export default function RegisterAlta() {
value={form.apellidoP}
onChange={handleChange}
placeholder="Coloca el apellido paterno"
required
/>
</div>
@@ -114,6 +132,7 @@ export default function RegisterAlta() {
value={form.apellidoM}
onChange={handleChange}
placeholder="Coloca el apellido materno"
required
/>
</div>
@@ -124,6 +143,7 @@ export default function RegisterAlta() {
name="fecha"
value={form.fecha}
onChange={handleChange}
required
/>
</div>
@@ -140,17 +160,27 @@ export default function RegisterAlta() {
<div className="containerForm">
<label className="label">Género</label>
<select name="genero" value={form.genero} onChange={handleChange}>
<select
name="genero"
value={form.genero}
onChange={handleChange}
required
>
<option value="">Selecciona género</option>
<option value="MUJER">Mujer</option>
<option value="HOMBRE">Hombre</option>
<option value="OTRO">Otro</option>
<option value="Femenino">Femenino</option>
<option value="Masculino">Masculino</option>
<option value="otro">Otro</option>
</select>
</div>
<div className="containerForm">
<label className="label">Carrera</label>
<select name="carrera" value={form.carrera} onChange={handleChange}>
<select
name="carrera"
value={form.carrera}
onChange={handleChange}
required
>
<option value="">Selecciona la carrera</option>
{listMajor.map((c) => (
<option key={c.id_carrera} value={c.id_carrera}>
@@ -170,7 +200,7 @@ export default function RegisterAlta() {
</button>
<button
className="button"
className="button buttonInscription"
disabled={!saved}
onClick={handleInscripcion}
>
-1
View File
@@ -70,7 +70,6 @@ export default function AsignacionMesas({
</label>
<select>
<option>6 minutos</option>
<option>15 minutos</option>
<option>30 minutos</option>
<option>40 minutos</option>
+133 -8
View File
@@ -1,15 +1,122 @@
"use client";
import { useEffect, useState } from "react";
import { envConfig } from "../lib/config";
import { useState } from "react";
import { getEquipoByCount } from "../lib/getEquipoByCount";
import SearchUser from "./Global/SearchUser/searchUser";
import SearchBoxEquipo from "./SearchEquipo";
import SearchEquipo from "./SearchEquipo";
import axios from "axios";
import Information from "./Global/Information/information";
import toast from "react-hot-toast";
export default function CheckBoxEquipo({
numAcount,
}: {
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<any>(null);
const [tiempoRestante, setTiempoRestante] = useState<string>("");
const [minutos, setMinutos] = useState<number>();
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;
fetchByCuenta(idCuenta);
}, [numAcount]);
useEffect(() => {
if (!machine) return;
const idEquipo = parseInt(machine);
if (isNaN(idEquipo)) return;
fetchByEquipo(idEquipo);
}, [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);
setMinutos(minutos + 1);
const segundos = Math.floor((restante % 60000) / 1000);
setTiempoRestante(`${minutos} minutos con ${segundos} segundos`);
}
}, 1000);
return () => clearInterval(interval);
}, [bitacora]);
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
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 (
<>
@@ -38,7 +145,25 @@ export default function CheckBoxEquipo({
</div>
{modo === "Cuenta" && <SearchUser value={numAcount ?? null} />}
{modo === "Equipo" && <SearchBoxEquipo />}
{modo === "Equipo" && <SearchEquipo value={machine ?? null} />}
{bitacora && (
<>
<Information
NoCuenta={bitacora.alumno_inscrito.alumno.id_cuenta}
Nombre={bitacora.alumno_inscrito.alumno.nombre}
Tiempo={tiempoRestante}
Equipo={bitacora.equipo.ubicacion}
/>
<button
className="button buttonSearch"
style={{ marginTop: "1rem" }}
onClick={handleButton}
>
Cancelar tiempo
</button>
</>
)}
</>
);
}
+19 -6
View File
@@ -2,14 +2,22 @@
import { useState } from "react";
import SearchUser from "./Global/SearchUser/searchUser";
import SearchBoxEquipo from "./SearchEquipo";
import SearchEquipo from "./SearchEquipo";
import Information from "./Global/Information/information";
import SearchMesa from "./SearchMesa";
export default function CheckBoxMesa({
numAcount,
}: {
numAcount?: string | null;
}) {
interface props {
numAcount: string | null;
student: {
id_cuenta: number;
nombre: string;
carrera:{
carrera:string;
}
};
}
export default function CheckBoxMesa({ numAcount, student }: props) {
const [modo, setModo] = useState<"Mesa" | "Cuenta" | null>(null);
return (
@@ -40,6 +48,11 @@ export default function CheckBoxMesa({
{modo === "Cuenta" && <SearchUser value={numAcount ?? null} />}
{modo === "Mesa" && <SearchMesa />}
{modo === "Mesa" && <SearchEquipo value={ null}/>}
{student && (
<Information NoCuenta={student.id_cuenta} nombre={student.nombre} carrera={student.carrera?.carrera}/>
)}
</>
);
}
+145 -19
View File
@@ -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,16 @@ export default function Equipos() {
const [idPlataforma, setIdPlataforma] = useState<number | "">("");
const [idArea, setIdArea] = useState<number | "">("");
const [isEditing, setIsEditing] = useState(false);
const [isCreating, setIsCreating] = 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,19 +61,103 @@ 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");
}
};
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 (
<form className="containerForm" onSubmit={handleSubmit}>
<div className="groupInput">
<input
placeholder="Número de Equipo a buscar..."
value={id}
onChange={(e) => setId(e.target.value)}
/>
<button type="submit" className="button buttonSearch">
Buscar
</button>
</div>
{!isCreating && (
<div className="groupInput">
<input
placeholder="Número de Equipo a buscar..."
value={id}
onChange={(e) => setId(e.target.value)}
/>
<button type="submit" className="button buttonSearch">
Buscar
</button>
</div>
)}
{data && (
<div>
@@ -77,6 +165,7 @@ export default function Equipos() {
<input
type="text"
value={ubicacion}
disabled={!isEditing}
onChange={(e) => setUbicacion(e.target.value)}
/>
@@ -84,12 +173,14 @@ export default function Equipos() {
<input
type="text"
value={nombre}
disabled={!isEditing}
onChange={(e) => setNombre(e.target.value)}
/>
<label className="label">Plataforma</label>
<select
value={idPlataforma}
disabled={!isEditing}
onChange={(e) => setIdPlataforma(Number(e.target.value))}
>
<option value="">Elige</option>
@@ -103,6 +194,7 @@ export default function Equipos() {
<label className="label">Área Ubicación</label>
<select
value={idArea}
disabled={!isEditing}
onChange={(e) => setIdArea(Number(e.target.value))}
>
<option value="">Elige</option>
@@ -114,12 +206,46 @@ export default function Equipos() {
</select>
<div className="containerButton" style={{ marginTop: "1rem" }}>
<button type="button" className="button buttonSearch">
Nuevo
</button>
<button type="button" className="button buttonSearch">
Editar
</button>
{isEditing ? (
<>
<button
type="button"
className="button buttonSearch"
onClick={isCreating ? handleCreate : handleSave}
>
Guardar
</button>
<button
type="button"
className="button buttonCancel"
onClick={() => {
setIsEditing(false);
setIsCreating(false);
setData(null);
}}
>
Cancelar
</button>
</>
) : (
<>
<button
type="button"
className="button buttonCharge"
onClick={handleNuevo}
>
Nuevo
</button>
<button
type="button"
className="button buttonSearch"
onClick={() => setIsEditing(true)}
>
Editar
</button>
</>
)}
</div>
</div>
)}
+1
View File
@@ -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()}`);
};
@@ -0,0 +1,53 @@
"use client";
import { usePathname, useSearchParams } from "next/navigation";
import { useRouter } from "next/navigation";
import { useState, ReactNode } from "react";
interface ToggleOption {
key: string;
label: string;
content: ReactNode;
}
interface ToggleProps {
options: ToggleOption[];
defaultView?: string;
}
export default function ToggleTable({ options, defaultView }: ToggleProps) {
const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
const [view, setView] = useState(defaultView || options[0].key);
const handleClick = (key: string) => {
setView(key);
const params = new URLSearchParams(searchParams.toString());
params.set("key", key);
router.replace(`${pathname}?${params.toString()}`);
};
return (
<section className="toggleSection" style={{maxWidth:"none"}}>
<div className="toggleGroup">
{options.map((opt) => (
<button
key={opt.key}
className={`toggleButton ${view === opt.key ? "active" : ""}`}
onClick={() => handleClick(opt.key)}
>
{opt.label}
</button>
))}
</div>
<div className="padding toggleContent">
{options.find((opt) => opt.key === view)?.content}
</div>
</section>
);
}
//IO
+12 -7
View File
@@ -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,13 +48,14 @@ function Impressions({ costs, numAcount }: ImpressionsProps) {
id_cuenta: numAcount,
numero_hojas: parseInt(pages),
monto: parseInt(cost) * parseInt(pages),
id_servicio,
});
if (result.error) {
if (result.error === "Token inválido") handleLogout();
else {
toast.error(result.error);
return;
if (result?.error) {
if (result.message === "Token inválido") {
handleLogout();
} else {
toast.error(result.message);
}
return;
}
@@ -101,7 +103,10 @@ function Impressions({ costs, numAcount }: ImpressionsProps) {
/>
</div>
<div className="groupInput" style={{height:"35px", flexWrap:"nowrap"}}>
<div
className="groupInput"
style={{ height: "35px", flexWrap: "nowrap" }}
>
<label className="label">Total:</label>
<label
style={{ width: "100%", minWidth: "200px", maxWidth: "500px" }}
@@ -2,6 +2,7 @@
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;
@@ -18,20 +19,23 @@ interface Programa {
programa: string;
}
export default function ProgramSelector() {
export default function ProgramSelector({
ubicacion,
id,
}: {
ubicacion?: string | null;
id?: number | null;
}) {
const [equipoSeleccionado, setEquipoSeleccionado] = useState<number | "">("");
const [equipos, setEquipos] = useState<Equipos[]>([]);
const [programas, setProgramas] = useState<Programa[]>([]);
const [checkboxes, setCheckboxes] = useState<Record<number, boolean>>({});
useEffect(() => {
const fetchData = async () => {
const [equiposRes, programasRes] = await Promise.all([
axios.get(`${envConfig.apiUrl}/equipo`),
const [programasRes] = await Promise.all([
axios.get(`${envConfig.apiUrl}/programa`),
]);
setEquipos(equiposRes.data);
setProgramas(programasRes.data);
const initialCheckboxes: Record<number, boolean> = {};
@@ -44,10 +48,6 @@ export default function ProgramSelector() {
fetchData();
}, []);
const handleChangeEquipo = (e: React.ChangeEvent<HTMLSelectElement>) => {
setEquipoSeleccionado(Number(e.target.value));
};
const handleTogglePrograma = (id: number) => {
setCheckboxes((prev) => ({
...prev,
@@ -66,9 +66,54 @@ export default function ProgramSelector() {
console.log("Programas seleccionados:", programasSeleccionados);
};
useEffect(() => {
if (!programas.length) return;
if (ubicacion === null && id === null) return;
const buildCheckboxes = async () => {
const baseCheckboxes: Record<number, boolean> = {};
programas.forEach((p) => {
baseCheckboxes[p.id_programa] = false;
});
try {
let res;
if (ubicacion && ubicacion !== "0") {
res = await axios.get(
`${envConfig.apiUrl}/programa-equipo/${ubicacion}`,
);
}
if (id) {
res = await axios.get(
`${envConfig.apiUrl}/area-ubicacion/programs/${id}`,
);
}
res?.data.forEach((item: any) => {
baseCheckboxes[item.id_programa] = true;
});
} catch (error: any) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 404) {
toast.error("Sin programas asignados");
} else {
toast.error("Error al cargar programas");
}
} else {
toast.error("Error inesperado");
}
}
setCheckboxes(baseCheckboxes);
};
buildCheckboxes();
}, [programas, ubicacion, id]);
return (
<form className="form-container" onSubmit={handleSubmit}>
<div className="checkbox-grid">
{programas.map((prog) => (
<label key={prog.id_programa}>
@@ -88,3 +133,4 @@ export default function ProgramSelector() {
</form>
);
}
//IO
@@ -1,7 +1,8 @@
"use client";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import axios from "axios";
interface Equipos {
id_equipo: number;
@@ -11,27 +12,44 @@ interface Equipos {
ubicacion: string;
activo: boolean;
ip: string;
areaUbicacion:{area:string}
plataforma:{nombre:string}
areaUbicacion: { area: string };
plataforma: { nombre: string };
}
interface Props {
onSearch: (id: number) => void;
onSearch: (ubicacion: string) => void;
}
export default function SelectorEquipo({ onSearch }: Props) {
const [sala, setSala] = useState<Equipos[]>();
const [selected, setSelected] = useState<number>();
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const paramSala = Number(searchParams.get("equipo")) || 0;
const [selected, setSelected] = useState<number>(paramSala);
const handleChangeEquipo = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelected(Number(e.target.value));
};
const value = e.target.value;
const numberValue = Number(e.target.value);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!selected) return;
onSearch(selected);
setSelected(numberValue);
if (numberValue === 0) {
onSearch('');
} else {
onSearch(value);
}
const params = new URLSearchParams(searchParams.toString());
if (numberValue === 0) {
params.delete("equipo");
} else {
params.set("equipo", value);
}
router.push(`${pathname}?${params.toString()}`);
};
useEffect(() => {
@@ -45,16 +63,19 @@ export default function SelectorEquipo({ onSearch }: Props) {
}, []);
return (
<form className="form-container" onSubmit={handleSubmit}>
<form className="form-container">
<label>Ubicacion de equipo</label>
<select value={selected} onChange={handleChangeEquipo}>
<option value="">-- Selecciona una equipo --</option>
<option value={0}>-- Selecciona una equipo --</option>
{sala &&
sala.map((eq) => (
<option key={eq.id_equipo} value={eq.id_equipo}>
{eq.ubicacion } {eq.nombre_equipo} {eq.plataforma.nombre} {eq.areaUbicacion.area}
</option>
<option key={eq.id_equipo} value={eq.ubicacion}>
{eq.ubicacion} {eq.nombre_equipo} {eq.plataforma.nombre}{" "}
{eq.areaUbicacion.area}
</option>
))}
</select>
</form>
);
}
//IO
@@ -1,7 +1,8 @@
"use client";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
import { useEffect, useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import axios from "axios";
interface Area {
id_area_ubicacion: number;
@@ -14,23 +15,38 @@ interface Props {
}
export default function SelectorSala({ onSearch }: Props) {
const [sala, setSala] = useState<Area[]>();
const [selected, setSelected] = useState<number>();
const [sala, setSala] = useState<Area[]>([]);
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const paramSala = Number(searchParams.get("sala")) || 0;
const [selected, setSelected] = useState<number>(paramSala);
useEffect(() => {
setSelected(paramSala);
onSearch(paramSala);
}, [paramSala]);
const handleChangeEquipo = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelected(Number(e.target.value));
};
const value = Number(e.target.value);
setSelected(value);
onSearch(value);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!selected) return;
onSearch(selected);
const params = new URLSearchParams(searchParams.toString());
if (value === 0) {
params.delete("sala");
} else {
params.set("sala", String(value));
}
router.push(`${pathname}?${params.toString()}`);
};
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`${envConfig.apiUrl}/area-ubicacion`);
setSala(response.data);
};
@@ -38,16 +54,17 @@ export default function SelectorSala({ onSearch }: Props) {
}, []);
return (
<form className="form-container" onSubmit={handleSubmit}>
<form className="form-container">
<label>Salas</label>
<select value={selected} onChange={handleChangeEquipo}>
<option value="">-- Selecciona una sala --</option>
{sala &&
sala.map((sala) => (
<option key={sala.id_area_ubicacion} value={sala.id_area_ubicacion}>
{sala.area}
</option>
))}
<option value={0}>-- Selecciona una sala --</option>
{sala.map((s) => (
<option key={s.id_area_ubicacion} value={s.id_area_ubicacion}>
{s.area}
</option>
))}
</select>
</form>
);
}
//IO
+34 -10
View File
@@ -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;
@@ -33,7 +35,8 @@ export default function Inscripcion({
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);
//restrict this month//
const day = new Date();
@@ -66,19 +69,36 @@ export default function Inscripcion({
return;
}
if (!plataformaSeleccionada) {
toast.error("Selecciona una plataforma");
return;
}
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}/operations/registration`,
{
monto: Number(amount),
id_cuenta: numAcount,
id_plataforma,
folio_recibo: folio,
fecha_recibo: date,
realizo_pago: true,
},
{ headers },
);
toast.success("Alumno con pago");
setFolio("");
setAmount("");
setDate("");
toast.success("Recibo guardado");
router.refresh();
} catch (err: any) {
toast.error(String(err));
@@ -161,6 +181,8 @@ export default function Inscripcion({
setFolio(value);
}
}}
placeholder="Numero de ticket..."
inputMode="numeric"
/>
</div>
@@ -178,6 +200,8 @@ export default function Inscripcion({
}
}
}}
placeholder="Monto recibido..."
inputMode="numeric"
/>
</div>
+2 -1
View File
@@ -16,7 +16,8 @@ function Receipt({ numAcount }: ReceiptsProps) {
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);
//restrict this month//
const day = new Date();
+53 -8
View File
@@ -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<HTMLFormElement>) => {
e.preventDefault();
const params = new URLSearchParams(searchParams.toString());
if (value) {
params.set("machine", `${value}`);
router.push(`${pathname}?${params.toString()}`);
}
};
return (
<form className="containerForm">
<label>Numero de equipo</label>
<div className="groupInput">
<input type="text" placeholder="Coloca el numero de equipo"/>
<button className="button buttonSearch">Buscar</button>
</div>
</form>
<>
<form className="containerForm" onSubmit={handleSubmit}>
<label className="label">Ubicacion del equipo</label>
<div className="groupInput">
<input
type="text"
value={value}
onChange={(e) => {
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]*"
/>
<button className="button buttonSearch" type="submit">
Buscar
</button>
</div>
</form>
</>
);
}
//IO
+3 -3
View File
@@ -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",
},
];
+3 -4
View File
@@ -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),
);
@@ -1,9 +1,9 @@
"use client";
import { useState } from "react";
import "./BarNavigation.css";
import Link from "next/link";
import "./BarNavigation.css";
function BarNavigation() {
export default function BarNavigation() {
const [openMenu, setOpenMenu] = useState(false);
const [openSubMenu, setOpenSubMenu] = useState<number | null>(null);
@@ -169,28 +169,18 @@ function BarNavigation() {
>
<li>Inscritos</li>
</Link>
<Link
href="/BitacoraSanciones"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<li>Bitacora y sanciones</li>
</Link>
</ul>
</li>
<li className="subMenu" onClick={toggleMenu}>
<Link
href="/QuitarSancion"
href="/BitacoraSanciones"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<span>Quitar sanción</span>
<span>Bitácora y sanciones</span>
</Link>
</li>
<li className="subMenu" onClick={toggleMenu}>
@@ -209,6 +199,4 @@ function BarNavigation() {
</nav>
);
}
export default BarNavigation;
//IO
@@ -0,0 +1,119 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import "./BarNavigation.css";
export default function BarNavigationServicio() {
const [openMenu, setOpenMenu] = useState(false);
const [openSubMenu, setOpenSubMenu] = useState<number | null>(null);
const toggleMenu = () => setOpenMenu(!openMenu);
const toggleSubMenu = (index: number) => {
if (typeof window !== "undefined" && window.innerWidth <= 1000) {
setOpenSubMenu(openSubMenu === index ? null : index);
}
};
const closeAllMenus = () => {
setOpenMenu(false);
setOpenSubMenu(null);
};
return (
<nav className="barNavigation">
<div className={`menuToggle ${openMenu ? "" : ""}`} onClick={toggleMenu}>
<div></div>
<div></div>
<div></div>
</div>
<ul className={openMenu ? "active" : ""}>
<li className={`subMenu ${openSubMenu === 0 ? "open" : ""}`}>
<span onClick={() => toggleSubMenu(0)}>Inscripciónes</span>
<ul onClick={toggleMenu}>
<Link
href="/Inscripcion"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<li>Inscripción Usuario</li>
</Link>
<Link
href="/AgregarTiempo"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<li>Agregar Tiempo</li>
</Link>
</ul>
</li>
<li className={`subMenu ${openSubMenu === 1 ? "open" : ""}`}>
<span onClick={() => toggleSubMenu(1)}>Servicios</span>
<ul onClick={toggleMenu}>
<Link
href="/Impresiones"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<li>Impresiones y Ploteo</li>
</Link>
<Link
href="/AsignacionMesas"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<li>Asignación de Mesas</li>
</Link>
<Link
href="/AsignacionEquipo"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<li>Asignación de Equipos</li>
</Link>
<Link
href="/Monitor"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<li>Monitor</li>
</Link>
</ul>
</li>
<li className="subMenu" onClick={toggleMenu}>
<Link
href="/Mensajes"
className="links"
onClick={(e) => {
e.stopPropagation();
closeAllMenus();
}}
>
<span>Mensajes</span>
</Link>
</li>
</ul>
</nav>
);
}
//IO
+4 -2
View File
@@ -3,6 +3,7 @@ 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";
export default async function Header() {
const cookieStore = await cookies();
@@ -28,10 +29,11 @@ export default async function Header() {
</Link>
<div className={header.yellowPart}></div>
<div className={header.containerBarNav}>
{role === "ADMINISTRADOR" && <BarNavigation />}
{role === "ADMINIS.TRADOR" && <BarNavigation />}
{role === "SUPER ADMINISTRADOR (quita sanciones)" && <BarNavigation />}
{role === "SERVICIO SOCIAL" && <BarNavigation/>}
{role === "ADMINISTRADOR" && <BarNavigation/>}
{role === "ATENCION A USUARIO" && <BarNavigation/>}
{role === "SERVICIO SOCIAL" && <BarNavigationServicio/>}
</div>
</header>
);
-1
View File
@@ -585,7 +585,6 @@ table td:last-child {
align-items: center;
font-size: 13px;
cursor: pointer;
margin-right: 1rem;
}
.checkbox-grid input[type="checkbox"] {
+19
View File
@@ -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" };
}
}
+7 -2
View File
@@ -6,10 +6,15 @@ export async function PostImpressions(data: any) {
return response.data;
} catch (error: any) {
const msg =
error.response?.data?.error ||
error.response?.data?.message ||
error.message ||
"Error desconocido al cobrar impresión";
return { error: msg };
return {
error: true,
message: msg,
statusCode: error.response?.status,
};
}
}
//IO
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB