Merge branch 'Lino' into Carlos

This commit is contained in:
2026-01-16 14:07:15 -06:00
58 changed files with 1780 additions and 863 deletions
@@ -1,13 +1,10 @@
import styles from "./Page.module.css";
export default async function MesasDisponibles() {
return (
<section className="containerSection">
<div className={styles.tableContainer}>
<table className={styles.machineTable}>
<div>
<table>
<thead>
<tr style={{fontSize:"15px"}}>
<tr style={{ fontSize: "15px" }}>
<th>Mesa</th>
<th>Activo</th>
</tr>
+24 -1
View File
@@ -76,6 +76,28 @@ export default function RegisterAlta() {
/>
</div>
<div className="containerForm">
<label className="label">Email</label>
<input
type="text"
//value={user}
//onChange={(e) => setUser(e.target.value)}
placeholder="Coloca el email"
/>
</div>
<div className="containerForm">
<label htmlFor="gender" className="label">
Género
</label>
<select id="gender" name="gender">
<option value="">Selecciona Género</option>
<option value="masculino">Masculino</option>
<option value="femenino">Femenino</option>
<option value="otro">Otro</option>
</select>
</div>
<div className="containerForm">
<label className="label">Carrera</label>
<select value={major} onChange={(e) => setMajor(e.target.value)}>
@@ -88,7 +110,8 @@ export default function RegisterAlta() {
</select>
</div>
<button className="button buttonSearch">registrar</button>
<button className="button buttonSearch">Guardar</button>
<button className="button buttonSearch">Inscripcion</button>
</form>
);
}
+22 -7
View File
@@ -1,19 +1,34 @@
"use client";
import { SetStateAction, useState } from "react";
import axios from "axios";
import { SetStateAction, useEffect, useState } from "react";
import { envConfig } from "../lib/config";
interface mesas {
idMesa: number;
active: number;
}
export default function AsignacionMesas() {
const [tiempo, setTiempo] = useState("");
const [mesa, setMesa] = useState<mesas[]>([]);
useEffect(() => {
const getMesa = async() => {
const response = await axios.get(`${envConfig.apiUrl}/mesa/activo`);
setMesa(response.data);
};
getMesa()
}, []);
return (
<form className="containerForm">
<label className="label">Mesas disponibles</label>
<div className="groupInput">
<select value={tiempo} onChange={(e) => setTiempo(e.target.value)}>
<option value="">-- Mesas disponibles --</option>
<option value="15">1 </option>
<option value="30">3 </option>
<option value="45">4 </option>
<option value="60">22 </option>
<option value="90">15 </option>
<option value="120">20 </option>
{mesa.map((mesa, key) => (
<option key={key} value={mesa.idMesa}>{mesa.idMesa}</option>
))}
</select>
<button className="button buttonSearch" type="submit">
Actualizar
@@ -1,9 +1,6 @@
"use client";
import SearchDate from "../SearchDate/SearchDate";
import { useState } from "react";
import styles from "./Page.module.css";
interface alumnos {
tiempo_entrada: string;
tiempo_asignado: string;
@@ -14,27 +11,24 @@ function BitacoraAlumno() {
const [alumnos, setAlumnos] = useState<alumnos[]>([]);
return (
<>
<SearchDate />
<div className={styles.tableContainer} style={{marginTop:"1rem"}}>
<table className={styles.machineTable}>
<thead>
<tr>
<th>Tiempo Entrada</th>
<th>Tiempo Asignado</th>
<th>Ubicacion del equipo</th>
<table>
<thead>
<tr>
<th>Tiempo Entrada</th>
<th>Tiempo Asignado</th>
<th>Ubicacion del equipo</th>
</tr>
</thead>
<tbody>
{alumnos.map((alumno, index) => (
<tr key={index}>
<td>{alumno.tiempo_entrada}</td>
<td>{alumno.tiempo_asignado}</td>
<td>{alumno.Ubicacion_equipo}</td>
</tr>
</thead>
<tbody>
{alumnos.map((alumno, index) => (
<tr key={index}>
<td>{alumno.tiempo_entrada}</td>
<td>{alumno.tiempo_asignado}</td>
<td>{alumno.Ubicacion_equipo}</td>
</tr>
))}
</tbody>
</table>
</div>
))}
</tbody>
</table>
</>
);
}
@@ -2,8 +2,6 @@
import SearchDate from "../SearchDate/SearchDate";
import { useState } from "react";
import styles from "./Page.module.css";
interface equipos {
hora_entrada: string;
min_utilizados: number;
@@ -16,7 +14,6 @@ function BitacoraEquipo() {
const [ubicacion_equipo, setUbicacionEquipo] = useState("");
return (
<>
<SearchDate />
<form className="containerForm">
<label className="label">Ubicacion de equipo</label>
@@ -28,13 +25,11 @@ function BitacoraEquipo() {
<option value="">-- Selecciona un equipo --</option>
<option value="255">Equipo 255</option>
</select>
<button className="button buttonSearch" type="submit">
Asignar
</button>
</div>
</form>
<div className={styles.tableContainer} style={{marginTop:"1rem"}}>
<table className={styles.machineTable}>
<SearchDate />
<div>
<table>
<thead>
<tr>
<th>Hora Entrada</th>
@@ -2,8 +2,6 @@
import SearchDate from "../SearchDate/SearchDate";
import { useState } from "react";
import styles from "./Page.module.css";
interface tables {
no_mesa: number;
no_cuenta: number;
@@ -18,8 +16,8 @@ function BitacoraMesas() {
return (
<>
<SearchDate />
<div className={styles.tableContainer} style={{marginTop:"1rem"}}>
<table className={styles.machineTable}>
<div>
<table>
<thead>
<tr>
<th>Mesa</th>
@@ -5,24 +5,22 @@ import TableSancion from "./TableSancion";
interface Student {
id_cuenta: string;
nombre: string;
credito?: number;
}
export default async function Sanciones(props: { student?: Student }) {
const idCuenta = props?.student?.id_cuenta ?? null;
export default function Sanciones({ student }: { student?: Student }) {
const idCuenta = student?.id_cuenta ?? null;
return (
<>
<SearchUser value={idCuenta} />
{props.student && (
{student && (
<>
<Information
NoCuenta={props.student.id_cuenta}
Nombre={props.student.nombre}
/>
<TableSancion />
<Information NoCuenta={student.id_cuenta} Nombre={student.nombre} />
<TableSancion idCuenta={parseInt(student.id_cuenta)} />
</>
)}
</>
);
}
//
@@ -1,73 +1,129 @@
"use client";
import { useEffect, useState } from "react";
import styles from "./Page.module.css";
import axios from "axios";
import toast from "react-hot-toast";
import { envConfig } from "@/app/lib/config";
interface alumno_sancion {
id_alumno_sancion: number;
fecha_inicio: string;
alumno: alumno;
sancion: sancion;
}
interface alumno {
interface Alumno {
id_cuenta: number;
nombre: string;
credito: number;
}
interface sancion {
interface Sancion {
id_sancion: number;
sancion: string;
duracion: number;
}
export default function TableSancion() {
const [sanciones, setSanciones] = useState<any>();
const [button, setButton] = useState<boolean>(false);
interface AlumnoSancion {
id_alumno_sancion: number;
fecha_inicio: string;
sancion: Sancion;
}
// useEffect(() => {
// const getSanciones = async () => {
// const response = await axios.get("");
// setSanciones(response);
// };
// getSanciones();
// }, [button]);
if (!envConfig.apiUrl) {
console.error("API URL is not defined in envConfig");
}
const handlebutton = () => {
setButton(!button);
export default function TableSancion({ idCuenta }: { idCuenta: number }) {
const [sanciones, setSanciones] = useState<Sancion[]>([]);
const [alumno, setAlumno] = useState<Alumno | null>(null);
const [alumnoSanciones, setAlumnoSanciones] = useState<AlumnoSancion[]>([]);
const [selectedSancion, setSelectedSancion] = useState("");
useEffect(() => {
if (!idCuenta) return;
// Obtener sanciones del alumno
axios
.get(
`${envConfig.apiUrl}/asignacionTiempo_test/alumno-sancion/${idCuenta}`
)
.then((res) => {
setAlumno(res.data.student);
setAlumnoSanciones(
res.data.alusancion?.length ? res.data.alusancion : []
);
})
.catch((err) =>
toast.error("Error al obtener las sanciones del alumno:", err)
);
// Obtener catálogo de sanciones
axios
.get(`${envConfig.apiUrl}/asignacionTiempo_test/sancion`)
.then((res) => setSanciones(res.data))
.catch((err) =>
toast.error("Error al obtener el catálogo de sanciones:", err)
);
}, [idCuenta]);
const calcularFechaFin = (fechaInicio: string, duracionSemanas: number) => {
const fecha = new Date(fechaInicio);
fecha.setDate(fecha.getDate() + duracionSemanas * 7);
return fecha.toLocaleDateString();
};
return (
<>
<div className={styles.tableContainer} style={{margin:"1rem 0"}}>
<table className={styles.machineTable}>
<div style={{ margin: "1rem 0" }}>
<table>
<thead>
<tr>
<th>Cuenta</th>
<th>Motivo de la sanción</th>
<th>Duracion (Semanas) </th>
<th>Duración (Semanas)</th>
<th>Fecha Sanción</th>
<th>Podra utilizar el servicio hasta</th>
<th>Podrá utilizar el servicio hasta</th>
</tr>
</thead>
<tbody></tbody>
<tbody>
{alumnoSanciones.length > 0 ? (
alumnoSanciones.map((item) => (
<tr key={item.id_alumno_sancion}>
<td>{alumno?.id_cuenta}</td>
<td>{item.sancion.sancion}</td>
<td>{item.sancion.duracion}</td>
<td>{new Date(item.fecha_inicio).toLocaleDateString()}</td>
<td>
{calcularFechaFin(item.fecha_inicio, item.sancion.duracion)}
</td>
</tr>
))
) : (
<tr>
<td colSpan={5}>No hay sanciones registradas</td>
</tr>
)}
</tbody>
</table>
</div>
<form className="containerForm">
<div className="groupInput">
<select>
<select
value={selectedSancion}
onChange={(e) => setSelectedSancion(e.target.value)}
>
<option value="">-- Selecciona una sanción --</option>
<option value="sancion 1">No cerrar sesion (Una semana)</option>
{sanciones.map((sancion) => (
<option key={sancion.id_sancion} value={sancion.id_sancion}>
{sancion.sancion} ({sancion.duracion} semana/s)
</option>
))}
</select>
</div>
</form>
<button className="button buttonSearch" style={{margin:"1rem 0"}} onClick={handlebutton}>
<button
className="button buttonSearch"
style={{ margin: "1rem 0" }}
onClick={() => alert(`Sanción ${selectedSancion} aplicada`)}
>
Aplicar sanción
</button>
</>
);
}
//IO
+59
View File
@@ -0,0 +1,59 @@
"use client";
import styles from "./style.module.css";
import Cookies from "js-cookie";
import { useState } from "react";
import toast from "react-hot-toast";
export default function DownloadReporteXLSX() {
const [loading, setLoading] = useState(false);
const handleDownload = async () => {
setLoading(true);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/equipos/reporteXLSX`,//Modificar
{ headers }
);
if (!response.ok) {
toast.error("Error al descargar el archivo");
return;
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "Reporte Recibos.xlsx";
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
} catch (error) {
console.error(error);
toast.error("Hubo un problema al descargar el archivo.");
} finally {
setLoading(false);
}
};
return (
<button
className={styles.downloadBtn}
onClick={handleDownload}
disabled={loading}
>
{loading ? (
<div className={styles.loader}></div>
) : (
<img src="/excel.svg" alt="Excel" className={styles.iconExcel} width={30} height={30}/>
)}
</button>
);
}
+46
View File
@@ -0,0 +1,46 @@
.downloadBtn {
position: absolute;
right: 15px;
top:-35px;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
background: #ffffff;
border: 1px solid #ddd;
border-bottom: none;
padding: 5px 5px;
cursor: pointer;
border-radius: 10px 10px 0 0;
font-size: 14px;
font-weight: 600;
transition: background 0.2s ease;
min-width: 50px;
}
.downloadBtn:hover {
background: #95d2b0;
}
.downloadBtn:disabled {
background: #a5a5a5;
cursor: not-allowed;
}
.loader {
border: 3px solid #ffffff55;
border-top: 3px solid white;
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
+83 -40
View File
@@ -8,75 +8,118 @@ interface Data {
id_equipo: number;
id_plataforma: number;
id_area_ubicacion: number;
ubicacion: string;
}
interface Plataforma {
id_plataforma: number;
nombre: string;
}
interface Area {
id_area_ubicacion: number;
area: string;
extra: string;
}
export default function Equipos() {
const [Equipo, setEquipo] = useState("");
const [Data, setData] = useState<Data | any>();
const [id, setId] = useState("");
const [data, setData] = useState<Data | null>(null);
const handleSubmit = async (e: React.MouseEvent<HTMLButtonElement>) => {
const [plataforma, setPlataforma] = useState<Plataforma[]>([]);
const [area, setArea] = useState<Area[]>([]);
const [ubicacion, setUbicacion] = useState("");
const [nombre, setNombre] = useState("");
const [idPlataforma, setIdPlataforma] = useState<number | "">("");
const [idArea, setIdArea] = useState<number | "">("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// const response = await axios.get(
// `${envConfig.apiUrl}/equipo/${Equipo}`
// );
// if (!response) {
// return;
// }
setData("response");
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`)
]);
const equipo = equipoRes.data as Data;
setData(equipo);
setNombre(equipo.nombre_equipo);
setUbicacion(equipo.ubicacion);
setIdPlataforma(equipo.id_plataforma);
setIdArea(equipo.id_area_ubicacion);
setPlataforma(plataformaRes.data);
setArea(areaRes.data);
};
return (
<form className="containerForm">
<form className="containerForm" onSubmit={handleSubmit}>
<div className="groupInput">
<input
placeholder="Numero de Equipo a buscar..."
value={Equipo}
onChange={(e) => {
setEquipo(e.target.value);
}}
placeholder="Número de Equipo a buscar..."
value={id}
onChange={(e) => setId(e.target.value)}
/>
<button type="button" className="button buttonSearch" onClick={handleSubmit}>
<button type="submit" className="button buttonSearch">
Buscar
</button>
</div>
{Data && (
{data && (
<div>
<label className="label">Ubicacion</label>
<label className="label">Ubicación</label>
<input
type="text"
placeholder="Coloca"
value={Data.id_area_ubicacion}
value={ubicacion}
onChange={(e) => setUbicacion(e.target.value)}
/>
<label className="label">Nombre</label>
<input type="text" placeholder="Coloca " value={Data.nombre_equipo} />
<input
type="text"
value={nombre}
onChange={(e) => setNombre(e.target.value)}
/>
<label className="label">Plataforma</label>
<select>
<option value="0">Elige</option>
<option value="1">2</option>
<option value="2">3</option>
<option value="3">4</option>
<option value="4">5</option>
<option value="5">6</option>
<select
value={idPlataforma}
onChange={(e) => setIdPlataforma(Number(e.target.value))}
>
<option value="">Elige</option>
{plataforma.map((plat) => (
<option key={plat.id_plataforma} value={plat.id_plataforma}>
{plat.nombre}
</option>
))}
</select>
<label className="label">Area Ubicacion</label>
<select>
<option value="0">Elige</option>
<option value="1">mmmmm</option>
<option value="2">mmmmm</option>
<option value="3">mmmmm</option>
<option value="4">mmmmm</option>
<option value="5">mmmmm</option>
<label className="label">Área Ubicación</label>
<select
value={idArea}
onChange={(e) => setIdArea(Number(e.target.value))}
>
<option value="">Elige</option>
{area.map((a) => (
<option key={a.id_area_ubicacion} value={a.id_area_ubicacion}>
{a.area}
</option>
))}
</select>
<div className="containerButton" style={{marginTop:"1rem"}}>
<button className="button buttonSearch">Nuevo</button>
<button className="button buttonSearch">Editar</button>
<div className="containerButton" style={{ marginTop: "1rem" }}>
<button type="button" className="button buttonSearch">
Nuevo
</button>
<button type="button" className="button buttonSearch">
Editar
</button>
</div>
</div>
)}
+3 -6
View File
@@ -1,13 +1,10 @@
import styles from "./Page.module.css";
export default async function TableEquipos() {
return (
<section className="containerSection">
<div className={styles.tableContainer}>
<table className={styles.machineTable}>
<div>
<table>
<thead>
<tr style={{fontSize:"15px"}}>
<tr style={{ fontSize: "15px" }}>
<th>Ubicación</th>
<th>Nombre</th>
<th>Plataforma</th>
@@ -0,0 +1,58 @@
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import SearchDate from "../../SearchDate/SearchDate";
interface urlProp {
value: string | null;
}
function SearchUserWithDate(props: urlProp) {
const [numAcount, setnumAcount] = useState("");
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (props.value) {
setnumAcount(props.value);
}
}, [props.value]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const params = new URLSearchParams(searchParams.toString());
if (numAcount) {
params.set("numAcount", `${numAcount}`);
router.push(`${pathname}?${params.toString()}`);
}
};
return (
<>
<form className="containerForm" onSubmit={handleSubmit}>
<label className="label">No.Cuenta</label>
<div className="groupInput">
<input
type="text"
value={numAcount}
onChange={(e) => {
const value = e.target.value;
if (/^\d*$/.test(value) && value.length <= 9) {
setnumAcount(value);
}
}}
placeholder="Coloca un número de cuenta..."
inputMode="numeric"
pattern="[0-9]*"
/>
</div>
</form>
<SearchDate />
</>
);
}
export default SearchUserWithDate;
//IO
+3 -3
View File
@@ -29,7 +29,7 @@ export default function Toggle({ options, defaultView }: ToggleProps) {
router.replace(`${pathname}?${params.toString()}`);
};
return (
<section className="toggleSection">
<div className="toggleGroup">
@@ -44,10 +44,10 @@ export default function Toggle({ options, defaultView }: ToggleProps) {
))}
</div>
<div className="padding">
<div className="padding toggleContent">
{options.find((opt) => opt.key === view)?.content}
</div>
</section>
);
}
//IO
//IO
+1
View File
@@ -1,4 +1,5 @@
.tableContainer {
margin-top: 1rem;
width: 100%;
max-width: 450px;
overflow-x: hidden;
+1 -1
View File
@@ -101,7 +101,7 @@ function Impressions({ costs, numAcount }: ImpressionsProps) {
/>
</div>
<div className="groupInput">
<div className="groupInput" style={{height:"35px", flexWrap:"nowrap"}}>
<label className="label">Total:</label>
<label
style={{ width: "100%", minWidth: "200px", maxWidth: "500px" }}
@@ -1,15 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import SelectAreas from "../SelectAreas";
import { url } from "inspector";
import SelectEquipo from "../SelectEquipo";
import { envConfig } from "@/app/lib/config";
import axios from "axios";
interface DatosEquipo {
titulo: string;
opcion: string[];
tipo: "equipo" | "sala"; // Nuevo prop para diferenciar
}
import { useEffect, useState } from "react";
const programas = [
"3D MAX STUDIO 2014",
@@ -38,12 +30,37 @@ const programas = [
"STATGRAOHICS Centurion XVI",
];
export default function ProgramSelector({ titulo, opcion, tipo }: DatosEquipo) {
const [seleccion, setSeleccion] = useState("");
const [programas, setPrograma] = useState("");
const caracteristicasPorEquipo: Record<string, string[]> = {
"Equipo 1": ["3D MAX STUDIO 2014", "AUTOCAD", "MATLAB"],
"Equipo 2": ["ADOBE CREATIVE SUITE", "COREL DRAW", "R Studio"],
"Equipo 3": ["ARCHICAD 20", "EVIEWS Enterprise", "SPPS Statiscs"],
"Equipo 4": ["MATHEMATICA", "MAPLE", "Maxima"],
};
interface Equipos {
id_equipo: number;
id_plataforma: number;
id_area_ubicacion: number;
nombre_equipo: string;
ubicacion: string;
activo: boolean;
ip: string;
}
export default function ProgramSelector() {
const [equipoSeleccionado, setEquipoSeleccionado] = useState("");
const [checkboxes, setCheckboxes] = useState<Record<string, boolean>>(
programas.reduce((acc, prog) => ({ ...acc, [prog]: false }), {})
);
const [equipo, setEquipo] = useState<Equipos[]>();
useEffect(() => {
const getEquipos = async () => {
const equipos = await axios.get(`${envConfig.apiUrl}/equipo`);
setEquipo(equipos.data);
};
getEquipos();
}, []);
useEffect(() => {
const response = axios.get(
@@ -52,8 +69,12 @@ export default function ProgramSelector({ titulo, opcion, tipo }: DatosEquipo) {
setPrograma(response)
});
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSeleccion(e.target.value);
const nuevosCheckboxes: Record<string, boolean> = {};
programas.forEach((prog) => {
nuevosCheckboxes[prog] =
caracteristicasPorEquipo[equipo]?.includes(prog) || false;
});
setCheckboxes(nuevosCheckboxes);
};
const handleSubmit = (e: React.FormEvent) => {
@@ -68,14 +89,15 @@ export default function ProgramSelector({ titulo, opcion, tipo }: DatosEquipo) {
return (
<form className="form-container" onSubmit={handleSubmit}>
<label>{titulo}</label>
{/* 👇 Mostrar Select diferente según el tipo */}
{tipo === "sala" ? (
<SelectAreas url="https://venus.acatlan.unam.mx/asignacionTiempo_test/area-ubicacion" />
) : (
<SelectEquipo url="https://venus.acatlan.unam.mx/asignacionTiempo_test/equipo" />
)}
<select value={equipoSeleccionado} onChange={handleChangeEquipo}>
<option value="">-- Selecciona un equipo --</option>
{equipo &&
equipo.map((eq, i) => (
<option key={i} value={eq.id_equipo}>
{eq.nombre_equipo}
</option>
))}
</select>
<div className="checkbox-grid">
{programas.map((prog) => (
+53 -28
View File
@@ -1,48 +1,73 @@
"use client";
import { useState } from "react";
import styles from "./Page.module.css";
interface quitarSanciones {
id: number;
interface Alumno {
id_cuenta: number;
nombre: string;
motivo: string;
duracion: number;
fecha_sancion: string;
utilizar_equipo: number;
credito: number;
}
function QuitarSancion() {
const [quitarSanciones, SetQuitarSanciones] = useState<quitarSanciones[]>([]);
interface Sancion {
id_sancion: number;
sancion: string;
duracion: number;
}
interface AlumnoSancion {
id_alumno_sancion: number;
fecha_inicio: string;
sancion: Sancion;
}
interface DataProps {
student: Alumno;
alusancion: AlumnoSancion[];
}
interface Props {
data: DataProps | null;
}
function QuitarSancion({ data }: Props) {
const calcularFechaFin = (fechaInicio: string, duracionSemanas: number) => {
const fecha = new Date(fechaInicio);
fecha.setDate(fecha.getDate() + duracionSemanas * 7);
return fecha.toLocaleDateString();
};
return (
<section className="containerSection">
<div className={styles.tableContainer} style={{margin:"1rem 0"}}>
<table className={styles.machineTable}>
<div>
<table>
<thead>
<tr>
<th>id</th>
<th>ID</th>
<th>Nombre</th>
<th>Motivo Sanción</th>
<th>Duracion (semanas) </th>
<th>Duración (semanas)</th>
<th>Fecha Sanción</th>
<th>Podria utilizar el servicio hasta</th>
<th>Podría utilizar el servicio hasta</th>
</tr>
</thead>
<tbody>
{quitarSanciones.map((quitarSanciones, index) => (
<tr key={index}>
<td>{quitarSanciones.id}</td>
<td>{quitarSanciones.nombre}</td>
<td>{quitarSanciones.motivo}</td>
<td>{quitarSanciones.duracion}</td>
<td>{quitarSanciones.fecha_sancion}</td>
<td>{quitarSanciones.utilizar_equipo}</td>
{data && data.alusancion.length !== 0 ? (
data.alusancion.map((item) => (
<tr key={item.id_alumno_sancion}>
<td>{item.id_alumno_sancion}</td>
<td>{data.student.nombre}</td>
<td>{item.sancion.sancion}</td>
<td>{item.sancion.duracion}</td>
<td>{new Date(item.fecha_inicio).toLocaleDateString()}</td>
<td>
{calcularFechaFin(item.fecha_inicio, item.sancion.duracion)}
</td>
</tr>
))
) : (
<tr>
<td colSpan={6}>No hay sanciones registradas</td>
</tr>
))}
)}
</tbody>
</table>
</div>
<button className="button buttonSearch" style={{marginTop:"1rem"}}>Quitar Sanción</button>
</section>
);
}
+154
View File
@@ -0,0 +1,154 @@
"use client";
import toast from "react-hot-toast";
import { useState } from "react";
import { PostReceipt } from "@/app/lib/postReceipt";
import { useRouter } from "next/navigation";
import "./Receipt.css";
import Selection from "../Selection/Selection";
interface ReceiptsProps {
numAcount: number | null;
}
export default function Inscripcion({ numAcount }: ReceiptsProps) {
const router = useRouter();
const [folio, setFolio] = useState("");
const [amount, setAmount] = useState("");
const [date, setDate] = useState("");
//restrict this month//
const day = new Date();
const year = day.getFullYear();
const month = day.getMonth();
const today = day.getDate();
const minFecha = new Date(year, month, 1).toISOString().split("T")[0];
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
//restrict this month//
const handleSaveReceipt = async () => {
if (!numAcount) {
toast.error("busca de nuevo al estudiante");
return;
}
if (!folio) {
toast.error("Ingresa el folio del ticket");
return;
}
if (!amount) {
toast.error("Coloca el monto a depositar");
return;
}
if (!date) {
toast.error("Coloca la fecha");
return;
}
try {
await PostReceipt({
id_cuenta: numAcount,
folio_recibo: folio,
monto: Number(amount),
fecha_recibo: date,
});
setFolio("");
setAmount("");
setDate("");
toast.success("Recibo guardado");
router.refresh();
} catch (err: any) {
toast.error(String(err));
}
};
return (
<section>
<Selection />
<select
style={{
marginBottom: "1rem",
maxWidth: "100px",
minWidth: "100px",
}}
>
<option value="0">con pago</option>
<option value="1">sin pago</option>
</select>
<form
onSubmit={(e) => {
e.preventDefault();
handleSaveReceipt();
}}
>
<div className="gap">
<div className="groupInput">
<label className="label">Ticket:</label>
<input
type="text"
value={folio}
onChange={(error) => {
const value = error.target.value;
if (/^\d*$/.test(value) && value.length <= 7) {
setFolio(value);
}
}}
placeholder="Numero de tiket..."
inputMode="numeric"
pattern="[0-9]*"
/>
</div>
<div className="groupInput">
<label className="label">Monto:</label>
<input
type="text"
value={amount}
onChange={(e) => {
const value = e.target.value;
if (/^\d*\.?\d*$/.test(value)) {
const numericValue = parseFloat(value);
if (value === "" || numericValue <= 1000) {
setAmount(value);
} else {
toast.error("El monto no puede superar $1000.00");
}
}
}}
placeholder="Monto recibido..."
inputMode="numeric"
pattern="^\d*\.?\d+$"
/>
</div>
<div className="groupInput">
<label className="label">Fecha de Pago:</label>
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
min={minFecha}
max={maxFecha}
/>
</div>
<div className="containerButton">
<button className="button buttonSearch">Inscribir</button>
</div>
</div>
</form>
</section>
);
}
//IO
+1 -1
View File
@@ -129,7 +129,7 @@ function Receipt({ numAcount }: ReceiptsProps) {
</div>
<div className="containerButton">
<button className="button buttonSearch">Guardar</button>
<button className="button buttonSearch" type="submit">Guardar</button>
</div>
</div>
</form>
+84 -23
View File
@@ -1,32 +1,93 @@
"use client";
import { useState } from "react";
import styles from "./Page.module.css";
interface reportes {
Servicio: "Plotter";
Total: "$1440.00";
import { useEffect, useState } from "react";
import axios from "axios";
import DownloadReporteXLSX from "../Dowload/Reporte";
interface Recibo {
id_recibo: number;
folio_recibo: string;
fecha_recibo: string;
fecha_registro: string;
monto: string;
user: {
nombre: string;
};
}
function PorRecibos() {
const [reportes, SetQuitarSanciones] = useState<reportes[]>([]);
interface Props {
desde: string | null;
hasta: string | null;
}
function PorRecibos({ desde, hasta }: Props) {
const [recibos, setRecibos] = useState<Recibo[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!desde || !hasta) return;
const fetchRecibos = async () => {
setLoading(true);
try {
const res = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/recibo/rango`,
{ desde, hasta }
);
setRecibos(res.data);
} catch (error) {
console.error("Error al obtener recibos", error);
} finally {
setLoading(false);
}
};
fetchRecibos();
}, [desde, hasta]);
return (
<div className={styles.tableContainer}>
<table className={styles.machineTable}>
<thead>
<tr>
<th>Servicio</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{reportes.map((reporte, index) => (
<tr key={index}>
<td>{reporte.Servicio}</td>
<td>{reporte.Total}</td>
<div style={{ position: "relative" }}>
{recibos.length > 0 && <DownloadReporteXLSX />}
<div
style={{
overflow: "auto",
height: "270px",
scrollbarColor: "#2563eb white",
}}
>
{loading && <p>Cargando...</p>}
<table>
<thead>
<tr>
<th>Folio</th>
<th>Monto</th>
<th>fecha recibo</th>
<th>fecha registro</th>
<th>Usuario</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{!loading && recibos.length === 0 && (
<tr>
<td colSpan={2}>No hay recibos en este rango</td>
</tr>
)}
{recibos.map((recibo) => (
<tr key={recibo.id_recibo}>
<td>{recibo.folio_recibo}</td>
<td>${Number(recibo.monto).toFixed(2)}</td>
<td>{recibo.fecha_recibo}</td>
<td>{recibo.fecha_registro}</td>
<td>{recibo.user.nombre}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
+76 -33
View File
@@ -1,43 +1,86 @@
"use client";
import { useState } from "react";
import styles from "./Page.module.css";
interface servicios {
folio_recibo: "100255";
monto: "40.00";
fecha_recibo: "10/10/2025";
fecha_registro: "10/10/2025 05:32:20 pm";
usuario: "modulo1";
import { useEffect, useState } from "react";
import axios from "axios";
import DownloadReporteXLSX from "../Dowload/Reporte";
interface ServicioReporte {
id_servicio: number;
servicio: string;
total: string;
}
function PorServicios() {
const [recibos, setRecibos] = useState<servicios[]>([]);
interface Props {
desde: string | null;
hasta: string | null;
}
function PorServicio({ desde, hasta }: Props) {
const [servicios, setServicios] = useState<ServicioReporte[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!desde || !hasta) return;
const fetchServicios = async () => {
setLoading(true);
try {
const res = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/detalle-servicio/rango`,
{ desde, hasta }
);
setServicios(res.data);
} catch (error) {
console.error("Error al obtener reporte por servicio", error);
} finally {
setLoading(false);
}
};
fetchServicios();
}, [desde, hasta]);
return (
<div className={styles.tableContainer}>
<table className={styles.machineTable}>
<thead>
<tr>
<th>Folio Recibo</th>
<th>Monto</th>
<th>Fecha Recibo</th>
<th>Fecha Registro</th>
<th>Usuario</th>
</tr>
</thead>
<tbody>
{recibos.map((recibo, index) => (
<tr key={index}>
<td>{recibo.folio_recibo}</td>
<td>{recibo.monto}</td>
<td>{recibo.fecha_recibo}</td>
<td>{recibo.fecha_registro}</td>
<td>{recibo.usuario}</td>
<div style={{ position: "relative" }}>
{servicios.length > 0 && <DownloadReporteXLSX />}
<div
style={{
overflow: "auto",
height: "270px",
scrollbarColor: "#2563eb white",
}}
>
{loading && <p>Cargando...</p>}
<table>
<thead>
<tr>
<th>Servicio</th>
<th>Monto total</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{!loading && servicios.length === 0 && (
<tr>
<td colSpan={2}>No hay servicios en este rango</td>
</tr>
)}
{servicios.map((servicio) => (
<tr key={servicio.id_servicio}>
<td>{servicio.servicio}</td>
<td>${Number(servicio.total).toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
export default PorServicios;
export default PorServicio;
@@ -1,51 +1,46 @@
"use client";
'use client';
import { useState } from "react";
import { useState } from 'react';
function SearchDateBetween() {
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
interface Props {
onSearch: (desde: string, hasta: string) => void;
}
function SearchDateBetween({ onSearch }: Props) {
const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState('');
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("Fecha inicio:", startDate);
console.log("Fecha fin:", endDate);
// Aquí puedes hacer la lógica para buscar entre fechas
if (!startDate || !endDate) return;
onSearch(startDate, endDate);
};
return (
<>
<form className="containerForm" onSubmit={handleSubmit}>
<label className="label">Desde</label>
<div className="groupInput">
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
placeholder="Fecha inicio"
/>
</div>
<label className="label">Hasta</label>
<div className="groupInput">
<input
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
placeholder="Fecha fin"
/>
</div>
<button
className="button buttonSearch "
type="submit"
style={{ margin: "1rem 0 " }}
>
Buscar
</button>
</form>
</>
<form className="containerForm" onSubmit={handleSubmit}>
<label className="label">Desde</label>
<div className="groupInput">
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
/>
</div>
<label className="label">Hasta</label>
<div className="groupInput">
<input
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
/>
</div>
<button className="button buttonSearch" type="submit" style={{marginBottom:"10px"}}>
Buscar
</button>
</form>
);
}
export default SearchDateBetween;
// By Tyrannuss
+12 -3
View File
@@ -1,6 +1,11 @@
"use client";
import axios from "axios";
import { useEffect, useState } from "react";
import { envConfig } from "../lib/config";
if (!envConfig.apiUrl) {
console.error("API URL is not defined in envConfig");
}
interface proms {
url: string;
@@ -12,9 +17,13 @@ export default function SelectAreas(props: proms) {
useEffect(() => {
axios
.get(props.url)
.then((data) => setAreas(data.data))
.catch((error) => console.error("Error al traer las áreas:", error));
.get(`${envConfig.apiUrl}/area-ubicacion`)
.then((data) => {
setAreas(data.data);
})
.catch((error) => {
console.error("Error al traer las áreas:", error);
});
}, []);
return (
@@ -1,17 +1,24 @@
"use client";
import apiClient from "@/app/lib/apiClient";
import { useState } from "react";
import toast from "react-hot-toast";
export default function ChangePassword() {
const [pass, setPass] = useState("");
const [newPass, setNewPass] = useState("");
const [password, setPass] = useState("");
const [newPassword, setNewPass] = useState("");
const [confirmNewPass, setconfirmNewPass] = useState("");
const handleChangePass = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault;
const data = { pass, newPass, confirmNewPass };
e.preventDefault();
const data = { password, newPassword };
await apiClient.post("/user/create", data);
try{
const response = await apiClient.post("/user/changePassword", data);
toast.success( `${response.data.message}`)
}catch{
toast.error( `No es la contraseña actual`)
}
};
return (
@@ -21,7 +28,8 @@ export default function ChangePassword() {
<label className="label">Contraseña actual</label>
<input
placeholder="Coloca tu contraseña..."
value={pass}
value={password}
type="password"
onChange={(e) => {
setPass(e.target.value);
}}
@@ -33,7 +41,8 @@ export default function ChangePassword() {
<label className="label">Nueva contraseña</label>
<input
placeholder="Coloca tu nueva contraseña..."
value={newPass}
value={newPassword}
type="password"
onChange={(e) => {
setNewPass(e.target.value);
}}
@@ -46,6 +55,7 @@ export default function ChangePassword() {
<input
placeholder="Coloca tu nueva contraseña..."
value={confirmNewPass}
type="password"
onChange={(e) => {
setconfirmNewPass(e.target.value);
}}
@@ -57,7 +67,6 @@ export default function ChangePassword() {
<button
className="button buttonSearch"
style={{ maxWidth: "100%", width: "100%" }}
type="submit"
>
Confirmar
</button>
@@ -65,7 +74,6 @@ export default function ChangePassword() {
<button
className="button buttonCancel"
style={{ maxWidth: "100%", width: "100%" }}
type="submit"
>
Cancelar
</button>
@@ -74,3 +82,4 @@ export default function ChangePassword() {
</section>
);
}
//IO
+3 -1
View File
@@ -33,9 +33,11 @@ function Login() {
const token = data.access_token;
const payload = JSON.parse(atob(token.split(".")[1]));
const usuario = payload.usuario;
const role = payload.role;
document.cookie = `token=${token}; path=/; SameSite=Strict`;
document.cookie = `user=${usuario}; path=/; SameSite=Strict`;
document.cookie = `role=${role}; path=/; SameSite=Strict`;
toast.success("Inicio de sesión exitoso");
router.push("/Impresiones");
@@ -45,7 +47,7 @@ function Login() {
return (
<section className="centerGrid containerSection">
<form className="login" onSubmit={handleLogin}>
<h2 className="title">Inicio de sesión</h2>
<h2 className="title">Bienvenido a AT</h2>
<div className="containerInput">
<label className="label">Usuario</label>
-1
View File
@@ -16,7 +16,6 @@
color: transparent;
padding: 2rem;
font-size: 0px;
top: 15px;
}
.button-logout::after {
+3
View File
@@ -10,6 +10,9 @@ export default function Logout() {
const handleLogout = () => {
Cookies.remove("token", { path: "/" });
Cookies.remove("role", { path: "/" });
Cookies.remove("user", { path: "/" });
router.push("/");
};
@@ -1,11 +1,11 @@
.barNavigation {
text-align: center;
background-color: #003e79;
padding: 0 20px;
background-color: #ffffff;
display: flex;
width: 100%;
top: 0;
min-height: 40px;
height: 45px;
max-height: 40px;
}
.barNavigation ul {
@@ -13,24 +13,23 @@
justify-content: space-around;
width: 100%;
gap: 10px;
transition: width 0.5 ease-in-out;
}
.barNavigation li {
transition: all 0.3s ease;
cursor: pointer;
color: white;
color: #585858;
width: 100%;
transition: background-color 0.3s ease;
transition: background-color 0.3s ease, border 0.3s ease;
font-size: 1.5rem;
}
.barNavigation li:hover {
background-color: #bd8c01;
}
.barNavigation ul.active {
display: flex;
width: 100%;
background-color: #f3f4f6;
color: black !important;
border-radius: 8px;
z-index: 2;
}
.menuToggle {
@@ -40,13 +39,12 @@
gap: 4px;
cursor: pointer;
padding: 10px;
margin-right: 10px;
}
.menuToggle div {
width: 25px;
height: 3px;
background-color: white;
background-color: #000000;
border-radius: 4px;
}
@@ -68,7 +66,10 @@
flex-direction: column;
top: 100%;
width: 100%;
background-color: #003e79;
min-width: 200px;
border: 1px solid #cfcfcf;
border-radius: 8px;
background-color: #ffffff;
transition: color 0.3s ease;
transition: opacity 0.4s ease, max-height 0.4s ease;
}
@@ -83,7 +84,7 @@
}
.subMenu ul:hover {
color: #5b8cc9;
color: #101113;
}
.subMenu:hover ul {
@@ -100,18 +101,26 @@
}
.subMenu span {
font-weight: bold;
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
color: rgb(255, 255, 255);
font-size: 2rem;
color: #545454;
font-size: 1.5rem;
padding: 1rem;
}
.subMenu:hover > span,
.subMenu > a:hover > span {
color: black;
}
.containerLinks {
padding: 0 !important;
border-radius: 0 0 4px 4px;
top: 20px;
}
.links {
@@ -129,7 +138,7 @@ tbody {
font-size: 1.25rem;
}
@media (max-width: 800px) {
@media (max-width: 1000px) {
.menuToggle {
display: flex;
position: relative;
@@ -149,13 +158,36 @@ tbody {
.barNavigation ul {
position: absolute;
background-color: #003e79;
top: 76px;
left: 0;
width: 100%;
background-color: #fff;
flex-direction: column;
padding: 10px 0;
align-items: center;
display: none;
z-index: 1;
height: auto;
z-index: 10;
transform: translateY(-15px);
opacity: 0;
pointer-events: none;
transition: transform 0.35s ease-in-out, opacity 0.35s ease-in-out,
visibility 0.35s ease-in-out;
}
.barNavigation ul.active {
position: absolute;
left: 0;
display: flex;
width: 100%;
transform: translateY(0);
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.containerLinks {
top: 20px;
}
.barNavigation {
@@ -163,17 +195,34 @@ tbody {
}
.barNavigation li:hover {
background-color: #d59f0f;
color: white;
border-radius: 0;
border: none;
}
.subMenu ul {
width: 100%;
border-radius: 4px;
position: relative;
top: 0px;
width: 100%;
background-color: rgb(1, 92, 184);
max-height: 0;
opacity: 0;
overflow: hidden;
transform: translateY(-5px);
transition: max-height 0.35s ease, opacity 0.3s ease, transform 0.3s ease;
}
.subMenu.open ul {
max-height: 500px;
opacity: 1;
transform: translateY(0);
}
.subMenu ul {
opacity: 0;
}
thead,
@@ -9,7 +9,7 @@ function BarNavigation() {
const toggleMenu = () => setOpenMenu(!openMenu);
const toggleSubMenu = (index: number) => {
if (typeof window !== "undefined" && window.innerWidth <= 800) {
if (typeof window !== "undefined" && window.innerWidth <= 1000) {
setOpenSubMenu(openSubMenu === index ? null : index);
}
};
@@ -24,16 +24,13 @@ function BarNavigation() {
<ul className={openMenu ? "active" : ""}>
<li className={`subMenu ${openSubMenu === 0 ? "open" : ""}`}>
<span onClick={() => toggleSubMenu(0)}>Inscripción</span>
<span onClick={() => toggleSubMenu(0)}>Inscripciónes</span>
<ul className="containerLinks" onClick={toggleMenu}>
<Link href="/Alta" className="links">
<li>Alta</li>
</Link>
<Link href="/AgregarTiempo" className="links">
<li>Agregar Tiempo</li>
</Link>
<Link href="/Inscripcion" className="links">
<li>Inscripción</li>
<li>Inscripción Usuario</li>
</Link>
</ul>
</li>
+80 -6
View File
@@ -1,8 +1,16 @@
.logo {
position: relative;
filter: invert(1);
z-index: 3;
margin: 5px;
object-fit: contain;
}
.logoContainer {
position: relative;
width: 100%;
max-width: 250px;
min-width: 200px;
height: auto;
aspect-ratio: 250/70;
}
.cedetecContainer {
@@ -26,16 +34,82 @@
content: "";
top: 0;
left: -20px;
width: 65%;
height: 100%;
min-width: 300px;
background: linear-gradient(to right, #bd8c01, #f9f9f9);
width: 100%;
height: 30px;
background: linear-gradient(to right, #bd8c01 40%, #fff 100%);
transform: skew(-45deg);
z-index: 0;
}
.header {
width: 100vw;
background-color: #fff;
display: grid;
grid-template-columns: auto 1fr;
padding: 0 10px;
position: relative;
z-index: 1;
align-items: end;
}
.header::after {
content: "";
top: 0;
left: -60px;
position: absolute;
background-color: #003e79;
height: 100%;
min-width: 350px;
transform: skew(45deg);
z-index: 2;
}
.header::before {
content: "";
top: 0;
left: -40px;
position: absolute;
background-color: rgb(1, 92, 184);
height: 100%;
width: 10%;
min-width: 350px;
transform: skew(45deg);
z-index: 2;
}
.containerBarNav {
display: flex;
align-items: center;
width: 100%;
height: 100%;
padding-left: 110px;
max-height: 60%;
}
@media (max-width: 800px) {
.yellowPart {
left: 0;
background: #bd8c01;
transform: skew(0deg);
}
.header::after {
left: -100px;
}
.header::before {
left: -80px;
}
}
@media (max-width: 400px) {
.header::after {
min-width: 100%;
transform: skew(25deg);
}
.header::before {
min-width: 100%;
transform: skew(25deg);
}
.containerBarNav {
padding-left: 0;
}
}
+32 -15
View File
@@ -1,31 +1,48 @@
"use client";
import Image from "next/image";
import header from "./Header.module.css";
import Link from "next/link";
import BarNavigation from "../BarNavigation/BarNavigation";
import { useEffect, useState } from "react";
function Header() {
const [role, setRole] = useState("");
useEffect(() => {
const cookies = Object.fromEntries(
document.cookie.split("; ").map((c) => c.split("="))
);
setRole(cookies.role || "");
}, []);
return (
<header>
<header className={header.header}>
<Link
href="https://www.unam.mx/"
target="_blank"
className={header.center}
style={{
display: "flex",
alignItems: "center",
margin: "10px 5px",
}}
>
<Image
className={header.logo}
src="/logo_fes.png"
alt="Logo FES"
width={200}
height={50}
/>
<div className={header.logoContainer}>
<Image
className={header.logo}
src="/logo_fes.png"
alt="Logo FES"
fill
style={{ objectFit: "contain" }}
/>
</div>
</Link>
<div className={header.yellowPart}></div>
<div className={header.cedetecContainer}>
<Image
src="/cedetec.jpg"
alt="Image of CEDETEC"
width={300}
height={71}
/>
<div className={header.containerBarNav}>
{role === "ADMINISTRADOR" && <BarNavigation />}
{role === "SUPER ADMINISTRADOR (quita sanciones)" && <BarNavigation />}
{role === "SERVICIO SOCIAL" && <BarNavigation/>}
{role === "ATENCION A USUARIO" && <BarNavigation/>}
</div>
</header>
);