merge Lino
This commit is contained in:
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -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} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"] {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.buttonInscription{
|
||||
background-color: #007a5b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.buttonSave:not(:disabled):hover {
|
||||
background-color: #001f5c;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -585,7 +585,6 @@ table td:last-child {
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.checkbox-grid input[type="checkbox"] {
|
||||
|
||||
@@ -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" };
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
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 |
Reference in New Issue
Block a user