Compare commits
133 Commits
ad09efaef6
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 17acb358d9 | |||
| f2afe92991 | |||
| edc56138d5 | |||
| 97f2a8851d | |||
| 1bdde40409 | |||
| 286075da19 | |||
| 3dfc092e98 | |||
| c2e392f3d8 | |||
| 35245a9195 | |||
| e271e2414e | |||
| 84e29d4a20 | |||
| 10d92b97cd | |||
| 78e62295e1 | |||
| f5a4f66715 | |||
| c4f68aa1e0 | |||
| f7297a2f64 | |||
| bb90e40906 | |||
| 4ab004c5ea | |||
| 4070d73eae | |||
| 6a5f15e247 | |||
| 45c3ba3076 | |||
| c4c06d9ed6 | |||
| 22753e9d84 | |||
| 1f29e72ad2 | |||
| edd4e6f0c0 | |||
| a7bcddb759 | |||
| 4d727d1766 | |||
| 329a1b0faf | |||
| c60ddf0797 | |||
| 68d61446c3 | |||
| 5b65a4795f | |||
| e294d226de | |||
| 2fc0267360 | |||
| d1bf9f6086 | |||
| 05f6ea24a7 | |||
| 9a81c800fd | |||
| 9752ccce6b | |||
| 07f67a6ac5 | |||
| 2faf13ee2c | |||
| 625084572e | |||
| f9aae13241 | |||
| 4ff4349fa7 | |||
| 52423dbac2 | |||
| 4f2ef1dd6e | |||
| 7155f8c7b1 | |||
| b4eb945b40 | |||
| 59f8fa673d | |||
| c69aa009b7 | |||
| 6c80ecf3db | |||
| 1de700902a | |||
| 32bbfe107f | |||
| c229b819d5 | |||
| 34677f2d82 | |||
| 1a632f15b9 | |||
| 9fea87e62f | |||
| 5fbe8982f6 | |||
| 5af56f4a12 | |||
| 7c92ef6415 | |||
| 55f700f20a | |||
| eb5f6b77c4 | |||
| 46357ee7e0 | |||
| 805f4835a3 | |||
| c2aba808ad | |||
| 552353f4d7 | |||
| 1027bd0673 | |||
| 3c44aa833e | |||
| 0ddb17a23f | |||
| 08386f7ae5 | |||
| 80be76f410 | |||
| f19b0d1e11 | |||
| 649ebe6ab9 | |||
| c0cf9ecda0 | |||
| b7aa2fa66b | |||
| 6b687a8be2 | |||
| 9315bdcb22 | |||
| b0d186784f | |||
| edb2fd2664 | |||
| 01eafd4038 | |||
| 0d37fb3536 | |||
| ee4a4be907 | |||
| ea83311f4d | |||
| 1caafbc597 | |||
| 6c7c477d6a | |||
| f3d19962b9 | |||
| 661121a69d | |||
| 6cf361f281 | |||
| bce1d473a2 | |||
| d53561e339 | |||
| d0b8cc3062 | |||
| a0cd2da717 | |||
| 8f99440207 | |||
| 080ce4137f | |||
| 8c6ca40a23 | |||
| e4082c274c | |||
| d4b18b372e | |||
| 01e463d560 | |||
| 68f05fb517 | |||
| d3e59e63f7 | |||
| 39a10ce46f | |||
| 960769ea33 | |||
| 57714ff36c | |||
| b7bee2d1fd | |||
| ebc718daf2 | |||
| 4743eec697 | |||
| 65fcf5eaff | |||
| b185a96eee | |||
| b4222f7d9a | |||
| 3e85cb862d | |||
| 8b803cbf81 | |||
| 6071f38af2 | |||
| f568bb307c | |||
| 6ab123c87b | |||
| 30de190e22 | |||
| ff08c85a50 | |||
| d24b0bebb4 | |||
| 608800f8fb | |||
| 8688bb028c | |||
| 6abbe700d6 | |||
| 9f64f1305f | |||
| 13be8d9c35 | |||
| 54a2046d06 | |||
| e2c4734306 | |||
| 4de5a7d27d | |||
| 45d60d87ec | |||
| 90bdb482cd | |||
| 62afe827dc | |||
| fc91b0a822 | |||
| 6e15f279b0 | |||
| c989b54fdc | |||
| 05b766afe1 | |||
| 0582269c31 | |||
| 812c8fbbe1 | |||
| 5af0385352 |
@@ -0,0 +1,50 @@
|
|||||||
|
name: Validar AT
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Obtener código
|
||||||
|
uses: https://github.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Configurar Node.js
|
||||||
|
uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Mostrar versiones
|
||||||
|
run: |
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
|
||||||
|
- name: Instalar dependencias
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build obligatorio
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
deploy-dev:
|
||||||
|
needs: validate
|
||||||
|
if: github.event_name == 'push' && github.ref_name == 'develop'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Desplegar AT en pruebas
|
||||||
|
uses: CIDWA/infra-actions/deploy-ssh@main
|
||||||
|
with:
|
||||||
|
ssh-private-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
|
||||||
|
known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
|
||||||
|
host: 10.10.10.11
|
||||||
|
user: deploy
|
||||||
|
deploy-script: /var/deploy/scripts/deploy-node-app.sh at-dev
|
||||||
|
commit-sha: ${{ github.sha }}
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
.checkbox-grid label {
|
.checkbox-grid label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 13px;
|
font-size: 1.4rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ import Areas from "@/app/Components/ActivosMantenimiento/Areas";
|
|||||||
import MesasDisponibles from "@/app/Components/ActivosMantenimiento/MesasDisponibles";
|
import MesasDisponibles from "@/app/Components/ActivosMantenimiento/MesasDisponibles";
|
||||||
import TableEquipos from "@/app/Components/Equipos/tableequipos";
|
import TableEquipos from "@/app/Components/Equipos/tableequipos";
|
||||||
import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "ActivosMantenimiento",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
key?: string;
|
key?: string;
|
||||||
numAcount?: string;
|
|
||||||
machine?: string;
|
|
||||||
}>;
|
}>;
|
||||||
}) {
|
}) {
|
||||||
const params = await props.searchParams;
|
const params = await props.searchParams;
|
||||||
const key = params?.key && params.key;
|
const key = params?.key && params.key;
|
||||||
const numAcount = params?.numAcount ? params.numAcount : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection">
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
|
||||||
import SelectionCo from "@/app/Components/Selection/SelectionCo";
|
import SelectionCo from "@/app/Components/Selection/SelectionCo";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { envConfig } from "@/app/lib/config";
|
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
import "./addTime.css";
|
import "./addTime.css";
|
||||||
|
|
||||||
const PLATAFORMA_MAP: Record<string, number> = {
|
const PLATAFORMA_MAP: Record<string, number> = {
|
||||||
@@ -35,7 +35,7 @@ export default function AddTime({
|
|||||||
const [folio, setFolio] = useState("");
|
const [folio, setFolio] = useState("");
|
||||||
const [amount, setAmount] = useState("");
|
const [amount, setAmount] = useState("");
|
||||||
const [lock, setLock] = useState<boolean>(true);
|
const [lock, setLock] = useState<boolean>(true);
|
||||||
const todayISO = new Date().toISOString().split("T")[0];
|
const todayISO = new Date().toLocaleDateString("en-CA");
|
||||||
const [date, setDate] = useState(todayISO);
|
const [date, setDate] = useState(todayISO);
|
||||||
|
|
||||||
const day = new Date();
|
const day = new Date();
|
||||||
@@ -43,32 +43,62 @@ export default function AddTime({
|
|||||||
const month = day.getMonth();
|
const month = day.getMonth();
|
||||||
const today = day.getDate();
|
const today = day.getDate();
|
||||||
|
|
||||||
const minFecha = new Date(year, month, 1).toISOString().split("T")[0];
|
let minMount = month
|
||||||
|
if (!lock) {
|
||||||
|
minMount = month - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const minFecha = new Date(year, minMount, 1).toISOString().split("T")[0];
|
||||||
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
|
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
|
||||||
|
|
||||||
const handleSaveReceipt = async () => {
|
const handleSaveReceipt = async () => {
|
||||||
if (!numAcount) {
|
if (numAcount == null) {
|
||||||
toast.error("Busca de nuevo al estudiante");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Busca de nuevo al estudiante!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!plataformaSeleccionada) {
|
if (!plataformaSeleccionada) {
|
||||||
toast.error("Selecciona una plataforma");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Selecciona una plataforma!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!folio) {
|
if (!folio) {
|
||||||
toast.error("Ingresa el folio del ticket");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Ingresa el folio del ticket!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!amount) {
|
if (!amount) {
|
||||||
toast.error("Coloca el monto a depositar");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Coloca el monto a depositar!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!date) {
|
if (!date) {
|
||||||
toast.error("Coloca la fecha");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Coloca la fecha!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,19 +120,27 @@ export default function AddTime({
|
|||||||
{ headers },
|
{ headers },
|
||||||
);
|
);
|
||||||
|
|
||||||
toast.success("Tiempo Guardado");
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Tiempo Guardado!",
|
title: "Tiempo Guardado Correctamente!",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
draggable: true
|
draggable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
setFolio("");
|
setFolio("");
|
||||||
setAmount("");
|
setAmount("");
|
||||||
setDate(todayISO);
|
setDate(todayISO);
|
||||||
router.refresh();
|
router.refresh();
|
||||||
} catch (err: any) {
|
} catch (error: any) {
|
||||||
toast.error(err.message || "Error al guardar");
|
const msg =
|
||||||
|
error.response?.data?.message ||
|
||||||
|
error.message ||
|
||||||
|
"Error desconocido al crear recibo";
|
||||||
|
Swal.fire({
|
||||||
|
title: msg,
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -148,7 +186,23 @@ export default function AddTime({
|
|||||||
const numericValue = parseFloat(value);
|
const numericValue = parseFloat(value);
|
||||||
|
|
||||||
if (lock && numericValue > 1000) {
|
if (lock && numericValue > 1000) {
|
||||||
toast.error("El monto no puede superar $1000.00");
|
|
||||||
|
Swal.fire({
|
||||||
|
title:
|
||||||
|
"El monto no puede superar $1000.00 Desbloquea el candado !",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numericValue > 10000) {
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "El monto no puede superar $10000.00 pesos",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +225,7 @@ export default function AddTime({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="containerButton" style={{ position: 'relative' }}>
|
<div className="containerButton" style={{ position: "relative" }}>
|
||||||
<button className="button buttonSearch" type="submit">
|
<button className="button buttonSearch" type="submit">
|
||||||
Guardar
|
Guardar
|
||||||
</button>
|
</button>
|
||||||
@@ -181,15 +235,14 @@ export default function AddTime({
|
|||||||
className={`button buttonCancel ${lock ? "buttonLock" : "buttonOpenLock"
|
className={`button buttonCancel ${lock ? "buttonLock" : "buttonOpenLock"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLock((prev) => !prev)
|
setLock((prev) => !prev);
|
||||||
setAmount('');
|
setAmount("");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//IO
|
//IO
|
||||||
|
|||||||
@@ -23,21 +23,21 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonLock{
|
.buttonLock {
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 100px;
|
||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonOpenLock{
|
.buttonOpenLock {
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 100px;
|
||||||
background-color: #8cd32f;
|
background-color: #8cd32f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonOpenLock:hover{
|
.buttonOpenLock:hover {
|
||||||
background-color: #78b32c;
|
background-color: #78b32c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +63,24 @@
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media(max-height:800px) {
|
||||||
|
.addTime {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.firstPartInformationTime {
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-height:800px) {
|
||||||
|
.buttonLock {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
.containeInformationTime {
|
.containeInformationTime {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -4,12 +4,18 @@ import ShowError from "@/app/Components/Global/ShowError";
|
|||||||
|
|
||||||
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
|
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
|
||||||
|
|
||||||
import "./addTime.css";
|
|
||||||
import AddTime from "./Addtime";
|
import AddTime from "./Addtime";
|
||||||
import Table from "@/app/Components/Global/table";
|
import Table from "@/app/Components/Global/table";
|
||||||
|
|
||||||
|
import "./addTime.css";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
const headers = ["Inscrito", "Tiempo", "Confirmó"];
|
const headers = ["Inscrito", "Tiempo", "Confirmó"];
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "AgregarTiempo",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{ numAcount: string }>;
|
searchParams?: Promise<{ numAcount: string }>;
|
||||||
}) {
|
}) {
|
||||||
@@ -46,7 +52,7 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection">
|
||||||
{errorMessage && <ShowError message={errorMessage} />}
|
{errorMessage && <ShowError key={Date.now()} message={errorMessage} />}
|
||||||
|
|
||||||
<h2 className="title">AGREGAR TIEMPO</h2>
|
<h2 className="title">AGREGAR TIEMPO</h2>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import RegisterAlta from "@/app/Components/Alta/registerAlta";
|
import RegisterAlta from "@/app/Components/Alta/registerAlta";
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Alta",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
|
|
||||||
import { getEquipoByCount } from "@/app/lib/getEquipoByCount";
|
import { getEquipoByCount } from "@/app/lib/getEquipoByCount";
|
||||||
import { getMesaByCount } from "@/app/lib/getMesaByCount";
|
import { getMesaByCount } from "@/app/lib/getMesaByCount";
|
||||||
|
|
||||||
import "./asignacion.css";
|
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
import "./asignacion.css";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
inscripcion: any[];
|
inscripcion: any[];
|
||||||
@@ -23,6 +25,12 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [tiempo, setTiempo] = useState<number>(15);
|
const [tiempo, setTiempo] = useState<number>(15);
|
||||||
const [bitacora, setBitacora] = useState<[] | null>([]);
|
const [bitacora, setBitacora] = useState<[] | null>([]);
|
||||||
|
const [error, setError] = useState<string>("Equipo");
|
||||||
|
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
const fetchByCuenta = async (idCuenta: number) => {
|
const fetchByCuenta = async (idCuenta: number) => {
|
||||||
const [result, resultMesa] = await Promise.all([
|
const [result, resultMesa] = await Promise.all([
|
||||||
@@ -34,17 +42,23 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
|
|||||||
setBitacora(null);
|
setBitacora(null);
|
||||||
} else {
|
} else {
|
||||||
setBitacora([]);
|
setBitacora([]);
|
||||||
if (!result?.error) toast.error("Ya cuentas con un equipo asignado");
|
if (!result?.error) {
|
||||||
if (!resultMesa?.error) toast.error("Ya cuentas con una mesa asignada");
|
setError("Equipo");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resultMesa?.error) {
|
||||||
|
setError("Mesa");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!numAcount) return;
|
if (!numAcount) return;
|
||||||
const idCuenta = numAcount;
|
if (isNaN(numAcount)) return;
|
||||||
if (isNaN(idCuenta)) return;
|
|
||||||
|
|
||||||
fetchByCuenta(idCuenta);
|
fetchByCuenta(numAcount);
|
||||||
}, [numAcount]);
|
}, [numAcount]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -55,7 +69,11 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!todasAsistieron) {
|
if (!todasAsistieron) {
|
||||||
toast.error("El alumno no asistió a todas las pláticas");
|
Swal.fire({
|
||||||
|
title: "El alumno no asistió a todas las pláticas!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
setPuedeContinuar(false);
|
setPuedeContinuar(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,27 +82,25 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
|
|||||||
}, [inscripcion]);
|
}, [inscripcion]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!puedeContinuar || !numAcount) return;
|
if (!puedeContinuar || !numAcount || !(error === "Equipo" || error === "Mesa")) return;
|
||||||
|
|
||||||
const fetchEquipos = async () => {
|
const fetchEquipos = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
setLoadingEquipos(true);
|
setLoadingEquipos(true);
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await axios.get(
|
||||||
`${envConfig.apiUrl}/equipo/student/${numAcount}`,
|
`${envConfig.apiUrl}/equipo/student/${numAcount}`,
|
||||||
{
|
{ headers },
|
||||||
cache: "no-store",
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!res.ok) {
|
setEquipos(res.data);
|
||||||
throw new Error("No se pudieron cargar los equipos");
|
} catch (err) {
|
||||||
}
|
Swal.fire({
|
||||||
|
title: "Error al cargar equipos disponibles!",
|
||||||
const data = await res.json();
|
icon: "error",
|
||||||
setEquipos(data);
|
draggable: true,
|
||||||
} catch (error) {
|
});
|
||||||
toast.error("Error al cargar equipos disponibles");
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingEquipos(false);
|
setLoadingEquipos(false);
|
||||||
}
|
}
|
||||||
@@ -99,7 +115,11 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!equipoSeleccionado) {
|
if (!equipoSeleccionado) {
|
||||||
toast.error("Selecciona un equipo");
|
Swal.fire({
|
||||||
|
title: "Selecciona un equipo!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,32 +131,41 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
|
|||||||
id_alumno_inscrito: inscripcion[0].id_alumno_inscrito,
|
id_alumno_inscrito: inscripcion[0].id_alumno_inscrito,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(`${envConfig.apiUrl}/bitacora`, {
|
await axios.post(`${envConfig.apiUrl}/bitacora`, body, { headers }
|
||||||
method: "POST",
|
);
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
setBitacora([]);
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
Swal.fire({
|
||||||
|
title: "Equipo asignado correctamente!",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "No se pudo asignar el equipo!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("Error al asignar equipo");
|
|
||||||
}
|
|
||||||
|
|
||||||
setBitacora([])
|
|
||||||
toast.success("Equipo asignado correctamente");
|
|
||||||
Swal.fire({
|
|
||||||
title: "Equipo asignado correctamente!",
|
|
||||||
icon: "success",
|
|
||||||
draggable: true,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
toast.error("No se pudo asignar el equipo");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const equiposFiltrados = equipos.filter((eq) => {
|
||||||
|
const texto = search.toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
eq.ubicacion?.toString().toLowerCase().includes(texto) ||
|
||||||
|
eq.plataforma?.nombre?.toLowerCase().includes(texto) ||
|
||||||
|
eq.nombre_equipo?.toLowerCase().includes(texto)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
if (bitacora) {
|
if (bitacora) {
|
||||||
return <></>;
|
return error === "Mesa"
|
||||||
|
? <p style={{ margin: "1rem", color: "red" }}>Ya cuentas con una mesa asignada</p>
|
||||||
|
: error === "Equipo"
|
||||||
|
? <p style={{ margin: "1rem", color: "red" }}>Ya cuentas con un equipo asignado</p>
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -152,6 +181,8 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
|
|||||||
<option value={40}>40 minutos</option>
|
<option value={40}>40 minutos</option>
|
||||||
<option value={60}>60 minutos</option>
|
<option value={60}>60 minutos</option>
|
||||||
<option value={90}>90 minutos</option>
|
<option value={90}>90 minutos</option>
|
||||||
|
<option value={120}>120 minutos</option>
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label>Seleccione un equipo</label>
|
<label>Seleccione un equipo</label>
|
||||||
@@ -162,13 +193,20 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`selectHeader ${equipoSeleccionado && equipoSeleccionado.plataforma?.nombre?.toUpperCase()}`}
|
className={`selectHeader ${equipoSeleccionado && equipoSeleccionado.plataforma?.nombre?.toUpperCase()}`}
|
||||||
onClick={() => !loadingEquipos && setOpen(!open)}
|
onClick={() => !loadingEquipos && setOpen(!open)} >
|
||||||
>
|
{open ? (
|
||||||
{equipoSeleccionado ? (
|
<input
|
||||||
<span
|
autoFocus
|
||||||
>
|
type="text"
|
||||||
{equipoSeleccionado.ubicacion}{" "}
|
placeholder="Buscar equipo..."
|
||||||
{equipoSeleccionado.nombre_equipo}
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="inputSelect"
|
||||||
|
/>
|
||||||
|
) : equipoSeleccionado ? (
|
||||||
|
<span>
|
||||||
|
{equipoSeleccionado.ubicacion} {equipoSeleccionado.nombre_equipo}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="placeholder">
|
<span className="placeholder">
|
||||||
@@ -183,19 +221,20 @@ export default function PlaticaGate({ inscripcion, numAcount }: Props) {
|
|||||||
|
|
||||||
{open && (
|
{open && (
|
||||||
<div className="options">
|
<div className="options">
|
||||||
{equipos.length === 0 && (
|
{equiposFiltrados.length === 0 && (
|
||||||
<div className="option disabled">
|
<div className="option disabled">
|
||||||
No hay equipos disponibles
|
No hay resultados
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{equipos.map((eq) => (
|
{equiposFiltrados.map((eq) => (
|
||||||
<div
|
<div
|
||||||
key={eq.id_equipo}
|
key={eq.id_equipo}
|
||||||
className={`option ${eq.plataforma?.nombre?.toUpperCase()}`}
|
className={`option ${eq.plataforma?.nombre?.toUpperCase()}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEquipoSeleccionado(eq);
|
setEquipoSeleccionado(eq);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
setSearch("");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{eq.ubicacion} {eq.nombre_equipo}
|
{eq.ubicacion} {eq.nombre_equipo}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 14px;
|
font-size: 1.4rem;
|
||||||
margin-bottom: 1rem;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customSelect.disabled {
|
.customSelect.disabled {
|
||||||
@@ -64,7 +64,8 @@
|
|||||||
|
|
||||||
.selectHeader {
|
.selectHeader {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 8px 12px;
|
padding: 0px 6px;
|
||||||
|
height: 35px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -91,4 +92,16 @@
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
max-height: 250px;
|
max-height: 250px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputSelect{
|
||||||
|
border: none;
|
||||||
|
padding: 0 5px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputSelect:focus{
|
||||||
|
background-color: #fff;
|
||||||
|
outline: none;
|
||||||
|
box-shadow:none;
|
||||||
}
|
}
|
||||||
@@ -6,33 +6,18 @@ import Information from "@/app/Components/Global/Information/information";
|
|||||||
import ShowError from "@/app/Components/Global/ShowError";
|
import ShowError from "@/app/Components/Global/ShowError";
|
||||||
import Table from "@/app/Components/Global/table";
|
import Table from "@/app/Components/Global/table";
|
||||||
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
|
||||||
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
|
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
|
||||||
import PlaticaGate from "./PlaticaGate";
|
import PlaticaGate from "./PlaticaGate";
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
|
import { GetRegisterStudentSancion } from "@/app/lib/GetRegisterStudentSancion";
|
||||||
async function getInscripcion(idCuenta: number) {
|
import { Metadata } from "next";
|
||||||
try {
|
|
||||||
const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito/${idCuenta}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`Error ${res.status}: ${res.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await res.json();
|
|
||||||
} catch (error: any) {
|
|
||||||
return { error: error.message || "No se pudo cargar la inscripción" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = ["Inscrito", "Tiempo", "Confirmó"];
|
const headers = ["Inscrito", "Tiempo", "Confirmó"];
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "AsignacionEquipo",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
key?: string;
|
key?: string;
|
||||||
@@ -53,7 +38,7 @@ export default async function Page(props: {
|
|||||||
if (numAcount) {
|
if (numAcount) {
|
||||||
const idCuenta = parseInt(numAcount);
|
const idCuenta = parseInt(numAcount);
|
||||||
|
|
||||||
const result = await GetRegisterStudent(idCuenta);
|
const result = await GetRegisterStudentSancion(idCuenta);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
errorMessage = `${result.error}`;
|
errorMessage = `${result.error}`;
|
||||||
} else {
|
} else {
|
||||||
@@ -63,10 +48,9 @@ export default async function Page(props: {
|
|||||||
rawPlatica?.type === "Buffer"
|
rawPlatica?.type === "Buffer"
|
||||||
? Boolean(rawPlatica.data[0])
|
? Boolean(rawPlatica.data[0])
|
||||||
: Boolean(rawPlatica);
|
: Boolean(rawPlatica);
|
||||||
console.log(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const inscResult = await getInscripcion(idCuenta);
|
const inscResult = await GetRegisterStudent(idCuenta);
|
||||||
if (!inscResult.error && Array.isArray(inscResult)) {
|
if (!inscResult.error && Array.isArray(inscResult)) {
|
||||||
inscripcion = inscResult;
|
inscripcion = inscResult;
|
||||||
} else {
|
} else {
|
||||||
@@ -124,7 +108,7 @@ export default async function Page(props: {
|
|||||||
label: "Cancelar tiempo",
|
label: "Cancelar tiempo",
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<CheckBoxEquipo numAcount={params?.numAcount} machine={params?.machine} />
|
<CheckBoxEquipo numAcount={numAcount} machine={machine} />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import styles from "../Monitor/Page.module.css";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
type Mesa = {
|
||||||
|
id_mesa: number;
|
||||||
|
ocupado: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TableMesas() {
|
||||||
|
const [mesas, setMesas] = useState<Mesa[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [refresh, setRefresh] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
const fetchMesas = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await axios.get(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/mesa/usoWhitout`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
setMesas(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cargando mesas:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchMesas();
|
||||||
|
setRefresh(false)
|
||||||
|
}, [refresh]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className={styles.tableContainer}>
|
||||||
|
<table className={styles.machineTable}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Cargando mesas...</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button className="button buttonCancel" onClick={() => { setRefresh(true) }}>Actualizar informacion</button>
|
||||||
|
<div className={styles.tableContainer}>
|
||||||
|
<table className={styles.machineTable}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID Mesa</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{mesas.map((mesa) => (
|
||||||
|
<tr key={mesa.id_mesa} className={!mesa.ocupado ? '' : styles.ocupado}>
|
||||||
|
<td>{mesa.id_mesa}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.tableTable{
|
||||||
|
margin: 0.1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 250px;
|
||||||
|
}
|
||||||
|
.asignacion{
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
@@ -4,28 +4,23 @@ import Information from "@/app/Components/Global/Information/information";
|
|||||||
import SearchUser from "@/app/Components/Global/SearchUser/searchUser";
|
import SearchUser from "@/app/Components/Global/SearchUser/searchUser";
|
||||||
import ShowError from "@/app/Components/Global/ShowError";
|
import ShowError from "@/app/Components/Global/ShowError";
|
||||||
import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
||||||
|
import TableMesas from "./TableMesas";
|
||||||
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
|
||||||
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
|
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
|
||||||
|
import { GetRegisterStudentSancion } from "@/app/lib/GetRegisterStudentSancion";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
async function getInscripcion(idCuenta: number) {
|
import './asignacion.css'
|
||||||
try {
|
|
||||||
const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito/${idCuenta}`, {
|
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error("Error al cargar inscripción");
|
export const metadata: Metadata = {
|
||||||
return await res.json();
|
title: "AsignacionMesas",
|
||||||
} catch (error: any) {
|
};
|
||||||
return { error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
key?: string;
|
key?: string;
|
||||||
numAcount?: string;
|
numAcount?: string;
|
||||||
table?:number
|
table?: string
|
||||||
}>;
|
}>;
|
||||||
}) {
|
}) {
|
||||||
const params = await props.searchParams;
|
const params = await props.searchParams;
|
||||||
@@ -33,7 +28,6 @@ export default async function Page(props: {
|
|||||||
const numAcount = params?.numAcount ?? null;
|
const numAcount = params?.numAcount ?? null;
|
||||||
const table = params?.table ?? null;
|
const table = params?.table ?? null;
|
||||||
|
|
||||||
|
|
||||||
let student: any = null;
|
let student: any = null;
|
||||||
let platica: any = null;
|
let platica: any = null;
|
||||||
let inscripcion: any[] = [];
|
let inscripcion: any[] = [];
|
||||||
@@ -42,7 +36,7 @@ export default async function Page(props: {
|
|||||||
if (numAcount) {
|
if (numAcount) {
|
||||||
const idCuenta = parseInt(numAcount);
|
const idCuenta = parseInt(numAcount);
|
||||||
|
|
||||||
const result = await GetRegisterStudent(idCuenta);
|
const result = await GetRegisterStudentSancion(idCuenta);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
errorMessage = `${result.error}`;
|
errorMessage = `${result.error}`;
|
||||||
} else {
|
} else {
|
||||||
@@ -52,11 +46,10 @@ export default async function Page(props: {
|
|||||||
rawPlatica?.type === "Buffer"
|
rawPlatica?.type === "Buffer"
|
||||||
? Boolean(rawPlatica.data[0])
|
? Boolean(rawPlatica.data[0])
|
||||||
: Boolean(rawPlatica);
|
: Boolean(rawPlatica);
|
||||||
console.log(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const inscResult = await getInscripcion(idCuenta);
|
|
||||||
|
const inscResult = await GetRegisterStudent(idCuenta);
|
||||||
if (!inscResult.error && Array.isArray(inscResult)) {
|
if (!inscResult.error && Array.isArray(inscResult)) {
|
||||||
inscripcion = inscResult;
|
inscripcion = inscResult;
|
||||||
} else {
|
} else {
|
||||||
@@ -66,45 +59,50 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection">
|
||||||
{errorMessage && <ShowError message={errorMessage} />}
|
{errorMessage && <ShowError key={Date.now()} message={errorMessage} />}
|
||||||
|
|
||||||
<h2 className="title"> ASIGNACIÓN DE MESAS </h2>
|
<h2 className="title"> ASIGNACIÓN DE MESAS </h2>
|
||||||
|
|
||||||
<Toggle
|
<div className="asignacion">
|
||||||
defaultView={key}
|
<Toggle
|
||||||
options={[
|
defaultView={key}
|
||||||
{
|
options={[
|
||||||
key: "Asignar",
|
{
|
||||||
label: "Asignar mesa",
|
key: "Asignar",
|
||||||
content: (
|
label: "Asignar mesa",
|
||||||
<>
|
content: (
|
||||||
<SearchUser value={numAcount} />
|
<>
|
||||||
|
<SearchUser value={numAcount} />
|
||||||
|
|
||||||
{student && (
|
{student && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<Information
|
<Information
|
||||||
NoCuenta={student.id_cuenta}
|
NoCuenta={student.id_cuenta}
|
||||||
nombre={student.nombre}
|
nombre={student.nombre}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AsignacionMesas
|
||||||
|
idCuenta={student.id_cuenta}
|
||||||
|
inscripcion={inscripcion}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
|
)}
|
||||||
<AsignacionMesas
|
</>
|
||||||
idCuenta={student.id_cuenta}
|
),
|
||||||
inscripcion={inscripcion}
|
},
|
||||||
/>
|
{
|
||||||
</>
|
key: "Liberar",
|
||||||
)}
|
label: "Cancelar mesa",
|
||||||
</>
|
content: <CheckBoxMesa numAcount={numAcount} table={table} />,
|
||||||
),
|
},
|
||||||
},
|
]}
|
||||||
{
|
/>
|
||||||
key: "Liberar",
|
<div className="tableTable">
|
||||||
label: "Cancelar mesa",
|
<TableMesas key={Date.now()}/>
|
||||||
content: <CheckBoxMesa numAcount={numAcount} table={table} />,
|
</div>
|
||||||
},
|
</div>
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,31 @@ import SearchUserWithDate from "@/app/Components/Global/SearchUser/SearchUserWit
|
|||||||
import ShowError from "@/app/Components/Global/ShowError";
|
import ShowError from "@/app/Components/Global/ShowError";
|
||||||
import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
||||||
import QuitarSancion from "@/app/Components/QuitarSancion/QuitarSancion";
|
import QuitarSancion from "@/app/Components/QuitarSancion/QuitarSancion";
|
||||||
import { GetStudent } from "@/app/lib/getStudent";
|
import { GetStudentWhitoutSancion } from "@/app/lib/getStudentWhitoutSancion";
|
||||||
import { GetSancionByStudent } from "@/app/lib/getStudent copy";
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "BitacoraSanciones",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
key?: string;
|
key?: string;
|
||||||
numAcount?: string;
|
numAcount?: string;
|
||||||
|
date?: string;
|
||||||
}>;
|
}>;
|
||||||
}) {
|
}) {
|
||||||
const params = await props.searchParams;
|
const params = await props.searchParams;
|
||||||
const key = params?.key && params.key;
|
const key = params?.key && params.key;
|
||||||
const numAcount = params?.numAcount ? params.numAcount : null;
|
const numAcount = params?.numAcount ? params.numAcount : null;
|
||||||
|
const date = params?.date ? params.date : null;
|
||||||
|
|
||||||
|
|
||||||
let student: any = null;
|
let student: any = null;
|
||||||
let errorMessage = "";
|
let errorMessage = "";
|
||||||
|
|
||||||
if (numAcount) {
|
if (numAcount) {
|
||||||
const result = await GetStudent(parseInt(numAcount));
|
const result = await GetStudentWhitoutSancion(parseInt(numAcount));
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
errorMessage = `${result.error}`;
|
errorMessage = `${result.error}`;
|
||||||
@@ -47,7 +54,7 @@ export default async function Page(props: {
|
|||||||
label: "Bitácora equipo",
|
label: "Bitácora equipo",
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<BitacoraEquipo />
|
<BitacoraEquipo date={date}/>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -57,7 +64,12 @@ export default async function Page(props: {
|
|||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<SearchUserWithDate value={numAcount} />
|
<SearchUserWithDate value={numAcount} />
|
||||||
<BitacoraAlumno />
|
<BitacoraAlumno
|
||||||
|
key={`${numAcount}-${date}`}
|
||||||
|
numAcount={numAcount}
|
||||||
|
date={date}
|
||||||
|
/>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -75,6 +87,7 @@ export default async function Page(props: {
|
|||||||
label: "Sanciones",
|
label: "Sanciones",
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
|
<SearchUser value={numAcount} />
|
||||||
<Sanciones student={student} />
|
<Sanciones student={student} />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@@ -86,7 +99,7 @@ export default async function Page(props: {
|
|||||||
<>
|
<>
|
||||||
<div className="containerSection">
|
<div className="containerSection">
|
||||||
<SearchUser value={numAcount} />
|
<SearchUser value={numAcount} />
|
||||||
{/* <QuitarSancion data={student} /> */}
|
<QuitarSancion id_cuenta={Number(numAcount)} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import ChangePassword from "@/app/Components/auth/ChangePassword/changePassword";
|
import ChangePassword from "@/app/Components/auth/ChangePassword/changePassword";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "CambiarPass",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
|
|||||||
@@ -5,9 +5,16 @@ import Information from "../../Components/Global/Information/information";
|
|||||||
import Impressions from "@/app/Components/Impressions/impressions";
|
import Impressions from "@/app/Components/Impressions/impressions";
|
||||||
import ShowError from "@/app/Components/Global/ShowError";
|
import ShowError from "@/app/Components/Global/ShowError";
|
||||||
|
|
||||||
import { GetStudent } from "@/app/lib/getStudent";
|
import { cookies } from "next/headers";
|
||||||
|
import { getCost } from "@/app/lib/getCost";
|
||||||
|
import { GetStudentWhitoutSancion } from "@/app/lib/getStudentWhitoutSancion";
|
||||||
|
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Impresiones",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
@@ -19,12 +26,16 @@ export default async function Page(props: {
|
|||||||
const key = params?.key && params.key;
|
const key = params?.key && params.key;
|
||||||
const numAcount = params?.numAcount ? params.numAcount : null;
|
const numAcount = params?.numAcount ? params.numAcount : null;
|
||||||
|
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
const role = cookieStore.get("role")?.value || "";
|
||||||
|
const roleNumber = parseInt(role);
|
||||||
|
|
||||||
let student: any = null;
|
let student: any = null;
|
||||||
let errorMessage = "";
|
let errorMessage = "";
|
||||||
|
let costos: any[] = [];
|
||||||
|
|
||||||
if (numAcount) {
|
if (numAcount) {
|
||||||
const result = await GetStudent(parseInt(numAcount));
|
const result = await GetStudentWhitoutSancion(parseInt(numAcount));
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
errorMessage = `${result.error}`;
|
errorMessage = `${result.error}`;
|
||||||
} else {
|
} else {
|
||||||
@@ -32,6 +43,110 @@ export default async function Page(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let id_servicio: number;
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case "B/N":
|
||||||
|
id_servicio = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "color":
|
||||||
|
id_servicio = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Plotter":
|
||||||
|
id_servicio = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Escaner":
|
||||||
|
id_servicio = 6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const response = await getCost();
|
||||||
|
costos = response;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error obteniendo costos:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCostsByService = (id_servicio: number) => {
|
||||||
|
return costos
|
||||||
|
.filter((c) => c.id_servicio === id_servicio)
|
||||||
|
.map((c) => ({
|
||||||
|
value: Number(c.costo),
|
||||||
|
id_costo: c.id_costo,
|
||||||
|
fecha_registro: c.fecha_registro,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let options: any[] = [];
|
||||||
|
|
||||||
|
if (student) {
|
||||||
|
options = [
|
||||||
|
{
|
||||||
|
key: "Recibo",
|
||||||
|
label: "Recibo",
|
||||||
|
content: <Receipt numAcount={student.id_cuenta} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "B/N",
|
||||||
|
label: "Impresiones B/N",
|
||||||
|
content: (
|
||||||
|
<Impressions
|
||||||
|
costs={getCostsByService(1)}
|
||||||
|
numAcount={student.id_cuenta}
|
||||||
|
id_servicio={1}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "color",
|
||||||
|
label: "Impresiones color",
|
||||||
|
content: (
|
||||||
|
<Impressions
|
||||||
|
key={2}
|
||||||
|
costs={getCostsByService(2)}
|
||||||
|
numAcount={student.id_cuenta}
|
||||||
|
id_servicio={2}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Plotter",
|
||||||
|
label: "Plotter",
|
||||||
|
content: (
|
||||||
|
<Impressions
|
||||||
|
key={3}
|
||||||
|
costs={getCostsByService(3)}
|
||||||
|
numAcount={student.id_cuenta}
|
||||||
|
id_servicio={3}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Escaner",
|
||||||
|
label: "Escaner",
|
||||||
|
content: (
|
||||||
|
<Impressions
|
||||||
|
costs={getCostsByService(6)}
|
||||||
|
numAcount={student.id_cuenta}
|
||||||
|
id_servicio={6}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionsToUse =
|
||||||
|
roleNumber === 5
|
||||||
|
? options.filter(
|
||||||
|
(option) => option.key === "Recibo" || option.key === "B/N"
|
||||||
|
)
|
||||||
|
: options;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection">
|
||||||
{errorMessage && <ShowError key={Date.now()} message={errorMessage} />}
|
{errorMessage && <ShowError key={Date.now()} message={errorMessage} />}
|
||||||
@@ -47,92 +162,9 @@ export default async function Page(props: {
|
|||||||
Carrera={student.carrera.carrera}
|
Carrera={student.carrera.carrera}
|
||||||
Credito={student.credito}
|
Credito={student.credito}
|
||||||
/>
|
/>
|
||||||
|
<div style={{ height: "10px" }}></div>
|
||||||
|
|
||||||
<Toggle
|
<Toggle defaultView={key} options={optionsToUse} />
|
||||||
defaultView={key}
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
key: "Recibo",
|
|
||||||
label: "Recibo",
|
|
||||||
content: <Receipt numAcount={student.id_cuenta} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "B/N",
|
|
||||||
label: "Impresiones B/N",
|
|
||||||
content: (
|
|
||||||
<Impressions
|
|
||||||
key={1}
|
|
||||||
costs={[{ value: 1 }, { value: 2 }]}
|
|
||||||
numAcount={student.id_cuenta}
|
|
||||||
id_servicio={1}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "color",
|
|
||||||
label: "Impresiones color",
|
|
||||||
content: (
|
|
||||||
<Impressions
|
|
||||||
key={2}
|
|
||||||
costs={[
|
|
||||||
{ value: 4 },
|
|
||||||
{ value: 5 },
|
|
||||||
{ value: 6 },
|
|
||||||
{ value: 7 },
|
|
||||||
{ value: 8 },
|
|
||||||
{ value: 10 },
|
|
||||||
{ value: 12 },
|
|
||||||
{ value: 14 },
|
|
||||||
]}
|
|
||||||
numAcount={student.id_cuenta}
|
|
||||||
id_servicio={2}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "Plotter",
|
|
||||||
label: "Plotter",
|
|
||||||
content: (
|
|
||||||
<Impressions
|
|
||||||
key={3}
|
|
||||||
costs={[
|
|
||||||
{ value: 15 },
|
|
||||||
{ value: 18 },
|
|
||||||
{ value: 20 },
|
|
||||||
{ value: 25 },
|
|
||||||
{ value: 30 },
|
|
||||||
{ value: 40 },
|
|
||||||
{ value: 45 },
|
|
||||||
{ value: 50 },
|
|
||||||
{ value: 60 },
|
|
||||||
{ value: 70 },
|
|
||||||
{ value: 80 },
|
|
||||||
{ value: 90 },
|
|
||||||
{ value: 100 },
|
|
||||||
{ value: 110 },
|
|
||||||
{ value: 120 },
|
|
||||||
{ value: 150 },
|
|
||||||
{ value: 200 },
|
|
||||||
]}
|
|
||||||
numAcount={student.id_cuenta}
|
|
||||||
id_servicio={3}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "Escaner",
|
|
||||||
label: "Escaner",
|
|
||||||
content: (
|
|
||||||
<Impressions
|
|
||||||
key={4}
|
|
||||||
costs={[{ value: 1 }, { value: 2 }, { value: 5 }]}
|
|
||||||
numAcount={student.id_cuenta}
|
|
||||||
id_servicio={6}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ export default function InformacionEquipo({
|
|||||||
defaultKey?: string;
|
defaultKey?: string;
|
||||||
}) {
|
}) {
|
||||||
const [id, setId] = useState<number | null>(null);
|
const [id, setId] = useState<number | null>(null);
|
||||||
const [ubicacion, setUbicacion] = useState<string | null>(null);
|
const [ubicacion, setUbicacion] = useState<number | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection">
|
||||||
<h2 className="title"> INFORMACION DE EQUIPOS </h2>
|
<h2 className="title"> </h2>
|
||||||
|
|
||||||
<Toggle
|
<Toggle
|
||||||
defaultView={defaultKey}
|
defaultView={defaultKey}
|
||||||
|
|||||||
@@ -15,17 +15,18 @@
|
|||||||
|
|
||||||
.form-container {
|
.form-container {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-container label {
|
.form-container label {
|
||||||
font-size: 14px;
|
font-size: 1.4rem;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-container select {
|
.form-container select {
|
||||||
font-size: 13px;
|
font-size: 1.4rem;
|
||||||
padding: 4px 8px;
|
padding: 8px 12px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -47,7 +48,8 @@
|
|||||||
|
|
||||||
.checkbox-grid {
|
.checkbox-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 240px); /* ancho fijo por columna */
|
grid-template-columns: repeat(3, 240px);
|
||||||
|
grid-template-rows: repeat(13, auto);
|
||||||
gap: 10px 20px;
|
gap: 10px 20px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
@@ -56,7 +58,7 @@
|
|||||||
.checkbox-grid label {
|
.checkbox-grid label {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 13px;
|
font-size: 1.4rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
import { Metadata } from "next";
|
||||||
import InformacionEquipo from "./InformacionEquipo";
|
import InformacionEquipo from "./InformacionEquipo";
|
||||||
|
|
||||||
import "./informacionequipo.css";
|
import "./informacionequipo.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "InformacionEquipo",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
key?: string;
|
key?: string;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 700px;
|
max-width: 600px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
@@ -23,18 +23,35 @@
|
|||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.restarPass{
|
.restarPass {
|
||||||
margin-top: 2rem;
|
margin-top: 3rem;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media(max-height:800px) {
|
||||||
|
.firstPartInformation {
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inscripcion {
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.restarPass {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
.containeInformation {
|
.containeInformation {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.restarPass{
|
|
||||||
|
.restarPass {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,38 +3,20 @@ import Information from "@/app/Components/Global/Information/information";
|
|||||||
import ShowError from "@/app/Components/Global/ShowError";
|
import ShowError from "@/app/Components/Global/ShowError";
|
||||||
import Table from "@/app/Components/Global/table";
|
import Table from "@/app/Components/Global/table";
|
||||||
import Inscripcion from "@/app/Components/Receipt/Inscripcion";
|
import Inscripcion from "@/app/Components/Receipt/Inscripcion";
|
||||||
import { GetStudent } from "@/app/lib/getStudent";
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
import Link from "next/link";
|
||||||
|
import { GetRegisterStudent } from "@/app/lib/getRegisterStudent";
|
||||||
|
import { GetStudentWhitoutSancion } from "@/app/lib/getStudentWhitoutSancion";
|
||||||
|
|
||||||
import "./inscripcion.css";
|
import "./inscripcion.css";
|
||||||
import Link from "next/link";
|
import { Metadata } from "next";
|
||||||
|
|
||||||
if (!envConfig.apiUrl) {
|
|
||||||
console.error("API URL is not defined in envConfig");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getInscripcion(idCuenta: number) {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito/${idCuenta}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`Error ${res.status}: ${res.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await res.json();
|
|
||||||
} catch (error: any) {
|
|
||||||
return { error: error.message || "No se pudo cargar la inscripción" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = ["Inscrito", "Tiempo", "Confirmó"];
|
const headers = ["Inscrito", "Tiempo", "Confirmó"];
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Inscripcion",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
numAcount: string;
|
numAcount: string;
|
||||||
@@ -46,18 +28,24 @@ export default async function Page(props: {
|
|||||||
let student: any = null;
|
let student: any = null;
|
||||||
let inscripcion: any = null;
|
let inscripcion: any = null;
|
||||||
let errorMessage = "";
|
let errorMessage = "";
|
||||||
|
let isSancionado = false;
|
||||||
|
|
||||||
if (numAcount) {
|
if (numAcount) {
|
||||||
const idCuenta = parseInt(numAcount);
|
const idCuenta = parseInt(numAcount);
|
||||||
|
|
||||||
const studentResult = await GetStudent(idCuenta);
|
const studentResult = await GetStudentWhitoutSancion(idCuenta);
|
||||||
if (studentResult.error) {
|
if (studentResult.error) {
|
||||||
errorMessage = `${studentResult.error}`;
|
errorMessage = `${studentResult.error}`;
|
||||||
|
|
||||||
|
if (studentResult.status === 403) {
|
||||||
|
isSancionado = true;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
student = studentResult;
|
student = studentResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inscResult = await getInscripcion(idCuenta);
|
const inscResult = await GetRegisterStudent(idCuenta);
|
||||||
if (!inscResult.error && Array.isArray(inscResult)) {
|
if (!inscResult.error && Array.isArray(inscResult)) {
|
||||||
inscripcion = inscResult;
|
inscripcion = inscResult;
|
||||||
} else {
|
} else {
|
||||||
@@ -67,12 +55,12 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
const tableData = Array.isArray(inscripcion)
|
const tableData = Array.isArray(inscripcion)
|
||||||
? inscripcion.map((ins) => ({
|
? inscripcion.map((ins) => ({
|
||||||
Inscrito: ins.plataforma?.nombre || "—",
|
Inscrito: ins.plataforma?.nombre || "—",
|
||||||
Tiempo: ins.tiempo_disponible
|
Tiempo: ins.tiempo_disponible
|
||||||
? `${ins.tiempo_disponible} minutos`
|
? `${ins.tiempo_disponible} minutos`
|
||||||
: "—",
|
: "—",
|
||||||
Confirmó: ins.platica.data?.[0] === 1 ? "sí" : "no",
|
Confirmó: ins.platica.data?.[0] === 1 ? "sí" : "no",
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const plataformasInscritas = Array.isArray(inscripcion)
|
const plataformasInscritas = Array.isArray(inscripcion)
|
||||||
@@ -99,7 +87,7 @@ export default async function Page(props: {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!student && numAcount && (
|
{!student && numAcount && !isSancionado && (
|
||||||
<Link href={`/Alta?numAcount=${numAcount}`}>
|
<Link href={`/Alta?numAcount=${numAcount}`}>
|
||||||
<button
|
<button
|
||||||
className="button buttonSearch"
|
className="button buttonSearch"
|
||||||
|
|||||||
@@ -1,79 +1,336 @@
|
|||||||
"use client"
|
"use client";
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
import axios from "axios";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import "./inscripciones.css";
|
||||||
|
import * as XLSX from "xlsx";
|
||||||
|
import { saveAs } from "file-saver";
|
||||||
|
|
||||||
interface Periodo{
|
interface Periodo {
|
||||||
semestre:string,
|
id_periodo: number;
|
||||||
|
semestre: string;
|
||||||
|
fecha_inicio_servicio: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiResponse {
|
||||||
|
semestre: string;
|
||||||
|
carrera: string;
|
||||||
|
profesor: string;
|
||||||
|
femenino: string;
|
||||||
|
masculino: string;
|
||||||
|
total: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tipo = "periodo" | "mes" | "anio";
|
||||||
|
|
||||||
|
type TipoConteo = "WINDOWS" | "MACINTOSH" | "LINUX" | "PROFESORES";
|
||||||
|
|
||||||
|
interface TablaRow {
|
||||||
|
carrera: string;
|
||||||
|
semestre: string;
|
||||||
|
WINDOWS: number;
|
||||||
|
MACINTOSH: number;
|
||||||
|
LINUX: number;
|
||||||
|
PROFESORES: number;
|
||||||
|
TOTAL: number;
|
||||||
|
femenino: number;
|
||||||
|
masculino: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Inscripciones() {
|
export default function Inscripciones() {
|
||||||
const [periodo,setPeriodo]= useState<Periodo[]>([])
|
const [periodo, setPeriodo] = useState<Periodo[]>([]);
|
||||||
|
const [tipo, setTipo] = useState<Tipo>("periodo");
|
||||||
|
|
||||||
useEffect(()=>{
|
const [periodoInicio, setPeriodoInicio] = useState<any>(null);
|
||||||
const getPeriodo = async()=>{
|
const [periodoFin, setPeriodoFin] = useState<any>(null);
|
||||||
const response = await axios.get(`${envConfig.apiUrl}/periodo`)
|
|
||||||
setPeriodo(response.data)
|
const [anio, setAnio] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const [tablaData, setTablaData] = useState<TablaRow[]>([]);
|
||||||
|
const [periodosUnicos, setPeriodosUnicos] = useState<string[]>([]);
|
||||||
|
const [tablaResumen, setTablaResumen] = useState<any[]>([]);
|
||||||
|
const [rawData, setRawData] = useState<ApiResponse[]>([]);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
// Obtener periodos
|
||||||
|
useEffect(() => {
|
||||||
|
const getPeriodo = async () => {
|
||||||
|
const response = await axios.get(`${envConfig.apiUrl}/periodo`, { headers });
|
||||||
|
setPeriodo(response.data);
|
||||||
|
};
|
||||||
|
getPeriodo();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Obtener años desde fechas
|
||||||
|
const aniosDisponibles = Array.from(
|
||||||
|
new Set(
|
||||||
|
periodo.map((p) =>
|
||||||
|
new Date(p.fecha_inicio_servicio).getFullYear()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).sort();
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// 🔍 BUSCAR
|
||||||
|
// ============================
|
||||||
|
const handleBuscar = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
|
||||||
|
if (tipo === "periodo") {
|
||||||
|
if (!periodoInicio || !periodoFin) {
|
||||||
|
alert("Selecciona ambos periodos");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
url = `${envConfig.apiUrl}/alumno-inscrito/inscritos/periodo/${periodoInicio}/${periodoFin}`;
|
||||||
}
|
}
|
||||||
getPeriodo()
|
|
||||||
},[])
|
if (tipo === "anio") {
|
||||||
|
if (!periodoInicio || !periodoFin) {
|
||||||
|
alert("Selecciona ambos años");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
url = `${envConfig.apiUrl}/alumno-inscrito/inscritos/anio/${periodoInicio}/${periodoFin}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("URL:", url);
|
||||||
|
|
||||||
|
const response = await axios.get(url, { headers });
|
||||||
|
const data: ApiResponse[] = response.data;
|
||||||
|
|
||||||
|
setRawData(data);
|
||||||
|
|
||||||
|
const agrupado: Record<string, TablaRow> = {};
|
||||||
|
|
||||||
|
data.forEach((item) => {
|
||||||
|
const key = `${item.carrera}-${item.semestre}`;
|
||||||
|
|
||||||
|
if (!agrupado[key]) {
|
||||||
|
agrupado[key] = {
|
||||||
|
carrera: item.carrera,
|
||||||
|
semestre: item.semestre,
|
||||||
|
WINDOWS: 0,
|
||||||
|
MACINTOSH: 0,
|
||||||
|
LINUX: 0,
|
||||||
|
PROFESORES: 0,
|
||||||
|
TOTAL: 0,
|
||||||
|
femenino: 0,
|
||||||
|
masculino: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const fila = agrupado[key];
|
||||||
|
const tipoProfesor = item.profesor as TipoConteo;
|
||||||
|
|
||||||
|
const total = Number(item.total);
|
||||||
|
const fem = Number(item.femenino);
|
||||||
|
const masc = Number(item.masculino);
|
||||||
|
|
||||||
|
fila[tipoProfesor] += total;
|
||||||
|
fila.TOTAL += total;
|
||||||
|
fila.femenino += fem;
|
||||||
|
fila.masculino += masc;
|
||||||
|
});
|
||||||
|
|
||||||
|
setTablaData(Object.values(agrupado));
|
||||||
|
|
||||||
|
const periodosUnicosList = Array.from(new Set(data.map((t) => t.semestre)));
|
||||||
|
setPeriodosUnicos(periodosUnicosList);
|
||||||
|
|
||||||
|
const carrerasUnicas = Array.from(new Set(data.map((t) => t.carrera)));
|
||||||
|
|
||||||
|
const resumen = carrerasUnicas.map((carrera) => {
|
||||||
|
const fila: any = { Carrera: carrera };
|
||||||
|
let totalFila = 0;
|
||||||
|
periodosUnicosList.forEach((periodo) => {
|
||||||
|
const items = data.filter((t) => t.carrera === carrera && t.semestre === periodo);
|
||||||
|
const totalPeriodo = items.reduce((acc, item) => acc + Number(item.total), 0);
|
||||||
|
fila[periodo] = totalPeriodo;
|
||||||
|
totalFila += totalPeriodo;
|
||||||
|
});
|
||||||
|
fila["Total"] = totalFila;
|
||||||
|
return fila;
|
||||||
|
});
|
||||||
|
|
||||||
|
setTablaResumen(resumen);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al obtener inscritos", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportarExcel = () => {
|
||||||
|
if (!rawData.length || !tablaResumen.length) return;
|
||||||
|
|
||||||
|
// HOJA 1: Inscritos (resumen)
|
||||||
|
const worksheet1 = XLSX.utils.json_to_sheet(tablaResumen);
|
||||||
|
|
||||||
|
const carrerasUnicas = Array.from(new Set(rawData.map((t) => t.carrera)));
|
||||||
|
|
||||||
|
// HOJA 2: genero
|
||||||
|
const dataGenero = carrerasUnicas.map((carrera) => {
|
||||||
|
const fila: any = { Carrera: carrera };
|
||||||
|
periodosUnicos.forEach((periodo) => {
|
||||||
|
const items = rawData.filter((t) => t.carrera === carrera && t.semestre === periodo);
|
||||||
|
const masc = items.reduce((acc, item) => acc + Number(item.masculino), 0);
|
||||||
|
const fem = items.reduce((acc, item) => acc + Number(item.femenino), 0);
|
||||||
|
fila[`Masculino - ${periodo}`] = masc;
|
||||||
|
fila[`Femenino - ${periodo}`] = fem;
|
||||||
|
});
|
||||||
|
return fila;
|
||||||
|
});
|
||||||
|
const worksheet2 = XLSX.utils.json_to_sheet(dataGenero);
|
||||||
|
|
||||||
|
// HOJA 3: areas
|
||||||
|
const dataAreas = carrerasUnicas.map((carrera) => {
|
||||||
|
const fila: any = { Carrera: carrera };
|
||||||
|
periodosUnicos.forEach((periodo) => {
|
||||||
|
const items = rawData.filter((t) => t.carrera === carrera && t.semestre === periodo);
|
||||||
|
let windows = 0, mac = 0, linux = 0, profes = 0;
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (item.profesor === "WINDOWS") windows += Number(item.total);
|
||||||
|
if (item.profesor === "MACINTOSH") mac += Number(item.total);
|
||||||
|
if (item.profesor === "LINUX") linux += Number(item.total);
|
||||||
|
if (item.profesor === "PROFESORES") profes += Number(item.total);
|
||||||
|
});
|
||||||
|
|
||||||
|
fila[`WINDOWS - ${periodo}`] = windows;
|
||||||
|
fila[`MACINTOSH - ${periodo}`] = mac;
|
||||||
|
fila[`LINUX - ${periodo}`] = linux;
|
||||||
|
fila[`PROFESORES - ${periodo}`] = profes;
|
||||||
|
});
|
||||||
|
return fila;
|
||||||
|
});
|
||||||
|
const worksheet3 = XLSX.utils.json_to_sheet(dataAreas);
|
||||||
|
|
||||||
|
const workbook = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet1, "Inscritos");
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet2, "genero");
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet3, "areas");
|
||||||
|
|
||||||
|
const excelBuffer = XLSX.write(workbook, {
|
||||||
|
bookType: "xlsx",
|
||||||
|
type: "array",
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([excelBuffer], {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
|
||||||
|
saveAs(blob, "reporte_inscritos.xlsx");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection">
|
||||||
<h2 className="title"> INSCRITOS </h2>
|
<h2 className="title">INSCRITOS</h2>
|
||||||
|
|
||||||
|
<form className="containerForm" onSubmit={handleBuscar}>
|
||||||
|
<label>Filtro</label>
|
||||||
|
|
||||||
|
{/* Selector tipo */}
|
||||||
|
<select value={tipo} onChange={(e) => setTipo(e.target.value as Tipo)}>
|
||||||
|
<option value="periodo">Periodo</option>
|
||||||
|
<option value="anio">Año</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<form className="containerForm">
|
|
||||||
<label>Seleccione el periodo</label>
|
|
||||||
<div className="groupInput">
|
<div className="groupInput">
|
||||||
<select>
|
{/* PERIODO */}
|
||||||
{periodo.map((p,index)=>(
|
{tipo === "periodo" && (
|
||||||
<option key={index}>
|
<>
|
||||||
|
<select onChange={(e) => setPeriodoInicio(Number(e.target.value))}>
|
||||||
|
<option value="">Inicio</option>
|
||||||
|
{periodo.map((p) => (
|
||||||
|
<option key={p.id_periodo} value={p.id_periodo}>
|
||||||
{p.semestre}
|
{p.semestre}
|
||||||
</option>))}
|
</option>
|
||||||
</select>
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select onChange={(e) => setPeriodoFin(Number(e.target.value))}>
|
||||||
|
<option value="">Fin</option>
|
||||||
|
{periodo.map((p) => (
|
||||||
|
<option key={p.id_periodo} value={p.id_periodo}>
|
||||||
|
{p.semestre}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* AÑO */}
|
||||||
|
{tipo === "anio" && (
|
||||||
|
<>
|
||||||
|
<select onChange={(e) => setPeriodoInicio(Number(e.target.value))}>
|
||||||
|
<option value="">Año inicio</option>
|
||||||
|
{aniosDisponibles.map((a) => (
|
||||||
|
<option key={a} value={a}>
|
||||||
|
{a}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select onChange={(e) => setPeriodoFin(Number(e.target.value))}>
|
||||||
|
<option value="">Año fin</option>
|
||||||
|
{aniosDisponibles.map((a) => (
|
||||||
|
<option key={a} value={a}>
|
||||||
|
{a}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<button className="button buttonSearch" type="submit">
|
<button className="button buttonSearch" type="submit">
|
||||||
Buscar
|
Buscar
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button type="button" className="button buttonSearch" onClick={exportarExcel}>
|
||||||
|
Exportar Excel
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div>
|
{/* TABLA */}
|
||||||
|
<div className="containerTable">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Carrera</th>
|
<th>Carrera</th>
|
||||||
<th>Genero</th>
|
<th>Periodo</th>
|
||||||
|
<th>Femenino</th>
|
||||||
|
<th>Masculino</th>
|
||||||
|
<th>WINDOWS</th>
|
||||||
|
<th>MACINTOSH</th>
|
||||||
|
<th>LINUX</th>
|
||||||
<th>Profesores</th>
|
<th>Profesores</th>
|
||||||
<th>Total</th>
|
<th>Total</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Impresione B/N</th>
|
|
||||||
<th>Impresiones Color</th>
|
|
||||||
<th>Plotteo</th>
|
|
||||||
<th>Escaner</th>
|
|
||||||
<th>Total</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
{tablaData.map((row, index) => (
|
||||||
<td>5,000</td>
|
<tr key={index}>
|
||||||
<td>10,000</td>
|
<td>{row.carrera}</td>
|
||||||
<td>10,000</td>
|
<td>{row.semestre}</td>
|
||||||
<td>20,000</td>
|
<td>{row.femenino}</td>
|
||||||
<td>45,000</td>
|
<td>{row.masculino}</td>
|
||||||
</tr>
|
<td>{row.WINDOWS}</td>
|
||||||
|
<td>{row.MACINTOSH}</td>
|
||||||
|
<td>{row.LINUX}</td>
|
||||||
|
<td>{row.PROFESORES}</td>
|
||||||
|
<td>{row.TOTAL}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//IO
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.containerTable{
|
||||||
|
overflow: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
background-color:rgba(84, 84, 84, 0.1) ;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textLeft{
|
||||||
|
padding-left: 20px;
|
||||||
|
text-align: start;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
|
import { Metadata } from "next";
|
||||||
import Inscripciones from "./Inscripciones";
|
import Inscripciones from "./Inscripciones";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Inscritos",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
period: string;
|
period: string;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 14px;
|
font-size: 1.4rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
color: #333;
|
color: #333;
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.formGroup label {
|
.formGroup label {
|
||||||
font-size: 14px;
|
font-size: 1.4rem;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
color: #444;
|
color: #444;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 14px;
|
font-size: 1.4rem;
|
||||||
transition: border 0.3s, box-shadow 0.3s;
|
transition: border 0.3s, box-shadow 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
.btn {
|
.btn {
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 14px;
|
font-size: 1.4rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
border: none;
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import EnviarMensaje from "@/app/Components/EviarMensaje/EnviarMensaje";
|
import EnviarMensaje from "@/app/Components/EviarMensaje/EnviarMensaje";
|
||||||
import EnviarMensajeSala from "@/app/Components/EviarMensaje/EnviarMensajeSala";
|
import EnviarMensajeSala from "@/app/Components/EviarMensaje/EnviarMensajeSala";
|
||||||
import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Mensajes",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
|
|||||||
+133
-74
@@ -1,85 +1,144 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
type EstadoPC = 'libre' | 'ocupada' | 'fallo';
|
|
||||||
|
|
||||||
const colores: Record<EstadoPC, string> = {
|
|
||||||
libre: '#22c55e', // verde
|
|
||||||
ocupada: '#ef4444', // rojo
|
|
||||||
fallo: '#facc15', // amarillo
|
|
||||||
};
|
|
||||||
|
|
||||||
const pcs = [
|
|
||||||
{
|
|
||||||
id: 'PC-01',
|
|
||||||
x: 120,
|
|
||||||
y: 180,
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
estado: 'libre' as EstadoPC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'PC-02',
|
|
||||||
x: 170,
|
|
||||||
y: 180,
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
estado: 'ocupada' as EstadoPC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'PC-03',
|
|
||||||
x: 220,
|
|
||||||
y: 180,
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
estado: 'fallo' as EstadoPC,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function DSC() {
|
export default function DSC() {
|
||||||
return (
|
return (
|
||||||
<svg viewBox="0 0 1500 900" width="100%" height="auto">
|
<svg viewBox="0 0 1600 1000">
|
||||||
|
|
||||||
{/* FONDO */}
|
{/* CONTORNO EXTERIOR */}
|
||||||
<image
|
<rect
|
||||||
href="/plano.svg"
|
x="20"
|
||||||
width="100%"
|
y="20"
|
||||||
height="100%"
|
width="1560"
|
||||||
preserveAspectRatio="xMidYMid meet"
|
height="960"
|
||||||
pointerEvents="none"
|
fill="none"
|
||||||
|
stroke="black"
|
||||||
|
strokeWidth="4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* CAPA INTERACTIVA */}
|
{/* ===== BLOQUE SUPERIOR IZQUIERDO (AULAS Y SALAS) ===== */}
|
||||||
<g>
|
|
||||||
{pcs.map(pc => (
|
|
||||||
<g key={pc.id}>
|
|
||||||
{/* CUADRO */}
|
|
||||||
<rect
|
|
||||||
x={pc.x}
|
|
||||||
y={pc.y}
|
|
||||||
width={pc.width}
|
|
||||||
height={pc.height}
|
|
||||||
fill={colores[pc.estado]}
|
|
||||||
stroke="#bb4242"
|
|
||||||
strokeWidth={1}
|
|
||||||
rx={4}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
onClick={() => console.log(pc.id)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* NÚMERO */}
|
<rect x="40" y="40" width="400" height="180"
|
||||||
<text
|
fill="none" stroke="black" strokeWidth="2" />
|
||||||
x={pc.x + pc.width / 2}
|
<text x="240" y="30" textAnchor="middle" fontSize="18" fontWeight="bold">
|
||||||
y={pc.y + pc.height / 2 + 5}
|
AULA 5
|
||||||
textAnchor="middle"
|
</text>
|
||||||
fontSize={12}
|
|
||||||
fill="#000"
|
<rect x="40" y="220" width="400" height="150"
|
||||||
pointerEvents="none"
|
fill="none" stroke="black" strokeWidth="2" />
|
||||||
>
|
<text x="240" y="210" textAnchor="middle" fontSize="16">
|
||||||
{pc.id.replace('PC-', '')}
|
SALA USOS MÚLTIPLES
|
||||||
</text>
|
</text>
|
||||||
</g>
|
|
||||||
))}
|
<rect x="40" y="370" width="400" height="150"
|
||||||
</g>
|
fill="none" stroke="black" strokeWidth="2" />
|
||||||
|
<text x="240" y="360" textAnchor="middle" fontSize="16">
|
||||||
|
SALA DE ENTRENAMIENTO
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* ===== BLOQUE CENTRAL WINDOWS ===== */}
|
||||||
|
|
||||||
|
<rect x="460" y="40" width="700" height="450"
|
||||||
|
fill="none" stroke="black" strokeWidth="3" />
|
||||||
|
<text x="810" y="30" textAnchor="middle" fontSize="22" fontWeight="bold">
|
||||||
|
ÁREA WINDOWS
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* División interna Windows */}
|
||||||
|
<line x1="460" y1="260" x2="1160" y2="260"
|
||||||
|
stroke="black" strokeWidth="2" />
|
||||||
|
|
||||||
|
{/* ===== BLOQUE DERECHO MAC ===== */}
|
||||||
|
|
||||||
|
<rect x="1200" y="40" width="350" height="300"
|
||||||
|
fill="none" stroke="black" strokeWidth="3" />
|
||||||
|
<text x="1375" y="30" textAnchor="middle" fontSize="22" fontWeight="bold">
|
||||||
|
ÁREA MAC
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* ===== ROBÓTICA ===== */}
|
||||||
|
|
||||||
|
<rect x="1200" y="360" width="350" height="200"
|
||||||
|
fill="none" stroke="black" strokeWidth="3" />
|
||||||
|
<text x="1375" y="350" textAnchor="middle" fontSize="18" fontWeight="bold">
|
||||||
|
ROBÓTICA
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* ===== ADMINISTRACIÓN Y OFICINAS ===== */}
|
||||||
|
|
||||||
|
<rect x="460" y="520" width="300" height="150"
|
||||||
|
fill="none" stroke="black" strokeWidth="2" />
|
||||||
|
<text x="610" y="510" textAnchor="middle" fontSize="16">
|
||||||
|
ADMINISTRACIÓN
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<rect x="780" y="520" width="200" height="150"
|
||||||
|
fill="none" stroke="black" strokeWidth="2" />
|
||||||
|
<text x="880" y="510" textAnchor="middle" fontSize="16">
|
||||||
|
ASIGNACIÓN
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<rect x="1000" y="520" width="200" height="150"
|
||||||
|
fill="none" stroke="black" strokeWidth="2" />
|
||||||
|
<text x="1100" y="510" textAnchor="middle" fontSize="16">
|
||||||
|
IMPRESIÓN
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* ===== CUBÍCULOS ===== */}
|
||||||
|
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<rect
|
||||||
|
key={i}
|
||||||
|
x={460 + i * 90}
|
||||||
|
y={700}
|
||||||
|
width="80"
|
||||||
|
height="120"
|
||||||
|
fill="none"
|
||||||
|
stroke="black"
|
||||||
|
strokeWidth="2"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<text x="460" y="690" fontSize="14">
|
||||||
|
CUBÍCULOS 1–8
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* ===== SANITARIOS ===== */}
|
||||||
|
|
||||||
|
<rect x="40" y="600" width="200" height="200"
|
||||||
|
fill="none" stroke="black" strokeWidth="2" />
|
||||||
|
<text x="140" y="590" textAnchor="middle" fontSize="16">
|
||||||
|
SANITARIO HOMBRES
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<rect x="260" y="600" width="200" height="200"
|
||||||
|
fill="none" stroke="black" strokeWidth="2" />
|
||||||
|
<text x="360" y="590" textAnchor="middle" fontSize="16">
|
||||||
|
SANITARIO MUJERES
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* ===== SALIDA EMERGENCIA ===== */}
|
||||||
|
|
||||||
|
<text
|
||||||
|
x="1530"
|
||||||
|
y="900"
|
||||||
|
transform="rotate(-90 1530,900)"
|
||||||
|
fontSize="16"
|
||||||
|
fontWeight="bold"
|
||||||
|
>
|
||||||
|
SALIDA DE EMERGENCIA
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* ===== ENTRADA PRINCIPAL ===== */}
|
||||||
|
|
||||||
|
<text
|
||||||
|
x="800"
|
||||||
|
y="960"
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="20"
|
||||||
|
fontWeight="bold"
|
||||||
|
>
|
||||||
|
ENTRADA Y SALIDA PRINCIPAL
|
||||||
|
</text>
|
||||||
|
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import styles from "./Page.module.css";
|
import styles from "./Page.module.css";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
type AreaUbicacion = {
|
type AreaUbicacion = {
|
||||||
id_area_ubicacion: number;
|
id_area_ubicacion: number;
|
||||||
@@ -23,21 +25,36 @@ export default function MachineTable() {
|
|||||||
const [selectedArea, setSelectedArea] = useState("Todo");
|
const [selectedArea, setSelectedArea] = useState("Todo");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
const fetchMachines = async () => {
|
const fetchMachines = async () => {
|
||||||
const res = await fetch(
|
try {
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/equipo/disable`,
|
|
||||||
{ cache: "no-store" },
|
const res = await axios.get(
|
||||||
);
|
`${process.env.NEXT_PUBLIC_API_URL}/equipo/disable`,
|
||||||
setMachines(await res.json());
|
{ headers },
|
||||||
setLoading(false);
|
);
|
||||||
|
|
||||||
|
setMachines(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cargando equipos:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchAreas = async () => {
|
const fetchAreas = async () => {
|
||||||
const res = await fetch(
|
try {
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
|
const res = await axios.get(
|
||||||
{ cache: "no-store" },
|
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
|
||||||
);
|
{ headers },
|
||||||
setAreas(await res.json());
|
);
|
||||||
|
|
||||||
|
setAreas(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cargando áreas:", error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -53,10 +70,8 @@ export default function MachineTable() {
|
|||||||
selectedArea === "Todo"
|
selectedArea === "Todo"
|
||||||
? machines
|
? machines
|
||||||
: machines.filter(
|
: machines.filter(
|
||||||
(machine) => machine.areaUbicacion.area === selectedArea,
|
(machine) => machine.areaUbicacion.area === selectedArea,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading) return <p>Cargando equipos...</p>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.tableContainer}>
|
<div className={styles.tableContainer}>
|
||||||
@@ -108,3 +123,4 @@ export default function MachineTable() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import styles from "./Page.module.css";
|
import styles from "./Page.module.css";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
type AreaUbicacion = {
|
type AreaUbicacion = {
|
||||||
id_area_ubicacion: number;
|
id_area_ubicacion: number;
|
||||||
@@ -14,31 +16,46 @@ type Equipo = {
|
|||||||
ubicacion: string;
|
ubicacion: string;
|
||||||
plataforma: string;
|
plataforma: string;
|
||||||
area: string;
|
area: string;
|
||||||
ocupado:boolean;
|
ocupado: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default function MachineTableActive() {
|
export default function MachineTableActive() {
|
||||||
const [machines, setMachines] = useState<Equipo[]>([]);
|
const [machines, setMachines] = useState<Equipo[]>([]);
|
||||||
const [areas, setAreas] = useState<AreaUbicacion[]>([]);
|
const [areas, setAreas] = useState<AreaUbicacion[]>([]);
|
||||||
const [selectedArea, setSelectedArea] = useState("Todo");
|
const [selectedArea, setSelectedArea] = useState("Todo");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
const fetchMachines = async () => {
|
const fetchMachines = async () => {
|
||||||
const res = await fetch(
|
try {
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/equipo/active`,
|
|
||||||
{ cache: "no-store" },
|
const res = await axios.get(
|
||||||
);
|
`${process.env.NEXT_PUBLIC_API_URL}/equipo/active`,
|
||||||
setMachines(await res.json());
|
{ headers },
|
||||||
setLoading(false);
|
);
|
||||||
|
|
||||||
|
setMachines(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cargando equipos:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchAreas = async () => {
|
const fetchAreas = async () => {
|
||||||
const res = await fetch(
|
try {
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
|
const res = await axios.get(
|
||||||
{ cache: "no-store" },
|
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
|
||||||
);
|
{ headers },
|
||||||
setAreas(await res.json());
|
|
||||||
|
);
|
||||||
|
|
||||||
|
setAreas(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cargando áreas:", error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -50,8 +67,8 @@ export default function MachineTableActive() {
|
|||||||
selectedArea === "Todo"
|
selectedArea === "Todo"
|
||||||
? machines
|
? machines
|
||||||
: machines.filter(
|
: machines.filter(
|
||||||
(machine) => machine.area === selectedArea,
|
(machine) => machine.area === selectedArea,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading) return <p>Cargando equipos...</p>;
|
if (loading) return <p>Cargando equipos...</p>;
|
||||||
|
|
||||||
@@ -66,7 +83,7 @@ export default function MachineTableActive() {
|
|||||||
<th>Área</th>
|
<th>Área</th>
|
||||||
<th>Disponible</th>
|
<th>Disponible</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style={{position:"relative", zIndex:"0"}}>
|
<tr style={{ position: "relative", zIndex: "0" }}>
|
||||||
<th />
|
<th />
|
||||||
<th />
|
<th />
|
||||||
<th />
|
<th />
|
||||||
@@ -90,7 +107,7 @@ export default function MachineTableActive() {
|
|||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredMachines.map((machine) => (
|
{filteredMachines.map((machine) => (
|
||||||
<tr key={machine.id_equipo} className={!machine.ocupado ? '':styles.ocupado}>
|
<tr key={machine.id_equipo} className={!machine.ocupado ? '' : styles.ocupado}>
|
||||||
<td>{machine.ubicacion}</td>
|
<td>{machine.ubicacion}</td>
|
||||||
<td>{machine.nombre_equipo}</td>
|
<td>{machine.nombre_equipo}</td>
|
||||||
<td className={styles[machine.plataforma]}>
|
<td className={styles[machine.plataforma]}>
|
||||||
@@ -105,3 +122,4 @@ export default function MachineTableActive() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ocupado {
|
.ocupado {
|
||||||
background-color: rgb(173, 233, 253);
|
background-color: #54bbfc !important;
|
||||||
color: red;
|
color: red;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import styles from "./Page.module.css"
|
||||||
|
|
||||||
|
export default function RefreshButton() {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={styles.resetButton}
|
||||||
|
onClick={() => router.refresh()}
|
||||||
|
>
|
||||||
|
Actualizar información
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import styles from "./Page.module.css";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
type Mesa = {
|
||||||
|
id_mesa: number;
|
||||||
|
ocupado: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TableTable() {
|
||||||
|
const [mesas, setMesas] = useState<Mesa[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
const fetchMesas = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/mesa/uso`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
setMesas(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cargando mesas:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchMesas();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <p>Cargando mesas...</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.tableContainer}>
|
||||||
|
<table className={styles.machineTable}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID Mesa</th>
|
||||||
|
<th>Disponible</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{mesas.map((mesa) => (
|
||||||
|
<tr key={mesa.id_mesa} className={!mesa.ocupado ? '' : styles.ocupado}>
|
||||||
|
<td>{mesa.id_mesa}</td>
|
||||||
|
<td>
|
||||||
|
{mesa.ocupado === 1 ? "No" : "Si"}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,29 +1,37 @@
|
|||||||
import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
|
||||||
import MachineTable from "./MachineTable";
|
|
||||||
import styles from "./Page.module.css";
|
import styles from "./Page.module.css";
|
||||||
import MachineTableActive from "./MachineTableActive";
|
import MachineTableActive from "./MachineTableActive";
|
||||||
import DSC from "./DSC";
|
import DSC from "./DSC";
|
||||||
|
import ToggleTable from "@/app/Components/Global/Toggle/ToggleTable";
|
||||||
|
import TableTable from "./TableTable";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import RefreshButton from "./RefreshButton";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Monitor",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{ numAcount: string; key: string }>;
|
searchParams?: Promise<{ numAcount: string; key: string }>;
|
||||||
}) {
|
}) {
|
||||||
const params = await props.searchParams;
|
const params = await props.searchParams;
|
||||||
const numAcount = params?.numAcount ?? null;
|
const numAcount = params?.numAcount ?? null;
|
||||||
const key = params?.key ?? "active";
|
const key = params?.key ?? "Equipos";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection">
|
||||||
<h2 className="title">MONITOR DE MÁQUINAS</h2>
|
<h2 className="title">MONITOR</h2>
|
||||||
|
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<button className={styles.resetButton}>Actualizar información</button>
|
<RefreshButton />
|
||||||
</div>
|
</div>
|
||||||
<Toggle
|
|
||||||
|
<ToggleTable
|
||||||
|
key={Date.now()}
|
||||||
defaultView={key}
|
defaultView={key}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
key: "active",
|
key: "Equipos",
|
||||||
label: "Disponibles",
|
label: "Equipos",
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<MachineTableActive />
|
<MachineTableActive />
|
||||||
@@ -31,11 +39,11 @@ export default async function Page(props: {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "disable",
|
key: "Mesas",
|
||||||
label: "No Disponibles",
|
label: "Mesas",
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<MachineTable />
|
<TableTable />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
interface Periodo {
|
||||||
|
id_periodo: number;
|
||||||
|
semestre: string;
|
||||||
|
fecha_inicio_servicio: string;
|
||||||
|
fecha_fin_servicio: string;
|
||||||
|
activo: {
|
||||||
|
type: string;
|
||||||
|
data: number[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PeriodoPage() {
|
||||||
|
|
||||||
|
const [periodos, setPeriodos] = useState<Periodo[]>([]);
|
||||||
|
const [semestre, setSemestre] = useState("");
|
||||||
|
const [fechaInicio, setFechaInicio] = useState("");
|
||||||
|
const [fechaFin, setFechaFin] = useState("");
|
||||||
|
const [modoEdicion, setModoEdicion] = useState(false);
|
||||||
|
const [idEditar, setIdEditar] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const API = `${envConfig.apiUrl}/periodo`;
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
const obtenerPeriodos = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(API, { headers });
|
||||||
|
setPeriodos(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al obtener periodos", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
obtenerPeriodos();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSemestreChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const valor = e.target.value;
|
||||||
|
|
||||||
|
if (/^\d{0,6}$/.test(valor)) {
|
||||||
|
setSemestre(valor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const crearPeriodo = async () => {
|
||||||
|
if (semestre.length !== 6) {
|
||||||
|
alert("El semestre debe tener exactamente 6 dígitos.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(
|
||||||
|
API,
|
||||||
|
{
|
||||||
|
semestre,
|
||||||
|
fecha_inicio_servicio: fechaInicio,
|
||||||
|
fecha_fin_servicio: fechaFin,
|
||||||
|
activo: true,
|
||||||
|
},
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
|
||||||
|
limpiarFormulario();
|
||||||
|
obtenerPeriodos();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al crear periodo", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cargarPeriodo = (p: Periodo) => {
|
||||||
|
setModoEdicion(true);
|
||||||
|
setIdEditar(p.id_periodo);
|
||||||
|
setSemestre(p.semestre);
|
||||||
|
setFechaInicio(p.fecha_inicio_servicio);
|
||||||
|
setFechaFin(p.fecha_fin_servicio);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const modificarPeriodo = async () => {
|
||||||
|
if (semestre.length !== 6) {
|
||||||
|
alert("El semestre debe tener exactamente 6 dígitos.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.put(
|
||||||
|
`${API}/${idEditar}`,
|
||||||
|
{
|
||||||
|
semestre,
|
||||||
|
fecha_inicio_servicio: fechaInicio,
|
||||||
|
fecha_fin_servicio: fechaFin,
|
||||||
|
},
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
|
||||||
|
limpiarFormulario();
|
||||||
|
obtenerPeriodos();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al modificar periodo", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const limpiarFormulario = () => {
|
||||||
|
setSemestre("");
|
||||||
|
setFechaInicio("");
|
||||||
|
setFechaFin("");
|
||||||
|
setModoEdicion(false);
|
||||||
|
setIdEditar(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 style={{ color: "rgb(3, 1, 72)" }}>PERIODO ESCOLAR</h1>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: "10px" }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Periodo (6 dígitos)"
|
||||||
|
value={semestre}
|
||||||
|
maxLength={6}
|
||||||
|
onChange={handleSemestreChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label style={{ color: "rgb(3, 1, 72)" }}>
|
||||||
|
Elige la fecha de inicio de Periodo
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={fechaInicio}
|
||||||
|
onChange={(e) => setFechaInicio(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label style={{ color: "rgb(3, 1, 72)" }}>
|
||||||
|
Elige la fecha de fin de Periodo
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={fechaFin}
|
||||||
|
onChange={(e) => setFechaFin(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{modoEdicion ? (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="button buttonSearch"
|
||||||
|
onClick={modificarPeriodo}
|
||||||
|
>
|
||||||
|
Guardar Cambios
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="button buttonCancel"
|
||||||
|
onClick={limpiarFormulario}
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className="button buttonSearch"
|
||||||
|
onClick={crearPeriodo}
|
||||||
|
>
|
||||||
|
Crear Periodo
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ overflow: "auto", maxHeight: "350px" }}>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>PERIODO</th>
|
||||||
|
<th>Inicio</th>
|
||||||
|
<th>Fin</th>
|
||||||
|
<th>Activo</th>
|
||||||
|
<th>Acción</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{periodos.map((p) => (
|
||||||
|
<tr key={p.id_periodo}>
|
||||||
|
<td>{p.id_periodo}</td>
|
||||||
|
<td>{p.semestre}</td>
|
||||||
|
<td>{p.fecha_inicio_servicio}</td>
|
||||||
|
<td>{p.fecha_fin_servicio}</td>
|
||||||
|
<td>{p.activo.data[0] === 1 ? "Sí" : "No"}</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
className="button buttonSearch"
|
||||||
|
onClick={() => cargarPeriodo(p)}
|
||||||
|
>
|
||||||
|
Modificar
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//IO
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import styleprograms from "./programas.module.css";
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
import styleprograms from "./programas.module.css";
|
||||||
|
import axios from "axios";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
interface Programa {
|
interface Programa {
|
||||||
id_programa: number;
|
id_programa: number;
|
||||||
@@ -11,66 +15,93 @@ interface Programa {
|
|||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [modo, setModo] = useState("ver");
|
const [modo, setModo] = useState("ver");
|
||||||
|
|
||||||
const [programaSeleccionado, setProgramaSeleccionado] = useState("");
|
const [programaSeleccionado, setProgramaSeleccionado] = useState("");
|
||||||
const [programas, setProgramas] = useState<Programa[]>([]);
|
const [programas, setProgramas] = useState<Programa[]>([]);
|
||||||
const [nombrePrograma, setNombrePrograma] = useState("");
|
const [nombrePrograma, setNombrePrograma] = useState("");
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
const cargarProgramas = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${envConfig.apiUrl}/programa/`, { headers }
|
||||||
|
);
|
||||||
|
setProgramas(response.data);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error al cargar programas:", error);
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al cargar programas",
|
||||||
|
text: error.response?.data?.message || error.message,
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${envConfig.apiUrl}/programa/`)
|
cargarProgramas();
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => setProgramas(data))
|
|
||||||
.catch((error) => console.error("Error al cargar programas:", error));
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleInsertar = async () => {
|
const handleInsertar = async () => {
|
||||||
if (!nombrePrograma) return;
|
if (!nombrePrograma) return;
|
||||||
|
|
||||||
await fetch(`${envConfig.apiUrl}/programa`, {
|
try {
|
||||||
method: "POST",
|
await axios.post(`${envConfig.apiUrl}/programa`, {
|
||||||
headers: {
|
programa: nombrePrograma,
|
||||||
"Content-Type": "application/json",
|
}, { headers }
|
||||||
},
|
);
|
||||||
body: JSON.stringify({
|
|
||||||
programa: nombrePrograma,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await fetch(`${envConfig.apiUrl}/programa/`);
|
await cargarProgramas();
|
||||||
const data = await res.json();
|
|
||||||
setProgramas(data);
|
|
||||||
|
|
||||||
setNombrePrograma("");
|
setNombrePrograma("");
|
||||||
setModo("ver");
|
setModo("ver");
|
||||||
};
|
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Programa creado correctamente",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al insertar",
|
||||||
|
text: error.response?.data?.message || error.message,
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleGuardar = async () => {
|
const handleGuardar = async () => {
|
||||||
if (!programaSeleccionado) return;
|
if (!programaSeleccionado) return;
|
||||||
|
try {
|
||||||
|
|
||||||
await fetch(`${envConfig.apiUrl}/programa/${programaSeleccionado}`, {
|
await axios.patch(
|
||||||
method: "PATCH",
|
`${envConfig.apiUrl}/programa/${programaSeleccionado}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
programa: nombrePrograma,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
{ headers },
|
||||||
programa: nombrePrograma,
|
);
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await fetch(`${envConfig.apiUrl}/programa/`);
|
await cargarProgramas();
|
||||||
const data = await res.json();
|
setModo("ver");
|
||||||
setProgramas(data);
|
|
||||||
|
|
||||||
setModo("ver");
|
Swal.fire({
|
||||||
|
title: "Programa actualizado correctamente",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al actualizar",
|
||||||
|
text: error.response?.data?.message || error.message,
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNuevo = () => setModo("nuevo");
|
const handleNuevo = () => setModo("nuevo");
|
||||||
|
|
||||||
const handleCancelar = () => setModo("ver");
|
const handleCancelar = () => setModo("ver");
|
||||||
|
|
||||||
const handleEditar = () => {
|
const handleEditar = () => {
|
||||||
const programa = programas.find(
|
const programa = programas.find(
|
||||||
(p) => p.id_programa === Number(programaSeleccionado),
|
(p) => p.id_programa === Number(programaSeleccionado)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (programa) {
|
if (programa) {
|
||||||
@@ -79,13 +110,39 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (!programaSeleccionado) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(
|
||||||
|
`${envConfig.apiUrl}/programa/${programaSeleccionado}`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
await cargarProgramas();
|
||||||
|
setProgramaSeleccionado("");
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Programa eliminado exitosamente",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al eliminar",
|
||||||
|
text: error.response?.data?.message || error.message,
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection">
|
||||||
{}
|
|
||||||
{modo === "ver" && (
|
{modo === "ver" && (
|
||||||
<section className="containersection">
|
<section className="containersection">
|
||||||
<h3 className="title">PROGRAMAS</h3>
|
<h3 className="title">PROGRAMAS</h3>
|
||||||
<label className="label">Selecciona el programa que quieres editar</label>
|
<label className="label">
|
||||||
|
Selecciona el programa que quieres editar
|
||||||
|
</label>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={programaSeleccionado}
|
value={programaSeleccionado}
|
||||||
@@ -106,7 +163,8 @@ export default function Page() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`button buttonSearch ${styleprograms.button}`}
|
className="button buttonCharge"
|
||||||
|
style={{ margin: "5px" }}
|
||||||
onClick={handleNuevo}
|
onClick={handleNuevo}
|
||||||
>
|
>
|
||||||
Nuevo
|
Nuevo
|
||||||
@@ -115,11 +173,9 @@ export default function Page() {
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{}
|
|
||||||
{modo === "editar" && (
|
{modo === "editar" && (
|
||||||
<section className="containersection">
|
<section className="containersection">
|
||||||
<h2 className="title">Editar Programa</h2>
|
<h2 className="title">Editar Programa</h2>
|
||||||
<label className="label"> {programaSeleccionado}</label>
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -129,11 +185,9 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="margin">
|
<div className="margin">
|
||||||
<button className="button buttonSearch" onClick={handleGuardar}>
|
<button className="button buttonSearch" onClick={handleGuardar}>
|
||||||
Guardar
|
Guardar
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`button buttonCancel ${styleprograms.button}`}
|
className={`button buttonCancel ${styleprograms.button}`}
|
||||||
@@ -141,29 +195,32 @@ export default function Page() {
|
|||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`button buttonCancel ${styleprograms.button}`}
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
Eliminar
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{}
|
|
||||||
{modo === "nuevo" && (
|
{modo === "nuevo" && (
|
||||||
<section className="containersection">
|
<section className="containersection">
|
||||||
<h2 className="title">Nuevo Programa</h2>
|
<h2 className="title">Nuevo Programa</h2>
|
||||||
|
|
||||||
<label className="label">Programa</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={nombrePrograma}
|
value={nombrePrograma}
|
||||||
onChange={(e) => setNombrePrograma(e.target.value)}
|
onChange={(e) => setNombrePrograma(e.target.value)}
|
||||||
placeholder="Coloca el programa"
|
placeholder="Coloca el programa"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<div className="margin">
|
<div className="margin">
|
||||||
<button className="button buttonSearch" onClick={handleInsertar}>
|
<button className="button buttonSearch" onClick={handleInsertar}>
|
||||||
Insertar
|
Insertar
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`button buttonCancel ${styleprograms.button}`}
|
className={`button buttonCancel ${styleprograms.button}`}
|
||||||
@@ -177,3 +234,4 @@ export default function Page() {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
@@ -6,8 +6,4 @@
|
|||||||
}
|
}
|
||||||
.margin {
|
.margin {
|
||||||
margin: 10px 10px;
|
margin: 10px 10px;
|
||||||
}
|
}
|
||||||
.button {
|
|
||||||
margin-left: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import SearchUser from "@/app/Components/Global/SearchUser/searchUser";
|
|
||||||
import ShowError from "@/app/Components/Global/ShowError";
|
import ShowError from "@/app/Components/Global/ShowError";
|
||||||
import QuitarSancion from "@/app/Components/QuitarSancion/QuitarSancion";
|
import { GetSancionByStudent } from "@/app/lib/getSancionByStudent";
|
||||||
import { GetSancionByStudent } from "@/app/lib/getStudent copy";
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "QuitarSancion",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
|
|||||||
@@ -0,0 +1,400 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
import {
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Line,
|
||||||
|
LineChart,
|
||||||
|
} from "recharts";
|
||||||
|
import PeriodoPicker from "@/app/Components/PeriodoPicker/periodoPicker";
|
||||||
|
import * as XLSX from "xlsx";
|
||||||
|
|
||||||
|
interface Periodo {
|
||||||
|
id_periodo: number;
|
||||||
|
semestre: string;
|
||||||
|
fecha_inicio_servicio: string;
|
||||||
|
fecha_fin_servicio: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function PorServicio() {
|
||||||
|
const [data, setData] = useState<any[]>([]);
|
||||||
|
const [periodos, setPeriodos] = useState<Periodo[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [serviciosSeleccionados, setServiciosSeleccionados] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [periodo1, setPeriodo1] = useState<Periodo | null>(null);
|
||||||
|
const [periodo2, setPeriodo2] = useState<Periodo | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!periodo1 || !periodo2) return;
|
||||||
|
|
||||||
|
const fetchServicios = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
// 🔥 1. traer TODOS los periodos en rango
|
||||||
|
const resPeriodos = await axios.get(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/periodo/range?p1=${periodo1.id_periodo}&p2=${periodo2.id_periodo}`,
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
|
||||||
|
const periodosRango = resPeriodos.data;
|
||||||
|
setPeriodos(periodosRango);
|
||||||
|
|
||||||
|
// 🔥 2. hacer requests por cada periodo
|
||||||
|
const resultados = await Promise.all(
|
||||||
|
periodosRango.map((p: Periodo) =>
|
||||||
|
axios.post(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/detalle-servicio/rango`,
|
||||||
|
{
|
||||||
|
desde: p.fecha_inicio_servicio,
|
||||||
|
hasta: p.fecha_fin_servicio,
|
||||||
|
},
|
||||||
|
{ headers }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 🔥 3. merge dinámico
|
||||||
|
const map = new Map();
|
||||||
|
|
||||||
|
periodosRango.forEach((p: Periodo, index: number) => {
|
||||||
|
resultados[index].data.forEach((item: any) => {
|
||||||
|
if (!map.has(item.servicio)) {
|
||||||
|
map.set(item.servicio, {
|
||||||
|
servicio: item.servicio,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
map.get(item.servicio)[p.semestre] = Number(item.total);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalData = Array.from(map.values());
|
||||||
|
setData(finalData);
|
||||||
|
setServiciosSeleccionados(finalData.map((item: any) => item.servicio));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error comparando servicios", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchServicios();
|
||||||
|
}, [periodo1, periodo2]);
|
||||||
|
|
||||||
|
// 🔥 TRANSFORMAR DATA PARA GRAFICA
|
||||||
|
const transformDataForChart = () => {
|
||||||
|
if (!data.length || !periodos.length) return [];
|
||||||
|
|
||||||
|
return periodos.map((p) => {
|
||||||
|
const row: any = { periodo: p.semestre };
|
||||||
|
|
||||||
|
data.forEach((servicio) => {
|
||||||
|
row[servicio.servicio] = servicio[p.semestre] || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// EXPORTAR A EXCEL
|
||||||
|
const exportToExcel = () => {
|
||||||
|
if (!data || data.length === 0) return;
|
||||||
|
|
||||||
|
const exportData = data.map((row) => {
|
||||||
|
const newRow: any = { Servicio: row.servicio };
|
||||||
|
periodos.forEach((p) => {
|
||||||
|
newRow[p.semestre] = row[p.semestre] || 0;
|
||||||
|
});
|
||||||
|
return newRow;
|
||||||
|
});
|
||||||
|
|
||||||
|
const worksheet = XLSX.utils.json_to_sheet(exportData);
|
||||||
|
const workbook = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Ingresos");
|
||||||
|
|
||||||
|
XLSX.writeFile(workbook, "Reporte_Ingresos_Servicio.xlsx");
|
||||||
|
};
|
||||||
|
|
||||||
|
// FORMATO DE NUMEROS
|
||||||
|
const formatNumber = (value: number) => {
|
||||||
|
return value.toLocaleString("en-US", {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// TOOLTIP ORDENADO
|
||||||
|
const CustomTooltip = ({ active, payload, label }: any) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
const sorted = [...payload]
|
||||||
|
.filter((item) => item.value > 0)
|
||||||
|
.sort((a, b) => b.value - a.value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: "white",
|
||||||
|
padding: "10px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ fontWeight: "bold" }}>
|
||||||
|
Periodo: {label}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{sorted.map((entry: any, index: number) => (
|
||||||
|
<div key={index} style={{ color: entry.color }}>
|
||||||
|
{entry.name}: ${formatNumber(entry.value)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const chartData = transformDataForChart();
|
||||||
|
|
||||||
|
const colors = ["black", "red", "blue", "orange", "purple", "green"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "5px", overflow: "auto" }}>
|
||||||
|
|
||||||
|
<PeriodoPicker
|
||||||
|
onChange={(p1, p2) => {
|
||||||
|
setPeriodo1(p1);
|
||||||
|
setPeriodo2(p2);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!periodo1 || !periodo2 ? (
|
||||||
|
<p>Seleccione un rango de periodos</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* CONTROLES SUPERIORES */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "20px",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
padding: "10px 0",
|
||||||
|
position: "absolute",
|
||||||
|
top: "34px",
|
||||||
|
right: "0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={exportToExcel}
|
||||||
|
disabled={data.length === 0}
|
||||||
|
style={{
|
||||||
|
padding: "8px 16px",
|
||||||
|
backgroundColor:
|
||||||
|
data.length === 0 ? "#ccc" : "#10b981",
|
||||||
|
color: "white",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor:
|
||||||
|
data.length === 0
|
||||||
|
? "not-allowed"
|
||||||
|
: "pointer",
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Excel
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{data.map((servicio) => (
|
||||||
|
<label
|
||||||
|
key={servicio.servicio}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "5px",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
fontSize:"9px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={serviciosSeleccionados.includes(
|
||||||
|
servicio.servicio
|
||||||
|
)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setServiciosSeleccionados((prev) => [
|
||||||
|
...prev,
|
||||||
|
servicio.servicio,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
setServiciosSeleccionados((prev) =>
|
||||||
|
prev.filter(
|
||||||
|
(s) =>
|
||||||
|
s !==
|
||||||
|
servicio.servicio
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{servicio.servicio}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TABLA Y GRÁFICA */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "30px",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
overflow: "auto"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* COLUMNA IZQUIERDA: TABLA */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: "1 1 300px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
overflow: "auto",
|
||||||
|
maxHeight: "400px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading && <p>Cargando...</p>}
|
||||||
|
|
||||||
|
<table style={{ width: "100%" }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Servicio</th>
|
||||||
|
|
||||||
|
{periodos.map((p) => (
|
||||||
|
<th key={p.id_periodo}>
|
||||||
|
{p.semestre}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{!loading && data.length === 0 && (
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colSpan={
|
||||||
|
periodos.length + 1
|
||||||
|
}
|
||||||
|
>
|
||||||
|
No hay datos en este rango
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data.map((row) => (
|
||||||
|
<tr key={row.servicio}>
|
||||||
|
<td>{row.servicio}</td>
|
||||||
|
|
||||||
|
{periodos.map((p) => (
|
||||||
|
<td
|
||||||
|
key={p.id_periodo}
|
||||||
|
>
|
||||||
|
$
|
||||||
|
{formatNumber(
|
||||||
|
row[
|
||||||
|
p.semestre
|
||||||
|
] || 0
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* COLUMNA DERECHA: GRÁFICA */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: "2 1 500px",
|
||||||
|
minWidth: "500px",
|
||||||
|
background: "white",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chartData.length > 0 && (
|
||||||
|
<ResponsiveContainer
|
||||||
|
width="100%"
|
||||||
|
height={400}
|
||||||
|
>
|
||||||
|
<LineChart data={chartData}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
|
||||||
|
<XAxis dataKey="periodo" />
|
||||||
|
|
||||||
|
<YAxis
|
||||||
|
tickFormatter={(value) =>
|
||||||
|
value.toLocaleString()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
content={<CustomTooltip />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Legend />
|
||||||
|
|
||||||
|
{data
|
||||||
|
.filter((servicio) =>
|
||||||
|
serviciosSeleccionados.includes(
|
||||||
|
servicio.servicio
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((servicio, i) => (
|
||||||
|
<Line
|
||||||
|
key={
|
||||||
|
servicio.servicio
|
||||||
|
}
|
||||||
|
dataKey={
|
||||||
|
servicio.servicio
|
||||||
|
}
|
||||||
|
stroke={
|
||||||
|
colors[
|
||||||
|
i %
|
||||||
|
colors.length
|
||||||
|
]
|
||||||
|
}
|
||||||
|
type="monotone"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,18 +5,25 @@ import Toggle from "@/app/Components/Global/Toggle/Toggle";
|
|||||||
import SearchDateBetween from "@/app/Components/SearchDateBetween/SearchDateBetween";
|
import SearchDateBetween from "@/app/Components/SearchDateBetween/SearchDateBetween";
|
||||||
import PorServicios from "@/app/Components/Reportes/porServicio";
|
import PorServicios from "@/app/Components/Reportes/porServicio";
|
||||||
import PorRecibos from "@/app/Components/Reportes/porRecibo";
|
import PorRecibos from "@/app/Components/Reportes/porRecibo";
|
||||||
import DownloadReporteXLSX from "@/app/Components/Dowload/Reporte";
|
import MonthYearPicker from "@/app/Components/MonthYearPicker/MonthYearPicker";
|
||||||
|
import PeriodoPicker from "@/app/Components/PeriodoPicker/periodoPicker";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import PorServicio from "../Reportes-graficas/page";
|
||||||
|
|
||||||
export default function ReportesClient({
|
export default function ReportesClient({
|
||||||
defaultKey,
|
defaultKey,
|
||||||
}: {
|
}: {
|
||||||
defaultKey?: string;
|
defaultKey?: string;
|
||||||
}) {
|
}) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const [desde, setDesde] = useState<string | null>(null);
|
const [desde, setDesde] = useState<string | null>(null);
|
||||||
const [hasta, setHasta] = useState<string | null>(null);
|
const [hasta, setHasta] = useState<string | null>(null);
|
||||||
|
const [periodo1, setPeriodo1] = useState<any>(null);
|
||||||
|
const [periodo2, setPeriodo2] = useState<any>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection" style={{ overflow: "visible", }}>
|
||||||
<h2 className="title">REPORTES</h2>
|
<h2 className="title">REPORTES</h2>
|
||||||
|
|
||||||
<Toggle
|
<Toggle
|
||||||
@@ -27,8 +34,8 @@ export default function ReportesClient({
|
|||||||
label: "Por recibo",
|
label: "Por recibo",
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<SearchDateBetween
|
<MonthYearPicker
|
||||||
onSearch={(d, h) => {
|
onChange={(d, h) => {
|
||||||
setDesde(d);
|
setDesde(d);
|
||||||
setHasta(h);
|
setHasta(h);
|
||||||
}}
|
}}
|
||||||
@@ -40,20 +47,13 @@ export default function ReportesClient({
|
|||||||
{
|
{
|
||||||
key: "Por Servicio",
|
key: "Por Servicio",
|
||||||
label: "Por Servicio",
|
label: "Por Servicio",
|
||||||
content: (
|
content: null,
|
||||||
<>
|
}
|
||||||
<SearchDateBetween
|
|
||||||
onSearch={(d, h) => {
|
|
||||||
setDesde(d);
|
|
||||||
setHasta(h);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<PorServicios desde={desde} hasta={hasta} />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
{defaultKey == "Por Servicio" &&
|
||||||
|
<PorServicio/>
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
import { Metadata } from 'next';
|
||||||
import ReportesClient from './ReportesClient';
|
import ReportesClient from './ReportesClient';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Reportes",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
key: string;
|
key: string;
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import "./user.css"
|
||||||
|
|
||||||
|
interface Perfil {
|
||||||
|
id_perfil: number;
|
||||||
|
perfil: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Usuario {
|
||||||
|
id_usuario: number;
|
||||||
|
nombre: string;
|
||||||
|
apellido_paterno: string;
|
||||||
|
apellido_materno: string;
|
||||||
|
usuario: string;
|
||||||
|
activo: number;
|
||||||
|
descripcion: string;
|
||||||
|
perfil: Perfil;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserPage() {
|
||||||
|
|
||||||
|
const [usuarios, setUsuarios] = useState<Usuario[]>([]);
|
||||||
|
const [perfiles, setPerfiles] = useState<Perfil[]>([]);
|
||||||
|
|
||||||
|
const [nombre, setNombre] = useState("");
|
||||||
|
const [apellidoPaterno, setApellidoPaterno] = useState("");
|
||||||
|
const [apellidoMaterno, setApellidoMaterno] = useState("");
|
||||||
|
const [usuario, setUsuario] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [descripcion, setDescripcion] = useState("");
|
||||||
|
const [idPerfil, setIdPerfil] = useState<number | "">("");
|
||||||
|
const [activo, setActivo] = useState(true);
|
||||||
|
|
||||||
|
const API = `${envConfig.apiUrl}/user`;
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
const obtenerUsuarios = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(API, { headers });
|
||||||
|
setUsuarios(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al obtener usuarios", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const obtenerPerfiles = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${API}/perfil`, { headers });
|
||||||
|
setPerfiles(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al obtener perfiles", error);
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al obtener perfiles!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
obtenerUsuarios();
|
||||||
|
obtenerPerfiles();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const crearUsuario = async () => {
|
||||||
|
|
||||||
|
if (!nombre || !apellidoPaterno || !usuario || !password || !idPerfil) {
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Todos los campos obligatorios deben llenarse.!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(
|
||||||
|
`${API}/create`,
|
||||||
|
{
|
||||||
|
nombre,
|
||||||
|
apellido_paterno: apellidoPaterno,
|
||||||
|
apellido_materno: apellidoMaterno,
|
||||||
|
usuario,
|
||||||
|
password,
|
||||||
|
descripcion,
|
||||||
|
activo: activo ? 1 : 0,
|
||||||
|
id_perfil: idPerfil,
|
||||||
|
},
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
|
||||||
|
limpiarFormulario();
|
||||||
|
obtenerUsuarios();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al crear usuario", error);
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al crear usuario.!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const limpiarFormulario = () => {
|
||||||
|
setNombre("");
|
||||||
|
setApellidoPaterno("");
|
||||||
|
setApellidoMaterno("");
|
||||||
|
setUsuario("");
|
||||||
|
setPassword("");
|
||||||
|
setDescripcion("");
|
||||||
|
setIdPerfil("");
|
||||||
|
setActivo(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const active = async (id_usuario: number) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
await axios.patch(
|
||||||
|
`${API}/${id_usuario}/activo`,
|
||||||
|
{},
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
|
||||||
|
setUsuarios((prev) =>
|
||||||
|
prev.map((u) =>
|
||||||
|
u.id_usuario === id_usuario
|
||||||
|
? { ...u, activo: u.activo === 1 ? 0 : 1 }
|
||||||
|
: u
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cambiar estado del usuario", error);
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al cambiar estado del usuario",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 style={{ color: "rgb(3, 1, 72)" }}>CREAR USUARIO</h1>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: "10px" }}>
|
||||||
|
|
||||||
|
<input placeholder="Nombre" value={nombre} onChange={(e) => setNombre(e.target.value)} />
|
||||||
|
|
||||||
|
<input placeholder="Apellido Paterno" value={apellidoPaterno} onChange={(e) => setApellidoPaterno(e.target.value)} />
|
||||||
|
|
||||||
|
<input placeholder="Apellido Materno" value={apellidoMaterno} onChange={(e) => setApellidoMaterno(e.target.value)} />
|
||||||
|
|
||||||
|
<input placeholder="Usuario" value={usuario} onChange={(e) => setUsuario(e.target.value)} />
|
||||||
|
|
||||||
|
<input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||||
|
|
||||||
|
<input placeholder="Descripción" value={descripcion} onChange={(e) => setDescripcion(e.target.value)} />
|
||||||
|
|
||||||
|
<select value={idPerfil} onChange={(e) => setIdPerfil(Number(e.target.value))}>
|
||||||
|
<option value="">Seleccione Perfil</option>
|
||||||
|
{perfiles.map((p) => (
|
||||||
|
<option key={p.id_perfil} value={p.id_perfil}>
|
||||||
|
{p.perfil}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label className="user">
|
||||||
|
|
||||||
|
Activo
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={activo}
|
||||||
|
onChange={(e) => setActivo(e.target.checked)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button className="button buttonSearch" onClick={crearUsuario}>
|
||||||
|
Crear Usuario
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ overflow: "auto", maxHeight: "350px" }}>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Usuario</th>
|
||||||
|
<th>Perfil</th>
|
||||||
|
<th>Activo</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{usuarios.map((u) => (
|
||||||
|
<tr key={u.id_usuario}>
|
||||||
|
<td>{u.id_usuario}</td>
|
||||||
|
<td>{u.nombre} {u.apellido_paterno} </td>
|
||||||
|
<td>{u.usuario}</td>
|
||||||
|
<td>{u.perfil?.perfil}</td>
|
||||||
|
<td style={{ display: "flex", alignItems: "center", gap: "5px" }}>
|
||||||
|
<span>{u.activo === 1 ? "Sí" : "No"}</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={u.activo === 1}
|
||||||
|
style={{ height: "2rem" }}
|
||||||
|
onChange={() => active(u.id_usuario)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.user{
|
||||||
|
padding-left: 20px;
|
||||||
|
margin-left: 180px;
|
||||||
|
font-size: 25px;;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import Logout from "../Components/auth/Logout/Logout";
|
import Logout from "../Components/auth/Logout/Logout";
|
||||||
|
import BackgroundRotator from "../Components/layout/BackgroundRotator/BackgroundRotator";
|
||||||
|
|
||||||
export default async function PrivateLayout({
|
export default async function PrivateLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
@@ -9,7 +11,7 @@ export default async function PrivateLayout({
|
|||||||
<div className="mainContainer">
|
<div className="mainContainer">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<Logout />
|
<Logout />
|
||||||
<div className="img"></div>
|
<BackgroundRotator/>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
import axios from "axios";
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
|
|
||||||
export default function Areas() {
|
export default function Areas() {
|
||||||
const [areas, setAreas] = useState([]);
|
const [areas, setAreas] = useState([]);
|
||||||
@@ -14,9 +16,15 @@ export default function Areas() {
|
|||||||
"Mantenimiento",
|
"Mantenimiento",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios
|
axios
|
||||||
.get(`${envConfig.apiUrl}/area-ubicacion`)
|
.get(`${envConfig.apiUrl}/area-ubicacion`,
|
||||||
|
{ headers }
|
||||||
|
)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setAreas(data.data);
|
setAreas(data.data);
|
||||||
})
|
})
|
||||||
@@ -29,12 +37,22 @@ export default function Areas() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!selectedArea) {
|
if (!selectedArea) {
|
||||||
toast.error("Selecciona un área antes de actualizar");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Selecciona un área antes de actualizar!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!modo) {
|
if (!modo) {
|
||||||
toast.error("Selecciona un modo");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Selecciona un modo!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,13 +62,23 @@ export default function Areas() {
|
|||||||
{
|
{
|
||||||
id_area_ubicacion: Number(selectedArea),
|
id_area_ubicacion: Number(selectedArea),
|
||||||
activo: modo === "Activo",
|
activo: modo === "Activo",
|
||||||
}
|
}, { headers }
|
||||||
);
|
);
|
||||||
|
|
||||||
toast.success("Equipos actualizados correctamente");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Equipos actualizados correctamente!",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toast.error("Error al actualizar los equipos");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al actualizar los equipos!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -111,4 +139,3 @@ export default function Areas() {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//IO
|
|
||||||
@@ -2,18 +2,33 @@
|
|||||||
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
function Equipos() {
|
export default function Equipos() {
|
||||||
const [cuenta, setCuenta] = useState("");
|
const [cuenta, setCuenta] = useState("");
|
||||||
const [equipoId, setEquipoId] = useState("");
|
const [equipoId, setEquipoId] = useState("");
|
||||||
const [equipos, setEquipos] = useState<any[]>([]);
|
const [equipos, setEquipos] = useState<any[]>([]);
|
||||||
const [mensaje, setMensaje] = useState("");
|
const [mensaje, setMensaje] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${envConfig.apiUrl}/equipo`)
|
const fetchEquipos = async () => {
|
||||||
.then((res) => res.json())
|
try {
|
||||||
.then((data) => setEquipos(data))
|
const token = Cookies.get("token");
|
||||||
.catch(() => setMensaje("Error al cargar equipos"));
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
const response = await axios.get(`${envConfig.apiUrl}/equipo`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
setEquipos(response.data);
|
||||||
|
} catch (error: any) {
|
||||||
|
setMensaje(
|
||||||
|
error.response?.data?.message || "Error al cargar equipos"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchEquipos();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleBuscar = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleBuscar = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
@@ -63,5 +78,4 @@ function Equipos() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
export default Equipos;
|
|
||||||
@@ -3,7 +3,7 @@ import { useState } from "react";
|
|||||||
|
|
||||||
import "@/app/globals.css"
|
import "@/app/globals.css"
|
||||||
|
|
||||||
function Mesas() {
|
export default function Mesas() {
|
||||||
const [mesa, setMesa] = useState(""); // Mesa seleccionada
|
const [mesa, setMesa] = useState(""); // Mesa seleccionada
|
||||||
const [mantenimiento, setMantenimiento] = useState(false); // Checkbox Mantenimiento
|
const [mantenimiento, setMantenimiento] = useState(false); // Checkbox Mantenimiento
|
||||||
const [mensaje, setMensaje] = useState(""); // Mensaje dinámico
|
const [mensaje, setMensaje] = useState(""); // Mensaje dinámico
|
||||||
@@ -18,11 +18,6 @@ function Mesas() {
|
|||||||
|
|
||||||
const estado = mantenimiento ? "en mantenimiento" : "disponible";
|
const estado = mantenimiento ? "en mantenimiento" : "disponible";
|
||||||
setMensaje(`Mesa ${mesa} confirmada como ${estado}`);
|
setMensaje(`Mesa ${mesa} confirmada como ${estado}`);
|
||||||
|
|
||||||
console.log({
|
|
||||||
mesa,
|
|
||||||
mantenimiento,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -60,5 +55,4 @@ function Mesas() {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
export default Mesas;
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
import axios from "axios";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface Mesa {
|
interface Mesa {
|
||||||
id_mesa: number;
|
id_mesa: number;
|
||||||
@@ -13,13 +15,16 @@ export default function MesasDisponibles() {
|
|||||||
const [mesas, setMesas] = useState<Mesa[]>();
|
const [mesas, setMesas] = useState<Mesa[]>();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
obtenerMesas();
|
obtenerMesas();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const obtenerMesas = async () => {
|
const obtenerMesas = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${envConfig.apiUrl}/mesa`);
|
const response = await axios.get(`${envConfig.apiUrl}/mesa`,{ headers });
|
||||||
setMesas(response.data);
|
setMesas(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error al obtener mesas:", error);
|
console.error("Error al obtener mesas:", error);
|
||||||
@@ -30,9 +35,12 @@ export default function MesasDisponibles() {
|
|||||||
|
|
||||||
const toggleEquipo = async (id_mesa: number) => {
|
const toggleEquipo = async (id_mesa: number) => {
|
||||||
try {
|
try {
|
||||||
await fetch(`${envConfig.apiUrl}/mesa/${id_mesa}/activo`, {
|
|
||||||
method: "PATCH",
|
await axios.patch(
|
||||||
});
|
`${envConfig.apiUrl}/mesa/${id_mesa}/activo`,
|
||||||
|
{},
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
setMesas((prev) =>
|
setMesas((prev) =>
|
||||||
prev
|
prev
|
||||||
@@ -42,12 +50,16 @@ export default function MesasDisponibles() {
|
|||||||
...e,
|
...e,
|
||||||
activo: e.activo === 1 ? 0 : 1,
|
activo: e.activo === 1 ? 0 : 1,
|
||||||
}
|
}
|
||||||
: e,
|
: e
|
||||||
)
|
)
|
||||||
: prev,
|
: prev
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error("Error al cambiar estado del equipo", error);
|
Swal.fire({
|
||||||
|
title: "Error al cambiar estado",
|
||||||
|
text: error.response?.data?.message || error.message,
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import Swal from "sweetalert2";
|
|
||||||
|
|
||||||
|
|
||||||
function AlertSwet(){
|
|
||||||
|
|
||||||
Swal.fire("SweetAlert2 is working!");
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AlertSwet
|
|
||||||
@@ -3,8 +3,8 @@ import { useEffect, useState } from "react";
|
|||||||
import apiClient from "@/app/lib/apiClient";
|
import apiClient from "@/app/lib/apiClient";
|
||||||
|
|
||||||
import "./registerAlta.css";
|
import "./registerAlta.css";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
type Carrera = {
|
type Carrera = {
|
||||||
id_carrera: number;
|
id_carrera: number;
|
||||||
@@ -72,10 +72,22 @@ export default function RegisterAlta(props: urlProp) {
|
|||||||
await apiClient.post("/student", payload);
|
await apiClient.post("/student", payload);
|
||||||
|
|
||||||
setSaved(true);
|
setSaved(true);
|
||||||
toast.success("Alumno registrado correctamente");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Alumno registrado correctamente!",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error al guardar alumno:", error);
|
console.error("Error al guardar alumno:", error);
|
||||||
toast.error("Error al guardar alumno");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al guardar alumno:!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { getEquipoByCount } from "../lib/getEquipoByCount";
|
import { getEquipoByCount } from "../lib/getEquipoByCount";
|
||||||
import { getMesaByCount } from "../lib/getMesaByCount";
|
import { getMesaByCount } from "../lib/getMesaByCount";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
inscripcion: any[];
|
inscripcion: any[];
|
||||||
@@ -19,6 +22,9 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
|
|||||||
const [mesaSeleccionada, setMesaSeleccionada] = useState<any>(null);
|
const [mesaSeleccionada, setMesaSeleccionada] = useState<any>(null);
|
||||||
const [tiempo, setTiempo] = useState<number>(15);
|
const [tiempo, setTiempo] = useState<number>(15);
|
||||||
const [bitacora, setBitacora] = useState<[] | null>([]);
|
const [bitacora, setBitacora] = useState<[] | null>([]);
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const fetchByCuenta = async (idCuenta: number) => {
|
const fetchByCuenta = async (idCuenta: number) => {
|
||||||
const [result, resultMesa] = await Promise.all([
|
const [result, resultMesa] = await Promise.all([
|
||||||
@@ -30,28 +36,35 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
|
|||||||
setBitacora(null);
|
setBitacora(null);
|
||||||
} else {
|
} else {
|
||||||
setBitacora([]);
|
setBitacora([]);
|
||||||
if (!result?.error) toast.error("Ya cuentas con un equipo asignado");
|
|
||||||
if (!resultMesa?.error) toast.error("Ya cuentas con una mesa asignada");
|
if (!result?.error) {
|
||||||
|
setError("Equipo")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resultMesa?.error) {
|
||||||
|
setError("Mesa")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!idCuenta) return;
|
if (!idCuenta || isNaN(idCuenta)) return;
|
||||||
const Cuenta = idCuenta;
|
fetchByCuenta(idCuenta);
|
||||||
if (isNaN(Cuenta)) return;
|
|
||||||
|
|
||||||
fetchByCuenta(Cuenta);
|
|
||||||
}, [idCuenta]);
|
}, [idCuenta]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!Array.isArray(inscripcion) || inscripcion.length === 0) return;
|
if (!Array.isArray(inscripcion) || inscripcion.length === 0) return;
|
||||||
|
|
||||||
const todasAsistieron = inscripcion.every(
|
const todasAsistieron = inscripcion.every(
|
||||||
(ins) => ins.platica?.data?.[0] === 1,
|
(ins) => ins.platica?.data?.[0] === 1
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!todasAsistieron) {
|
if (!todasAsistieron) {
|
||||||
toast.error("El alumno no asistió a todas las pláticas");
|
Swal.fire({
|
||||||
|
title: "El alumno no asistió a todas las pláticas!",
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
|
||||||
setPuedeContinuar(false);
|
setPuedeContinuar(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,19 +77,23 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
|
|||||||
|
|
||||||
const fetchMesas = async () => {
|
const fetchMesas = async () => {
|
||||||
try {
|
try {
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
setLoadingMesas(true);
|
setLoadingMesas(true);
|
||||||
const res = await fetch(`${envConfig.apiUrl}/mesa/activo`, {
|
|
||||||
cache: "no-store",
|
const response = await axios.get(
|
||||||
|
`${envConfig.apiUrl}/mesa/activo`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
setMesas(response.data);
|
||||||
|
} catch (error: any) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al cargar mesas disponibles!",
|
||||||
|
text: error.response?.data?.message || error.message,
|
||||||
|
icon: "error",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("No se pudieron cargar las mesas");
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
setMesas(data);
|
|
||||||
} catch (error) {
|
|
||||||
toast.error("Error al cargar mesas disponibles");
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingMesas(false);
|
setLoadingMesas(false);
|
||||||
}
|
}
|
||||||
@@ -88,48 +105,60 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
|
|||||||
const submit = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
const submit = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!mesaSeleccionada) {
|
if (!mesaSeleccionada || Number(mesaSeleccionada) === 0) {
|
||||||
toast.error("Selecciona un mesa");
|
Swal.fire({
|
||||||
|
title: "Seleccione una mesa!",
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
tiempo_asignado: tiempo,
|
tiempo_asignado: tiempo,
|
||||||
id_mesa: Number(mesaSeleccionada),
|
id_mesa: Number(mesaSeleccionada),
|
||||||
id_alumno_inscrito: inscripcion[0].id_alumno_inscrito,
|
id_alumno_inscrito: inscripcion[0].id_alumno_inscrito,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(`${envConfig.apiUrl}/bitacora-mesa`, {
|
await axios.post(
|
||||||
method: "POST",
|
`${envConfig.apiUrl}/bitacora-mesa`,
|
||||||
headers: {
|
body,
|
||||||
"Content-Type": "application/json",
|
{ headers },
|
||||||
},
|
);
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("Error al asignar mesa");
|
|
||||||
}
|
|
||||||
|
|
||||||
setBitacora([]);
|
setBitacora([]);
|
||||||
toast.success("Mesa asignada correctamente");
|
router.refresh();
|
||||||
|
setError("Mesa")
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Mesa asignada correctamente!",
|
title: "Mesa asignada correctamente!",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
draggable: true
|
|
||||||
});
|
});
|
||||||
|
} catch (error: any) {
|
||||||
} catch (error) {
|
Swal.fire({
|
||||||
toast.error("No se pudo asignar la mesa");
|
title: "No se pudo asignar la mesa!",
|
||||||
|
text: error.response?.data?.message || error.message,
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!puedeContinuar) return null;
|
if (!puedeContinuar) return null;
|
||||||
|
|
||||||
if (bitacora) {
|
if (bitacora) {
|
||||||
return <></>;
|
return error === "Mesa" ? (
|
||||||
|
<p style={{ margin: "1rem", color: "red" }}>
|
||||||
|
Ya cuentas con una mesa asignada
|
||||||
|
</p>
|
||||||
|
) : error === "Equipo" ? (
|
||||||
|
<p style={{ margin: "1rem", color: "red" }}>
|
||||||
|
Ya cuentas con un equipo asignado
|
||||||
|
</p>
|
||||||
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="containerForm">
|
<div className="containerForm">
|
||||||
<label style={{ marginTop: "1rem" }}>Seleccionar tiempo</label>
|
<label style={{ marginTop: "1rem" }}>Seleccionar tiempo</label>
|
||||||
@@ -142,6 +171,8 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
|
|||||||
<option value={30}>30 minutos</option>
|
<option value={30}>30 minutos</option>
|
||||||
<option value={40}>40 minutos</option>
|
<option value={40}>40 minutos</option>
|
||||||
<option value={60}>60 minutos</option>
|
<option value={60}>60 minutos</option>
|
||||||
|
<option value={90}>90 minutos</option>
|
||||||
|
<option value={120}>120 minutos</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label style={{ marginTop: "1rem" }}>Seleccione una mesa</label>
|
<label style={{ marginTop: "1rem" }}>Seleccione una mesa</label>
|
||||||
@@ -158,6 +189,7 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<option value={0}>Selecciona una mesa</option>
|
<option value={0}>Selecciona una mesa</option>
|
||||||
|
|
||||||
{!loadingMesas &&
|
{!loadingMesas &&
|
||||||
mesas.map((mesa) => (
|
mesas.map((mesa) => (
|
||||||
<option key={mesa.id_mesa} value={mesa.id_mesa}>
|
<option key={mesa.id_mesa} value={mesa.id_mesa}>
|
||||||
@@ -173,7 +205,6 @@ export default function AsignacionMesas({ idCuenta, inscripcion }: Props) {
|
|||||||
>
|
>
|
||||||
Asignar Mesa
|
Asignar Mesa
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
import { Alumno, AlumnoSancion, Sancion } from "@/app/types/sanction";
|
||||||
|
import { GetSancionByStudent } from "@/app/lib/getSancionByStudent";
|
||||||
|
import TableSanction from "./TableSanction";
|
||||||
|
import axios from "axios";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
if (!envConfig.apiUrl) {
|
||||||
|
console.error("API URL is not defined in envConfig");
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ApplySanction({ idCuenta }: { idCuenta: number }) {
|
||||||
|
const [sanciones, setSanciones] = useState<Sancion[]>([]);
|
||||||
|
const [alumno, setAlumno] = useState<Alumno>();
|
||||||
|
const [alumnoSanciones, setAlumnoSanciones] = useState<AlumnoSancion[]>([]);
|
||||||
|
const [selectedSancion, setSelectedSancion] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingTable, setLoadingTable] = useState(false);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
const fetchAlumnoSanciones = async () => {
|
||||||
|
|
||||||
|
const res = await GetSancionByStudent(idCuenta);
|
||||||
|
|
||||||
|
setAlumno(res.student ?? null);
|
||||||
|
setAlumnoSanciones(res.alusancion ?? []);
|
||||||
|
setLoadingTable(true)
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
setLoadingTable(false)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!idCuenta) return;
|
||||||
|
|
||||||
|
fetchAlumnoSanciones();
|
||||||
|
|
||||||
|
axios
|
||||||
|
.get(`${envConfig.apiUrl}/sancion`, { headers })
|
||||||
|
.then((res) => setSanciones(res.data))
|
||||||
|
.catch(() =>
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al obtener el catálogo de sanciones!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
}, [idCuenta]);
|
||||||
|
|
||||||
|
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!selectedSancion) {
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Selecciona una sanción!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
await axios.post(`${envConfig.apiUrl}/alumno-sancion/`, {
|
||||||
|
id_sancion: Number(selectedSancion),
|
||||||
|
id_cuenta: idCuenta,
|
||||||
|
}, { headers });
|
||||||
|
|
||||||
|
await fetchAlumnoSanciones();
|
||||||
|
setSelectedSancion("");
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Sanción aplicada correctamente!",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al aplicar la sanción!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableSanction alumnoSanciones={alumnoSanciones} alumno={alumno} />
|
||||||
|
|
||||||
|
{loadingTable && alumnoSanciones.length === 0 && (
|
||||||
|
<>
|
||||||
|
<form className="containerForm">
|
||||||
|
<div className="groupInput">
|
||||||
|
<select
|
||||||
|
value={selectedSancion}
|
||||||
|
onChange={(e) => setSelectedSancion(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">-- Selecciona una sanción --</option>
|
||||||
|
{sanciones.map((sancion) => (
|
||||||
|
<option key={sancion.id_sancion} value={sancion.id_sancion}>
|
||||||
|
{sancion.sancion} ({sancion.duracion} semana/s)
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="button buttonSearch"
|
||||||
|
style={{ margin: "1rem 0" }}
|
||||||
|
onClick={handleButton}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? "Aplicando..." : "Aplicar sanción"}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,36 +1,73 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface alumnos {
|
interface AlumnoBitacora {
|
||||||
tiempo_entrada: string;
|
tiempo_entrada: string;
|
||||||
tiempo_asignado: string;
|
tiempo_asignado: number;
|
||||||
Ubicacion_equipo: number;
|
equipo: {
|
||||||
|
ubicacion: string;
|
||||||
|
nombre_equipo: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function BitacoraAlumno() {
|
interface BitacoraAlumnoProps {
|
||||||
const [alumnos, setAlumnos] = useState<alumnos[]>([]);
|
numAcount: string | null;
|
||||||
|
date: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BitacoraAlumno({ numAcount, date }: BitacoraAlumnoProps) {
|
||||||
|
const [alumnos, setAlumnos] = useState<AlumnoBitacora[]>([]);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!numAcount) return;
|
||||||
|
if (!date) return;
|
||||||
|
|
||||||
|
axios.post(`${envConfig.apiUrl}/bitacora/equipos-por-dia`, {
|
||||||
|
numero_cuenta: numAcount,
|
||||||
|
fecha: date,
|
||||||
|
}, { headers })
|
||||||
|
.then(res => {
|
||||||
|
setAlumnos(res.data);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [numAcount, date]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<table>
|
||||||
<table>
|
<thead>
|
||||||
<thead>
|
<tr>
|
||||||
<tr>
|
<th>Tiempo Entrada</th>
|
||||||
<th>Tiempo Entrada</th>
|
<th>Tiempo Asignado</th>
|
||||||
<th>Tiempo Asignado</th>
|
<th>Ubicación del equipo</th>
|
||||||
<th>Ubicacion del equipo</th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody>
|
||||||
<tbody>
|
{alumnos.length > 0 ? (
|
||||||
{alumnos.map((alumno, index) => (
|
alumnos.map((alumno, index) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td>{alumno.tiempo_entrada}</td>
|
<td>{new Date(alumno.tiempo_entrada).toLocaleString()}</td>
|
||||||
<td>{alumno.tiempo_asignado}</td>
|
<td>{alumno.tiempo_asignado}</td>
|
||||||
<td>{alumno.Ubicacion_equipo}</td>
|
<td>{alumno.equipo.ubicacion}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))
|
||||||
</tbody>
|
) : (
|
||||||
</table>
|
<tr>
|
||||||
</>
|
<td colSpan={3} style={{ textAlign: "center" }}>
|
||||||
|
No tuvo equipos asignados
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
export default BitacoraAlumno;
|
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import SearchDate from "../SearchDate/SearchDate";
|
import { useEffect, useState } from "react";
|
||||||
import { useState } from "react";
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
|
||||||
|
import SearchDate from "../SearchDate/SearchDate";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
import "../../(private)/AsignacionEquipo/asignacion.css"
|
||||||
interface equipos {
|
interface equipos {
|
||||||
hora_entrada: string;
|
hora_entrada: string;
|
||||||
min_utilizados: number;
|
min_utilizados: number;
|
||||||
@@ -9,25 +14,120 @@ interface equipos {
|
|||||||
cuenta_asociada: number;
|
cuenta_asociada: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BitacoraEquipo() {
|
export default function BitacoraEquipo({ date }: { date: string | null }) {
|
||||||
const [equipo, setEquipo] = useState<equipos[]>([]);
|
const [equipo, setEquipo] = useState<equipos[]>([]);
|
||||||
const [ubicacion_equipo, setUbicacionEquipo] = useState("");
|
const [open, setOpen] = useState(false);
|
||||||
|
const [loadingEquipos, setLoadingEquipos] = useState(false);
|
||||||
|
const [equipoSeleccionado, setEquipoSeleccionado] = useState<any>(null);
|
||||||
|
const [equipos, setEquipos] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoadingEquipos(true);
|
||||||
|
const response = await axios.get(`${envConfig.apiUrl}/equipo`, { headers },
|
||||||
|
);
|
||||||
|
setEquipos(response.data);
|
||||||
|
setLoadingEquipos(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!equipoSeleccionado?.id_equipo) return;
|
||||||
|
if (!date) return;
|
||||||
|
|
||||||
|
axios.post(`${envConfig.apiUrl}/bitacora/equipos-por-equipo`, {
|
||||||
|
id_equipo: equipoSeleccionado.id_equipo,
|
||||||
|
fecha: date,
|
||||||
|
}, { headers })
|
||||||
|
.then(res => {
|
||||||
|
setEquipo(res.data);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [equipoSeleccionado, date]);
|
||||||
|
|
||||||
|
const equiposFiltrados = equipos.filter((eq) => {
|
||||||
|
const texto = search.toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
eq.ubicacion?.toString().toLowerCase().includes(texto) ||
|
||||||
|
eq.plataforma?.nombre?.toLowerCase().includes(texto) ||
|
||||||
|
eq.nombre_equipo?.toLowerCase().includes(texto)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form className="containerForm">
|
<label>Seleccione un equipo</label>
|
||||||
<label className="label">Ubicacion de equipo</label>
|
<div className="containerForm">
|
||||||
|
|
||||||
<div className="groupInput">
|
<div className="groupInput">
|
||||||
<select
|
<div
|
||||||
value={ubicacion_equipo}
|
className={`customSelect ${open ? "open" : ""} ${loadingEquipos ? "disabled" : ""}`}
|
||||||
onChange={(e) => setUbicacionEquipo(e.target.value)}
|
|
||||||
>
|
>
|
||||||
<option value="">-- Selecciona un equipo --</option>
|
<div
|
||||||
<option value="255">Equipo 255</option>
|
className={`selectHeader ${equipoSeleccionado && equipoSeleccionado.plataforma?.nombre?.toUpperCase()}`}
|
||||||
</select>
|
onClick={() => !loadingEquipos && setOpen(!open)} >
|
||||||
|
{open ? (
|
||||||
|
<input
|
||||||
|
autoFocus
|
||||||
|
type="text"
|
||||||
|
placeholder="Buscar equipo..."
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="inputSelect"
|
||||||
|
/>
|
||||||
|
) : equipoSeleccionado ? (
|
||||||
|
<span>
|
||||||
|
{equipoSeleccionado.ubicacion} {equipoSeleccionado.nombre_equipo}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="placeholder">
|
||||||
|
{loadingEquipos
|
||||||
|
? "Cargando equipos..."
|
||||||
|
: "Seleccione un equipo"}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="arrow">{open ? "▲" : "▼"}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<div className="options" style={{ bottom: "auto" }}>
|
||||||
|
{equiposFiltrados.length === 0 && (
|
||||||
|
<div className="option disabled">
|
||||||
|
No hay resultados
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{equiposFiltrados.map((eq) => (
|
||||||
|
<div
|
||||||
|
key={eq.id_equipo}
|
||||||
|
className={`option ${eq.plataforma?.nombre?.toUpperCase()}`}
|
||||||
|
onClick={() => {
|
||||||
|
setEquipoSeleccionado(eq);
|
||||||
|
setOpen(false);
|
||||||
|
setSearch("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{eq.ubicacion} {eq.nombre_equipo}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<SearchDate />
|
||||||
<SearchDate />
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -39,19 +139,31 @@ function BitacoraEquipo() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{equipo.map((equipo, index) => (
|
{equipo.length > 0 ? (
|
||||||
<tr key={index}>
|
equipo.map((registro, index) => (
|
||||||
<td>{equipo.hora_entrada}</td>
|
<tr key={index}>
|
||||||
<td>{equipo.min_utilizados}</td>
|
<td>{new Date(registro.hora_entrada).toLocaleString()}</td>
|
||||||
<td>{equipo.hora_salida}</td>
|
<td>{registro.min_utilizados}</td>
|
||||||
<td>{equipo.cuenta_asociada}</td>
|
<td>
|
||||||
|
{registro.hora_salida
|
||||||
|
? new Date(registro.hora_salida).toLocaleString()
|
||||||
|
: "—"}
|
||||||
|
</td>
|
||||||
|
<td>{registro.cuenta_asociada}</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={4} style={{ textAlign: "center" }}>
|
||||||
|
Este equipo no tuvo asignaciones en esa fecha
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
export default BitacoraEquipo;
|
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import SearchDate from "../SearchDate/SearchDate";
|
import { useEffect, useState } from "react";
|
||||||
import { useState } from "react";
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
|
||||||
interface tables {
|
import SearchDate from "../SearchDate/SearchDate";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
interface Tables {
|
||||||
no_mesa: number;
|
no_mesa: number;
|
||||||
no_cuenta: number;
|
no_cuenta: number;
|
||||||
hora_entrada: string;
|
hora_entrada: string;
|
||||||
@@ -10,14 +15,35 @@ interface tables {
|
|||||||
hora_salida: string;
|
hora_salida: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BitacoraMesas() {
|
export default function BitacoraMesas() {
|
||||||
const [tables, setTables] = useState<tables[]>([]);
|
const [tables, setTables] = useState<Tables[]>([]);
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const date = searchParams.get("date");
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!date) return;
|
||||||
|
|
||||||
|
axios.post(`${envConfig.apiUrl}/bitacora-mesa/mesas-por-dia`, {
|
||||||
|
fecha: date,
|
||||||
|
}, { headers })
|
||||||
|
.then(res => {
|
||||||
|
setTables(res.data);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
}, [date]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchDate />
|
<SearchDate />
|
||||||
<div>
|
|
||||||
<table>
|
<div style={{ maxHeight: "400px", overflowY: "auto", border: "1px solid #ccc" }}>
|
||||||
|
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Mesa</th>
|
<th>Mesa</th>
|
||||||
@@ -28,20 +54,26 @@ function BitacoraMesas() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{tables.map((table, index) => (
|
{tables.length === 0 ? (
|
||||||
<tr key={index}>
|
<tr>
|
||||||
<td>{table.no_mesa}</td>
|
<td colSpan={5} style={{ textAlign: "center" }}>
|
||||||
<td>{table.no_cuenta}</td>
|
No hubo mesas utilizadas en esta fecha
|
||||||
<td>{table.hora_entrada}</td>
|
</td>
|
||||||
<td>{table.tiempo_asignado}</td>
|
|
||||||
<td>{table.hora_salida}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
) : (
|
||||||
|
tables.map((table, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{table.no_mesa}</td>
|
||||||
|
<td>{table.no_cuenta}</td>
|
||||||
|
<td>{new Date(table.hora_entrada).toLocaleString()}</td>
|
||||||
|
<td>{table.tiempo_asignado}</td>
|
||||||
|
<td>{new Date(table.hora_salida).toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BitacoraMesas;
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Information from "../Global/Information/information";
|
import Information from "../Global/Information/information";
|
||||||
import SearchUser from "../Global/SearchUser/searchUser";
|
import ApplySanction from "./ApplySanction";
|
||||||
import TableSancion from "./TableSancion";
|
|
||||||
|
|
||||||
interface Student {
|
interface Student {
|
||||||
id_cuenta: string;
|
id_cuenta: string;
|
||||||
@@ -13,11 +12,10 @@ export default function Sanciones({ student }: { student?: Student }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchUser value={idCuenta} />
|
|
||||||
{student && (
|
{student && (
|
||||||
<>
|
<>
|
||||||
<Information NoCuenta={student.id_cuenta} Nombre={student.nombre} />
|
<Information NoCuenta={student.id_cuenta} Nombre={student.nombre} />
|
||||||
<TableSancion idCuenta={parseInt(student.id_cuenta)} />
|
<ApplySanction idCuenta={parseInt(student.id_cuenta)} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import axios from "axios";
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
|
||||||
|
|
||||||
interface Alumno {
|
|
||||||
id_cuenta: number;
|
|
||||||
nombre: string;
|
|
||||||
credito: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Sancion {
|
|
||||||
id_sancion: number;
|
|
||||||
sancion: string;
|
|
||||||
duracion: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AlumnoSancion {
|
|
||||||
id_alumno_sancion: number;
|
|
||||||
fecha_inicio: string;
|
|
||||||
sancion: Sancion;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!envConfig.apiUrl) {
|
|
||||||
console.error("API URL is not defined in envConfig");
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TableSancion({ idCuenta }: { idCuenta: number }) {
|
|
||||||
const [sanciones, setSanciones] = useState<Sancion[]>([]);
|
|
||||||
const [alumno, setAlumno] = useState<Alumno | null>(null);
|
|
||||||
const [alumnoSanciones, setAlumnoSanciones] = useState<AlumnoSancion[]>([]);
|
|
||||||
const [selectedSancion, setSelectedSancion] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!idCuenta) return;
|
|
||||||
|
|
||||||
axios
|
|
||||||
.get(`${envConfig.apiUrl}/alumno-sancion/${idCuenta}`)
|
|
||||||
.then((res) => {
|
|
||||||
setAlumno(res.data.student);
|
|
||||||
setAlumnoSanciones(
|
|
||||||
res.data.alusancion?.length ? res.data.alusancion : [],
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
const mensaje =
|
|
||||||
err.response?.data?.message ||
|
|
||||||
"Error al obtener las sanciones del alumno";
|
|
||||||
|
|
||||||
toast.error(mensaje);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Obtener catálogo de sanciones
|
|
||||||
axios
|
|
||||||
.get(`${envConfig.apiUrl}/sancion`)
|
|
||||||
.then((res) => setSanciones(res.data))
|
|
||||||
.catch((err) =>
|
|
||||||
toast.error("Error al obtener el catálogo de sanciones:", err),
|
|
||||||
);
|
|
||||||
}, [idCuenta]);
|
|
||||||
|
|
||||||
const calcularFechaFin = (fechaInicio: string, duracionSemanas: number) => {
|
|
||||||
const fecha = new Date(fechaInicio);
|
|
||||||
fecha.setDate(fecha.getDate() + duracionSemanas * 7);
|
|
||||||
return fecha.toLocaleDateString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div style={{ margin: "1rem 0" }}>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cuenta</th>
|
|
||||||
<th>Motivo de la sanción</th>
|
|
||||||
<th>Duración (Semanas)</th>
|
|
||||||
<th>Fecha Sanción</th>
|
|
||||||
<th>Podrá utilizar el servicio hasta</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{alumnoSanciones.length > 0 ? (
|
|
||||||
alumnoSanciones.map((item) => (
|
|
||||||
<tr key={item.id_alumno_sancion}>
|
|
||||||
<td>{alumno?.id_cuenta}</td>
|
|
||||||
<td>{item.sancion.sancion}</td>
|
|
||||||
<td>{item.sancion.duracion}</td>
|
|
||||||
<td>{new Date(item.fecha_inicio).toLocaleDateString()}</td>
|
|
||||||
<td>
|
|
||||||
{calcularFechaFin(item.fecha_inicio, item.sancion.duracion)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={5}>No hay sanciones registradas</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form className="containerForm">
|
|
||||||
<div className="groupInput">
|
|
||||||
<select
|
|
||||||
value={selectedSancion}
|
|
||||||
onChange={(e) => setSelectedSancion(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">-- Selecciona una sanción --</option>
|
|
||||||
{sanciones.map((sancion) => (
|
|
||||||
<option key={sancion.id_sancion} value={sancion.id_sancion}>
|
|
||||||
{sancion.sancion} ({sancion.duracion} semana/s)
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="button buttonSearch"
|
|
||||||
style={{ margin: "1rem 0" }}
|
|
||||||
>
|
|
||||||
Aplicar sanción
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { Alumno, AlumnoSancion } from "@/app/types/sanction";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
alumnoSanciones?: AlumnoSancion[] | null,
|
||||||
|
alumno?: Alumno
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TableSanction({ alumnoSanciones, alumno }: Props) {
|
||||||
|
|
||||||
|
const calcularFechaFin = (fechaInicio: string, duracionSemanas: number) => {
|
||||||
|
const fecha = new Date(fechaInicio);
|
||||||
|
fecha.setDate(fecha.getDate() + duracionSemanas * 7);
|
||||||
|
|
||||||
|
return fecha.toLocaleString("es-MX", {
|
||||||
|
timeZone: "America/Mexico_City",
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatearFechaMexico = (fechaISO: string) => {
|
||||||
|
return new Date(fechaISO).toLocaleString("es-MX", {
|
||||||
|
timeZone: "America/Mexico_City",
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div style={{ margin: "1rem 0", maxWidth: "730px" }}>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Cuenta</th>
|
||||||
|
<th>Motivo de la sanción</th>
|
||||||
|
<th>Duración (Semanas)</th>
|
||||||
|
<th>Fecha Sanción</th>
|
||||||
|
<th>Podrá utilizar el servicio hasta</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{!(alumnoSanciones?.length === 0) ? (
|
||||||
|
alumnoSanciones?.map((item) => (
|
||||||
|
<tr key={item.id_alumno_sancion}>
|
||||||
|
<td>{alumno?.id_cuenta}</td>
|
||||||
|
<td>{item.sancion.sancion}</td>
|
||||||
|
<td>{item.sancion.duracion}</td>
|
||||||
|
<td>{formatearFechaMexico(item.fecha_inicio)}</td> <td>
|
||||||
|
{calcularFechaFin(item.fecha_inicio, item.sancion.duracion)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={5}>No hay sanciones registradas</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { envConfig } from "../lib/config";
|
import { envConfig } from "../lib/config";
|
||||||
|
|
||||||
import { getEquipoByCount } from "../lib/getEquipoByCount";
|
import { getEquipoByCount } from "../lib/getEquipoByCount";
|
||||||
import SearchUser from "./Global/SearchUser/searchUser";
|
import SearchUser from "./Global/SearchUser/searchUser";
|
||||||
import SearchEquipo from "./SearchEquipo";
|
import SearchEquipo from "./SearchEquipo";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Information from "./Global/Information/information";
|
import Information from "./Global/Information/information";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
async function getEquipoId(idEquipo: number) {
|
async function getEquipoId(idEquipo: number) {
|
||||||
try {
|
try {
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
const res = await axios.get(
|
const res = await axios.get(
|
||||||
`${envConfig.apiUrl}/bitacora/equipo/${idEquipo}`,
|
`${envConfig.apiUrl}/bitacora/equipo/${idEquipo}`,
|
||||||
|
{ headers }
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
@@ -22,7 +27,6 @@ async function getEquipoId(idEquipo: number) {
|
|||||||
error: error.response?.data?.message || "Error al consultar equipo",
|
error: error.response?.data?.message || "Error al consultar equipo",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { error: "Error desconocido" };
|
return { error: "Error desconocido" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,19 +37,31 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
||||||
const [modo, setModo] = useState<"Equipo" | "Cuenta" | null>("Cuenta");
|
const [modo, setModo] = useState<"Equipo" | "Cuenta">("Equipo");
|
||||||
const [bitacora, setBitacora] = useState<any>(null);
|
|
||||||
const [tiempoRestante, setTiempoRestante] = useState<string>("");
|
const [bitacoraCuenta, setBitacoraCuenta] = useState<any>(null);
|
||||||
const [minutos, setMinutos] = useState<number>();
|
const [bitacoraEquipo, setBitacoraEquipo] = useState<any>(null);
|
||||||
|
|
||||||
|
const [tiempoRestante, setTiempoRestante] = useState("");
|
||||||
|
const [minutos, setMinutos] = useState<number>(0);
|
||||||
|
|
||||||
|
const [errorCuenta, setErrorCuenta] = useState<string | null>(null);
|
||||||
|
const [errorEquipo, setErrorEquipo] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const bitacora =
|
||||||
|
modo === "Cuenta" ? bitacoraCuenta : bitacoraEquipo;
|
||||||
|
|
||||||
const fetchByCuenta = async (idCuenta: number) => {
|
const fetchByCuenta = async (idCuenta: number) => {
|
||||||
const result = await getEquipoByCount(idCuenta);
|
const result = await getEquipoByCount(idCuenta);
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
toast.error(result.error);
|
setErrorCuenta(result.error);
|
||||||
setBitacora(null);
|
setBitacoraCuenta(null);
|
||||||
} else {
|
} else {
|
||||||
setBitacora(result);
|
setErrorCuenta(null);
|
||||||
|
setBitacoraCuenta(result);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,28 +69,27 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
|||||||
const result = await getEquipoId(idEquipo);
|
const result = await getEquipoId(idEquipo);
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
toast.error(result.error);
|
setErrorEquipo(result.error);
|
||||||
setBitacora(null);
|
setBitacoraEquipo(null);
|
||||||
} else {
|
} else {
|
||||||
setBitacora(result);
|
setErrorEquipo(null);
|
||||||
|
setBitacoraEquipo(result);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!numAcount) return;
|
if (modo !== "Cuenta" || !numAcount) return;
|
||||||
const idCuenta = parseInt(numAcount);
|
|
||||||
if (isNaN(idCuenta)) return;
|
|
||||||
|
|
||||||
fetchByCuenta(idCuenta);
|
const idCuenta = parseInt(numAcount);
|
||||||
}, [numAcount]);
|
if (!isNaN(idCuenta)) fetchByCuenta(idCuenta);
|
||||||
|
}, [numAcount, modo]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!machine) return;
|
if (modo !== "Equipo" || !machine) return;
|
||||||
const idEquipo = parseInt(machine);
|
|
||||||
if (isNaN(idEquipo)) return;
|
|
||||||
|
|
||||||
fetchByEquipo(idEquipo);
|
const idEquipo = parseInt(machine);
|
||||||
}, [machine]);
|
if (!isNaN(idEquipo)) fetchByEquipo(idEquipo);
|
||||||
|
}, [machine, modo]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!bitacora) return;
|
if (!bitacora) return;
|
||||||
@@ -82,19 +97,32 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
|||||||
const entrada = new Date(bitacora.tiempo_entrada).getTime();
|
const entrada = new Date(bitacora.tiempo_entrada).getTime();
|
||||||
const asignadoMs = bitacora.tiempo_asignado * 60 * 1000;
|
const asignadoMs = bitacora.tiempo_asignado * 60 * 1000;
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const updateTimer = () => {
|
||||||
const ahora = Date.now();
|
const ahora = Date.now();
|
||||||
const restante = entrada + asignadoMs - ahora;
|
const restante = entrada + asignadoMs - ahora;
|
||||||
|
|
||||||
if (restante <= 0) {
|
if (restante <= 0) {
|
||||||
setTiempoRestante("agotado");
|
setTiempoRestante("agotado");
|
||||||
clearInterval(interval);
|
setMinutos(0);
|
||||||
} else {
|
return false;
|
||||||
const minutos = Math.floor(restante / 60000);
|
|
||||||
setMinutos(minutos + 1);
|
|
||||||
const segundos = Math.floor((restante % 60000) / 1000);
|
|
||||||
setTiempoRestante(`${minutos} minutos con ${segundos} segundos`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const minutosRestantes = Math.floor(restante / 60000);
|
||||||
|
const segundos = Math.floor((restante % 60000) / 1000);
|
||||||
|
|
||||||
|
setMinutos(minutosRestantes + 1);
|
||||||
|
setTiempoRestante(
|
||||||
|
`${minutosRestantes} minuto(s) con ${segundos} segundo(s)`
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTimer();
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const sigue = updateTimer();
|
||||||
|
if (!sigue) clearInterval(interval);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
@@ -102,25 +130,42 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
|||||||
|
|
||||||
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!bitacora) return;
|
||||||
|
|
||||||
await axios.patch(
|
setLoading(true);
|
||||||
`${envConfig.apiUrl}/bitacora/cancelar/${bitacora.id_bitacora}`,
|
|
||||||
{ tiempo_asignado: minutos },
|
|
||||||
);
|
|
||||||
|
|
||||||
toast.success("Tiempo cancelado");
|
|
||||||
Swal.fire({
|
|
||||||
title: "Tiempo cancelado!",
|
|
||||||
icon: "success",
|
|
||||||
draggable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (modo === "Cuenta" && numAcount) {
|
try {
|
||||||
fetchByCuenta(parseInt(numAcount));
|
const token = Cookies.get("token");
|
||||||
}
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
if (modo === "Equipo" && machine) {
|
await axios.patch(
|
||||||
fetchByEquipo(parseInt(machine));
|
`${envConfig.apiUrl}/bitacora/cancelar/${bitacora.id_bitacora}`,
|
||||||
|
{ tiempo_asignado: minutos },
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Tiempo cancelado correctamente!",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (modo === "Cuenta" && numAcount) {
|
||||||
|
fetchByCuenta(parseInt(numAcount));
|
||||||
|
setBitacoraEquipo(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modo === "Equipo" && machine) {
|
||||||
|
fetchByEquipo(parseInt(machine));
|
||||||
|
setBitacoraCuenta(null);
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al cancelar tiempo",
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,7 +175,6 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
|||||||
<label className="radio-card">
|
<label className="radio-card">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="modo"
|
|
||||||
checked={modo === "Equipo"}
|
checked={modo === "Equipo"}
|
||||||
onChange={() => setModo("Equipo")}
|
onChange={() => setModo("Equipo")}
|
||||||
/>
|
/>
|
||||||
@@ -141,7 +185,6 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
|||||||
<label className="radio-card">
|
<label className="radio-card">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="modo"
|
|
||||||
checked={modo === "Cuenta"}
|
checked={modo === "Cuenta"}
|
||||||
onChange={() => setModo("Cuenta")}
|
onChange={() => setModo("Cuenta")}
|
||||||
/>
|
/>
|
||||||
@@ -150,10 +193,21 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{modo === "Cuenta" && <SearchUser value={numAcount ?? null} />}
|
{modo === "Cuenta" && (
|
||||||
{modo === "Equipo" && <SearchEquipo value={machine ?? null} />}
|
<>
|
||||||
|
<SearchUser value={numAcount ?? null} />
|
||||||
|
<h2>{errorCuenta}</h2>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{bitacora && (
|
{modo === "Equipo" && (
|
||||||
|
<>
|
||||||
|
<SearchEquipo value={machine ?? null} />
|
||||||
|
<h2>{errorEquipo}</h2>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{bitacora && tiempoRestante !== "agotado" && (
|
||||||
<>
|
<>
|
||||||
<Information
|
<Information
|
||||||
NoCuenta={bitacora.alumno_inscrito.alumno.id_cuenta}
|
NoCuenta={bitacora.alumno_inscrito.alumno.id_cuenta}
|
||||||
@@ -161,10 +215,12 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
|||||||
Tiempo={tiempoRestante}
|
Tiempo={tiempoRestante}
|
||||||
Equipo={bitacora.equipo.ubicacion}
|
Equipo={bitacora.equipo.ubicacion}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="button buttonSearch"
|
className="button buttonSearch"
|
||||||
style={{ marginTop: "1rem" }}
|
style={{ marginTop: "1rem" }}
|
||||||
onClick={handleButton}
|
onClick={handleButton}
|
||||||
|
disabled={loading || minutos === 0}
|
||||||
>
|
>
|
||||||
Cancelar tiempo
|
Cancelar tiempo
|
||||||
</button>
|
</button>
|
||||||
@@ -172,5 +228,4 @@ export default function CheckBoxEquipo({ numAcount, machine }: Props) {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//IO
|
|
||||||
+148
-61
@@ -1,63 +1,80 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { getTableByCount } from "../lib/getTableByCount";
|
||||||
|
import { envConfig } from "../lib/config";
|
||||||
|
import { getMesaByCount } from "../lib/getMesaByCount";
|
||||||
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
import SearchUser from "./Global/SearchUser/searchUser";
|
import SearchUser from "./Global/SearchUser/searchUser";
|
||||||
import Information from "./Global/Information/information";
|
import Information from "./Global/Information/information";
|
||||||
import SearchMesa from "./SearchMesa";
|
import SearchMesa from "./SearchMesa";
|
||||||
import { getTableByCount } from "../lib/getTableByCount";
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { envConfig } from "../lib/config";
|
|
||||||
import { getMesaByCount } from "../lib/getMesaByCount";
|
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
numAcount: string | null;
|
numAcount: string | null;
|
||||||
table: number | null;
|
table: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CheckBoxMesa({ numAcount, table }: props) {
|
export default function CheckBoxMesa({ numAcount, table }: props) {
|
||||||
const [modo, setModo] = useState<"Mesa" | "Cuenta" | null>("Cuenta");
|
const [modo, setModo] = useState<"Mesa" | "Cuenta" | null>("Mesa");
|
||||||
const [bitacora, setBitacora] = useState<any>(null);
|
const [bitacoraMesa, setBitacoraMesa] = useState<any>(null);
|
||||||
|
const [bitacoraCuenta, setBitacoraCuenta] = useState<any>(null);
|
||||||
const [tiempoRestante, setTiempoRestante] = useState<string>("");
|
const [tiempoRestante, setTiempoRestante] = useState<string>("");
|
||||||
const [minutos, setMinutos] = useState<number>();
|
const [minutos, setMinutos] = useState<number>();
|
||||||
|
const [errorMesa, setErrorMesa] = useState<string | null>()
|
||||||
|
const [errorCount, setErrorCount] = useState<string | null>()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const bitacora = modo === "Cuenta" ? bitacoraCuenta : bitacoraMesa;
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
|
||||||
const fetchByCuenta = async (idCuenta: number) => {
|
const fetchByCuenta = async (idCuenta: number) => {
|
||||||
const result = await getMesaByCount(idCuenta);
|
const result = await getMesaByCount(idCuenta);
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
toast.error(result.error);
|
setErrorCount(result.error);
|
||||||
setBitacora(null);
|
setBitacoraCuenta(null);
|
||||||
} else {
|
} else {
|
||||||
setBitacora(result);
|
setBitacoraCuenta(result);
|
||||||
|
setErrorCount(null)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchByEquipo = async (idEquipo: number) => {
|
const fetchByMesa = async (idEquipo: number) => {
|
||||||
const result = await getTableByCount(idEquipo);
|
const result = await getTableByCount(idEquipo);
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
toast.error(result.error);
|
setErrorMesa(result.error);
|
||||||
setBitacora(null);
|
setBitacoraMesa(null);
|
||||||
} else {
|
} else {
|
||||||
setBitacora(result);
|
setBitacoraMesa(result);
|
||||||
|
setErrorMesa(null)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!numAcount) return;
|
if (modo !== "Cuenta" || !numAcount) return;
|
||||||
const idCuenta = parseInt(numAcount);
|
const idCuenta = parseInt(numAcount);
|
||||||
if (isNaN(idCuenta)) return;
|
if (isNaN(idCuenta)) return;
|
||||||
|
|
||||||
fetchByCuenta(idCuenta);
|
fetchByCuenta(idCuenta);
|
||||||
}, [numAcount]);
|
}, [numAcount, modo]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!table) return;
|
setBitacoraMesa(null);
|
||||||
const idEquipo = table;
|
params.delete("numAcount");
|
||||||
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
|
if (modo !== "Mesa" || !table) return;
|
||||||
|
const idEquipo = parseInt(table);
|
||||||
if (isNaN(idEquipo)) return;
|
if (isNaN(idEquipo)) return;
|
||||||
|
fetchByMesa(idEquipo);
|
||||||
fetchByEquipo(idEquipo);
|
|
||||||
}, [table]);
|
}, [table]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -66,19 +83,32 @@ export default function CheckBoxMesa({ numAcount, table }: props) {
|
|||||||
const entrada = new Date(bitacora.tiempo_entrada).getTime();
|
const entrada = new Date(bitacora.tiempo_entrada).getTime();
|
||||||
const asignadoMs = bitacora.tiempo_asignado * 60 * 1000;
|
const asignadoMs = bitacora.tiempo_asignado * 60 * 1000;
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const updateTimer = () => {
|
||||||
const ahora = Date.now();
|
const ahora = Date.now();
|
||||||
const restante = entrada + asignadoMs - ahora;
|
const restante = entrada + asignadoMs - ahora;
|
||||||
|
|
||||||
if (restante <= 0) {
|
if (restante <= 0) {
|
||||||
setTiempoRestante("agotado");
|
setTiempoRestante("agotado");
|
||||||
clearInterval(interval);
|
setMinutos(0);
|
||||||
} else {
|
return false;
|
||||||
const minutos = Math.floor(restante / 60000);
|
|
||||||
setMinutos(minutos + 1);
|
|
||||||
const segundos = Math.floor((restante % 60000) / 1000);
|
|
||||||
setTiempoRestante(`${minutos} minutos con ${segundos} segundos`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const minutosRestantes = Math.floor(restante / 60000);
|
||||||
|
const segundos = Math.floor((restante % 60000) / 1000);
|
||||||
|
|
||||||
|
setMinutos(minutosRestantes + 1);
|
||||||
|
setTiempoRestante(
|
||||||
|
`${minutosRestantes} minuto(s) con ${segundos} segundo(s)`
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTimer();
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const sigue = updateTimer();
|
||||||
|
if (!sigue) clearInterval(interval);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
@@ -86,28 +116,52 @@ export default function CheckBoxMesa({ numAcount, table }: props) {
|
|||||||
|
|
||||||
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
await axios.patch(
|
const token = Cookies.get("token");
|
||||||
`${envConfig.apiUrl}/bitacora-mesa/cancelar/${bitacora.id_bitacora_mesa}`,
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
{ tiempo_asignado: minutos },
|
|
||||||
);
|
|
||||||
|
|
||||||
toast.success("Tiempo cancelado");
|
try {
|
||||||
Swal.fire({
|
if (!bitacora) return;
|
||||||
title: "Tiempo cancelado!",
|
|
||||||
icon: "success",
|
|
||||||
draggable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (modo === "Cuenta" && numAcount) {
|
await axios.patch(
|
||||||
fetchByCuenta(parseInt(numAcount));
|
`${envConfig.apiUrl}/bitacora-mesa/cancelar/${bitacora.id_bitacora_mesa}`,
|
||||||
}
|
{ tiempo_asignado: minutos ?? 0 },
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
|
||||||
if (modo === "Mesa" && table) {
|
Swal.fire({
|
||||||
fetchByEquipo(table);
|
title: "Tiempo cancelado correctamente!",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (modo === "Cuenta" && numAcount) {
|
||||||
|
fetchByCuenta(parseInt(numAcount));
|
||||||
|
params.delete("numAcount");
|
||||||
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modo === "Mesa" && table) {
|
||||||
|
fetchByMesa(parseInt(table));
|
||||||
|
params.delete("table");
|
||||||
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
router.refresh();
|
||||||
|
|
||||||
|
setBitacoraMesa(null);
|
||||||
|
setBitacoraCuenta(null);
|
||||||
|
setErrorMesa('Alumno sin mesa');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al cancelar tiempo",
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="radio-grid">
|
<div className="radio-grid">
|
||||||
@@ -134,26 +188,59 @@ export default function CheckBoxMesa({ numAcount, table }: props) {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{modo === "Cuenta" && <SearchUser value={numAcount ?? null} />}
|
{modo === "Cuenta" &&
|
||||||
{modo === "Mesa" && <SearchMesa />}
|
|
||||||
|
|
||||||
{bitacora && (
|
|
||||||
<>
|
<>
|
||||||
<Information
|
<SearchUser value={numAcount ?? null} />
|
||||||
NoCuenta={bitacora.alumno_inscrito.alumno.id_cuenta}
|
<h2 style={{ marginTop: "10px" }}>{errorCount}</h2>
|
||||||
Nombre={bitacora.alumno_inscrito.alumno.nombre}
|
|
||||||
Tiempo={tiempoRestante}
|
{bitacoraCuenta && tiempoRestante !== "agotado" && (
|
||||||
Mesa={bitacora.mesa.id_mesa}
|
<>
|
||||||
/>
|
<Information
|
||||||
<button
|
NoCuenta={bitacoraCuenta.alumno_inscrito.alumno.id_cuenta}
|
||||||
className="button buttonSearch"
|
Nombre={bitacoraCuenta.alumno_inscrito.alumno.nombre}
|
||||||
style={{ marginTop: "1rem" }}
|
Tiempo={tiempoRestante}
|
||||||
onClick={handleButton}
|
Mesa={bitacoraCuenta.mesa.id_mesa}
|
||||||
>
|
/>
|
||||||
Cancelar tiempo
|
|
||||||
</button>
|
<button
|
||||||
|
className="button buttonSearch"
|
||||||
|
style={{ marginTop: "1rem" }}
|
||||||
|
onClick={handleButton}
|
||||||
|
disabled={loading || !minutos}
|
||||||
|
>
|
||||||
|
Cancelar tiempo
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
}
|
||||||
|
|
||||||
|
{modo === "Mesa" &&
|
||||||
|
<>
|
||||||
|
<SearchMesa value={table ?? null} />
|
||||||
|
<h2 style={{ marginTop: "10px" }}>{errorMesa}</h2>
|
||||||
|
|
||||||
|
{bitacoraMesa && tiempoRestante !== "agotado" && (
|
||||||
|
<>
|
||||||
|
<Information
|
||||||
|
NoCuenta={bitacoraMesa.alumno_inscrito.alumno.id_cuenta}
|
||||||
|
Nombre={bitacoraMesa.alumno_inscrito.alumno.nombre}
|
||||||
|
Tiempo={tiempoRestante}
|
||||||
|
Mesa={bitacoraMesa.mesa.id_mesa}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="button buttonSearch"
|
||||||
|
style={{ marginTop: "1rem" }}
|
||||||
|
onClick={handleButton}
|
||||||
|
disabled={loading || !minutos}
|
||||||
|
>
|
||||||
|
Cancelar tiempo
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,74 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import styles from "./style.module.css";
|
|
||||||
import Cookies from "js-cookie";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import { saveAs } from "file-saver";
|
||||||
|
import ExcelJS from "exceljs";
|
||||||
|
|
||||||
export default function DownloadReporteXLSX() {
|
import styles from "./style.module.css";
|
||||||
|
|
||||||
|
interface Recibo {
|
||||||
|
id_recibo: number;
|
||||||
|
folio_recibo: string;
|
||||||
|
fecha_recibo: string;
|
||||||
|
fecha_registro: string;
|
||||||
|
monto: string;
|
||||||
|
user: {
|
||||||
|
usuario: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Columns{
|
||||||
|
header:string;
|
||||||
|
key:string;
|
||||||
|
width:number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: Recibo[];
|
||||||
|
columns:Columns[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DownloadReporteXLSX({ data,columns }: Props) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const formatearFechaMX = (fechaISO: string) => {
|
||||||
|
return new Date(fechaISO).toLocaleString("es-MX", {
|
||||||
|
timeZone: "America/Mexico_City",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleDownload = async () => {
|
const handleDownload = async () => {
|
||||||
|
if (!data || data.length === 0) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const token = Cookies.get("token");
|
|
||||||
const headers = { Authorization: `Bearer ${token}` };
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const workbook = new ExcelJS.Workbook();
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/equipos/reporteXLSX`,//Modificar
|
const worksheet = workbook.addWorksheet("Recibos");
|
||||||
{ headers }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
worksheet.columns = columns;
|
||||||
toast.error("Error al descargar el archivo");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = await response.blob();
|
data.forEach((recibo) => {
|
||||||
const url = window.URL.createObjectURL(blob);
|
worksheet.addRow({
|
||||||
|
folio: recibo.folio_recibo,
|
||||||
|
monto: Number(recibo.monto),
|
||||||
|
fechaRecibo: recibo.fecha_recibo,
|
||||||
|
fechaRegistro: formatearFechaMX(recibo.fecha_registro),
|
||||||
|
usuario: recibo.user.usuario,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const a = document.createElement("a");
|
worksheet.getColumn("monto").numFmt = '"$"#,##0.00';
|
||||||
a.href = url;
|
|
||||||
a.download = "Reporte Recibos.xlsx";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
|
|
||||||
window.URL.revokeObjectURL(url);
|
const buffer = await workbook.xlsx.writeBuffer();
|
||||||
|
const blob = new Blob([buffer], {
|
||||||
|
type:
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
|
||||||
|
saveAs(blob, "Reporte_Recibos.xlsx");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error("Error generando Excel", error);
|
||||||
toast.error("Hubo un problema al descargar el archivo.");
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -52,7 +83,13 @@ export default function DownloadReporteXLSX() {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<div className={styles.loader}></div>
|
<div className={styles.loader}></div>
|
||||||
) : (
|
) : (
|
||||||
<img src="/excel.svg" alt="Excel" className={styles.iconExcel} width={30} height={30}/>
|
<img
|
||||||
|
src="/excel.svg"
|
||||||
|
alt="Excel"
|
||||||
|
className={styles.iconExcel}
|
||||||
|
width={30}
|
||||||
|
height={30}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
padding: 5px 5px;
|
padding: 5px 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 10px 10px 0 0;
|
border-radius: 10px 10px 0 0;
|
||||||
font-size: 14px;
|
font-size: 1.4rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: background 0.2s ease;
|
transition: background 0.2s ease;
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import axios from "axios";
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import Swal from "sweetalert2";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
nombre_equipo: string;
|
nombre_equipo: string;
|
||||||
@@ -40,31 +41,54 @@ export default function Equipos() {
|
|||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const [equipoRes, plataformaRes, areaRes] = await Promise.all([
|
try {
|
||||||
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;
|
const [equipoRes, plataformaRes, areaRes] = await Promise.all([
|
||||||
|
axios.get(`${envConfig.apiUrl}/equipo/${id}`, { headers },
|
||||||
|
),
|
||||||
|
axios.get(`${envConfig.apiUrl}/alumno-inscrito/plataforma`, { headers },
|
||||||
|
),
|
||||||
|
axios.get(`${envConfig.apiUrl}/area-ubicacion`, { headers },
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const equipo = equipoRes.data as Data;
|
||||||
|
|
||||||
|
setData(equipo);
|
||||||
|
setNombre(equipo.nombre_equipo);
|
||||||
|
setUbicacion(equipo.ubicacion);
|
||||||
|
setIdPlataforma(equipo.id_plataforma);
|
||||||
|
setIdArea(equipo.id_area_ubicacion);
|
||||||
|
|
||||||
|
setPlataforma(plataformaRes.data);
|
||||||
|
setArea(areaRes.data);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Equipo no encontrado",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setData(equipo);
|
|
||||||
setNombre(equipo.nombre_equipo);
|
|
||||||
setUbicacion(equipo.ubicacion);
|
|
||||||
setIdPlataforma(equipo.id_plataforma);
|
|
||||||
setIdArea(equipo.id_area_ubicacion);
|
|
||||||
|
|
||||||
setPlataforma(plataformaRes.data);
|
|
||||||
setArea(areaRes.data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log({
|
||||||
|
id_equipo: data.id_equipo,
|
||||||
|
id_plataforma: idPlataforma,
|
||||||
|
nombre
|
||||||
|
});
|
||||||
await axios.patch(`${envConfig.apiUrl}/equipo`, {
|
await axios.patch(`${envConfig.apiUrl}/equipo`, {
|
||||||
id_equipo: data.id_equipo,
|
id_equipo: data.id_equipo,
|
||||||
nombre_equipo: nombre,
|
nombre_equipo: nombre,
|
||||||
@@ -72,7 +96,9 @@ export default function Equipos() {
|
|||||||
id_plataforma: idPlataforma,
|
id_plataforma: idPlataforma,
|
||||||
id_area_ubicacion: idArea,
|
id_area_ubicacion: idArea,
|
||||||
ip: data.ip,
|
ip: data.ip,
|
||||||
});
|
},
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
|
||||||
// Actualiza el estado local
|
// Actualiza el estado local
|
||||||
setData({
|
setData({
|
||||||
@@ -84,18 +110,28 @@ export default function Equipos() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
toast.success("Equipo actualizado correctamente");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Equipo actualizado correctamente!",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toast.error("Error al guardar el equipo");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al guardar el equipo!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNuevo = async () => {
|
const handleNuevo = async () => {
|
||||||
try {
|
try {
|
||||||
const [plataformaRes, areaRes] = await Promise.all([
|
const [plataformaRes, areaRes] = await Promise.all([
|
||||||
axios.get(`${envConfig.apiUrl}/alumno-inscrito/plataforma`),
|
axios.get(`${envConfig.apiUrl}/alumno-inscrito/plataforma`, { headers }),
|
||||||
axios.get(`${envConfig.apiUrl}/area-ubicacion`),
|
axios.get(`${envConfig.apiUrl}/area-ubicacion`, { headers }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setPlataforma(plataformaRes.data);
|
setPlataforma(plataformaRes.data);
|
||||||
@@ -118,7 +154,11 @@ export default function Equipos() {
|
|||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Error al preparar formulario");
|
Swal.fire({
|
||||||
|
title: "Error al preparar formulario!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,9 +170,14 @@ export default function Equipos() {
|
|||||||
id_plataforma: idPlataforma,
|
id_plataforma: idPlataforma,
|
||||||
id_area_ubicacion: idArea,
|
id_area_ubicacion: idArea,
|
||||||
ip: "0.0.0.0",
|
ip: "0.0.0.0",
|
||||||
});
|
}, { headers });
|
||||||
|
|
||||||
toast.success("Equipo creado correctamente");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Equipo creado correctamente!",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
@@ -140,7 +185,11 @@ export default function Equipos() {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const message =
|
const message =
|
||||||
error?.response?.data?.message || "Error al crear el equipo";
|
error?.response?.data?.message || "Error al crear el equipo";
|
||||||
toast.error(message);
|
Swal.fire({
|
||||||
|
title: message,
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -230,13 +279,6 @@ export default function Equipos() {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="button buttonCharge"
|
|
||||||
onClick={handleNuevo}
|
|
||||||
>
|
|
||||||
Nuevo
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="button buttonSearch"
|
className="button buttonSearch"
|
||||||
@@ -249,6 +291,14 @@ export default function Equipos() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="button buttonCharge"
|
||||||
|
onClick={handleNuevo}
|
||||||
|
>
|
||||||
|
Nuevo
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
@@ -3,44 +3,99 @@
|
|||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
import "./tableEquipos.css";
|
import "./tableEquipos.css";
|
||||||
|
|
||||||
|
type AreaUbicacion = {
|
||||||
|
id_area_ubicacion: number;
|
||||||
|
area: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Equipo = {
|
||||||
|
id_equipo: number;
|
||||||
|
nombre_equipo: string;
|
||||||
|
ubicacion: string;
|
||||||
|
plataforma: {
|
||||||
|
nombre: string
|
||||||
|
};
|
||||||
|
areaUbicacion: {area:string};
|
||||||
|
ocupado: boolean;
|
||||||
|
activo: {
|
||||||
|
data: number[]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default function TableEquipos() {
|
export default function TableEquipos() {
|
||||||
const [equipos, setEquipos] = useState<any[]>([]);
|
const [machines, setMachines] = useState<Equipo[]>([]);
|
||||||
|
const [areas, setAreas] = useState<AreaUbicacion[]>([]);
|
||||||
|
const [selectedArea, setSelectedArea] = useState("Todo");
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${envConfig.apiUrl}/equipo`)
|
|
||||||
.then((res) => res.json())
|
const fetchEquipos = async () => {
|
||||||
.then((data) => {
|
try {
|
||||||
setEquipos(data);
|
const response = await axios.get(`${envConfig.apiUrl}/equipo`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
setMachines(response.data);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("ERROR API:", error.response?.data || error.message);
|
||||||
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
}
|
||||||
.catch((err) => {
|
};
|
||||||
console.error("ERROR API:", err);
|
|
||||||
setLoading(false);
|
fetchEquipos();
|
||||||
});
|
fetchAreas();
|
||||||
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const fetchAreas = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/area-ubicacion`,
|
||||||
|
{ headers },
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
setAreas(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cargando áreas:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toggleEquipo = async (id_equipo: number) => {
|
const toggleEquipo = async (id_equipo: number) => {
|
||||||
try {
|
try {
|
||||||
await fetch(`${envConfig.apiUrl}/mesa/${id_equipo}/activo`, {
|
await axios.patch(
|
||||||
method: "PATCH",
|
`${envConfig.apiUrl}/equipo/${id_equipo}/activo`,
|
||||||
});
|
{},
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
setEquipos((prev) =>
|
setMachines((prev) =>
|
||||||
prev.map((e) =>
|
prev.map((e) =>
|
||||||
e.id_equipo === id_equipo
|
e.id_equipo === id_equipo
|
||||||
? {
|
? {
|
||||||
...e,
|
...e,
|
||||||
activo: {
|
activo: {
|
||||||
data: [e.activo?.data?.[0] === 1 ? 0 : 1],
|
data: [e.activo?.data?.[0] === 1 ? 0 : 1],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: e,
|
: e
|
||||||
),
|
)
|
||||||
|
);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(
|
||||||
|
"Error al cambiar estado del equipo",
|
||||||
|
error.response?.data || error.message
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
console.error("Error al cambiar estado del equipo", error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,6 +103,14 @@ export default function TableEquipos() {
|
|||||||
return <p>Cargando equipos...</p>;
|
return <p>Cargando equipos...</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filteredMachines =
|
||||||
|
selectedArea === "Todo"
|
||||||
|
? machines
|
||||||
|
: machines.filter(
|
||||||
|
(machine) => machine.areaUbicacion.area === selectedArea,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -65,9 +128,29 @@ export default function TableEquipos() {
|
|||||||
<th>Área</th>
|
<th>Área</th>
|
||||||
<th>Activo</th>
|
<th>Activo</th>
|
||||||
</tr>
|
</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>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{equipos.map((eq) => {
|
{filteredMachines.map((eq) => {
|
||||||
const isActivo = eq.activo?.data?.[0] === 1;
|
const isActivo = eq.activo?.data?.[0] === 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -77,7 +160,7 @@ export default function TableEquipos() {
|
|||||||
<td className={eq.plataforma?.nombre}>
|
<td className={eq.plataforma?.nombre}>
|
||||||
{eq.plataforma?.nombre}
|
{eq.plataforma?.nombre}
|
||||||
</td>
|
</td>
|
||||||
<td>{eq.areaUbicacion?.nombre || eq.id_area_ubicacion}</td>
|
<td>{eq.areaUbicacion.area}</td>
|
||||||
|
|
||||||
<td
|
<td
|
||||||
style={{ display: "flex", alignItems: "center", gap: "5px" }}
|
style={{ display: "flex", alignItems: "center", gap: "5px" }}
|
||||||
@@ -98,3 +181,4 @@ export default function TableEquipos() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
import "../../(private)/AsignacionEquipo/asignacion.css";
|
import "../../(private)/AsignacionEquipo/asignacion.css";
|
||||||
|
|
||||||
@@ -39,14 +40,17 @@ const EnviarMensaje = ({ titulo }: EnviarMensajeProps) => {
|
|||||||
|
|
||||||
const [customMsg, setCustomMsg] = useState("");
|
const [customMsg, setCustomMsg] = useState("");
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoadingMensajes(true);
|
setLoadingMensajes(true);
|
||||||
|
|
||||||
const [equiposRes, mensajesRes] = await Promise.all([
|
const [equiposRes, mensajesRes] = await Promise.all([
|
||||||
axios.get(`${envConfig.apiUrl}/equipo/busy`),
|
axios.get(`${envConfig.apiUrl}/equipo/busy`, { headers }),
|
||||||
axios.get(`${envConfig.apiUrl}/mensaje`)
|
axios.get(`${envConfig.apiUrl}/mensaje`, { headers })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setEquipos(equiposRes.data);
|
setEquipos(equiposRes.data);
|
||||||
@@ -70,8 +74,6 @@ const EnviarMensaje = ({ titulo }: EnviarMensajeProps) => {
|
|||||||
mensaje: customMsg || mensaje,
|
mensaje: customMsg || mensaje,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(payload);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface Area {
|
interface Area {
|
||||||
id_area_ubicacion: number;
|
id_area_ubicacion: number;
|
||||||
@@ -20,11 +21,13 @@ const EnviarMensajeSala = ({ titulo }: EnviarMensajeSalaProps) => {
|
|||||||
const [mensaje, setMensaje] = useState("");
|
const [mensaje, setMensaje] = useState("");
|
||||||
const [customMsg, setCustomMsg] = useState("");
|
const [customMsg, setCustomMsg] = useState("");
|
||||||
|
|
||||||
// 🔹 Traer salas desde API
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSalas = async () => {
|
const fetchSalas = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${envConfig.apiUrl}/area-ubicacion`);
|
const response = await axios.get(`${envConfig.apiUrl}/area-ubicacion`, { headers });
|
||||||
setSalas(response.data);
|
setSalas(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error cargando salas:", error);
|
console.error("Error cargando salas:", error);
|
||||||
@@ -42,14 +45,11 @@ const EnviarMensajeSala = ({ titulo }: EnviarMensajeSalaProps) => {
|
|||||||
mensaje: customMsg || mensaje,
|
mensaje: customMsg || mensaje,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(payload);
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="containerForm" onSubmit={handleSubmit}>
|
<form className="containerForm" onSubmit={handleSubmit}>
|
||||||
|
|
||||||
<label className="label">{titulo}</label>
|
<label className="label">{titulo}</label>
|
||||||
<div className="groupInput">
|
<div className="groupInput">
|
||||||
<select
|
<select
|
||||||
@@ -66,7 +66,7 @@ const EnviarMensajeSala = ({ titulo }: EnviarMensajeSalaProps) => {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<label className="label">Seleccione el mensaje</label>
|
<label className="label">Seleccione el mensaje</label>
|
||||||
<div className="groupInput">
|
<div className="groupInput">
|
||||||
<select value={mensaje} onChange={(e) => setMensaje(e.target.value)}>
|
<select value={mensaje} onChange={(e) => setMensaje(e.target.value)}>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default function Information(props: InformationProps) {
|
|||||||
{entries.map(([key, value]) => (
|
{entries.map(([key, value]) => (
|
||||||
<li key={key} className="informationItem">
|
<li key={key} className="informationItem">
|
||||||
<span className="informationKey">{key}</span>
|
<span className="informationKey">{key}</span>
|
||||||
<span className="informationValue">{value}</span>
|
<span className={`informationValue ${key}`}>{value}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -2,57 +2,77 @@
|
|||||||
|
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import SearchDate from "../../SearchDate/SearchDate";
|
|
||||||
|
|
||||||
interface urlProp {
|
interface urlProp {
|
||||||
value: string | null;
|
value: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SearchUserWithDate(props: urlProp) {
|
function SearchUserWithDate(props: urlProp) {
|
||||||
const [numAcount, setnumAcount] = useState("");
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const todayISO = new Date().toLocaleDateString("en-CA");
|
||||||
|
|
||||||
|
const [numAcount, setNumAcount] = useState("");
|
||||||
|
const [date, setDate] = useState(todayISO);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.value) {
|
const num = searchParams.get("numAcount");
|
||||||
setnumAcount(props.value);
|
const dateParam = searchParams.get("date");
|
||||||
}
|
|
||||||
}, [props.value]);
|
if (num) setNumAcount(num);
|
||||||
|
if (dateParam) setDate(dateParam);
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
|
||||||
if (numAcount) {
|
if (numAcount) {
|
||||||
params.set("numAcount", `${numAcount}`);
|
params.set("numAcount", numAcount);
|
||||||
router.push(`${pathname}?${params.toString()}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (date) {
|
||||||
|
params.set("date", date);
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<form className="containerForm" onSubmit={handleSubmit}>
|
||||||
<form className="containerForm" onSubmit={handleSubmit}>
|
<label className="label">No.Cuenta</label>
|
||||||
<label className="label">No.Cuenta</label>
|
<div className="groupInput">
|
||||||
<div className="groupInput">
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
value={numAcount}
|
||||||
value={numAcount}
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
const value = e.target.value;
|
||||||
const value = e.target.value;
|
if (/^\d*$/.test(value) && value.length <= 9) {
|
||||||
if (/^\d*$/.test(value) && value.length <= 9) {
|
setNumAcount(value);
|
||||||
setnumAcount(value);
|
}
|
||||||
}
|
}}
|
||||||
}}
|
placeholder="Coloca un número de cuenta..."
|
||||||
placeholder="Coloca un número de cuenta..."
|
/>
|
||||||
inputMode="numeric"
|
</div>
|
||||||
pattern="[0-9]*"
|
|
||||||
/>
|
<label className="label">Fecha</label>
|
||||||
</div>
|
<div className="groupInput">
|
||||||
</form>
|
|
||||||
<SearchDate />
|
<input
|
||||||
</>
|
type="date"
|
||||||
|
value={date}
|
||||||
|
onChange={(e) => setDate(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button className="button buttonSearch" type="submit">
|
||||||
|
Buscar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SearchUserWithDate;
|
export default SearchUserWithDate;
|
||||||
//IO
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface urlProp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SearchUser(props: urlProp) {
|
function SearchUser(props: urlProp) {
|
||||||
const [numAcount, setnumAcount] = useState("");
|
const [numAcount, setnumAcount] = useState<string>("");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -17,6 +17,10 @@ function SearchUser(props: urlProp) {
|
|||||||
if (props.value) {
|
if (props.value) {
|
||||||
setnumAcount(props.value);
|
setnumAcount(props.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(props.value===null){
|
||||||
|
setnumAcount("")
|
||||||
|
}
|
||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface SearchUserProps {
|
interface SearchUserProps {
|
||||||
value?: string | null;
|
value?: string | null;
|
||||||
@@ -19,7 +20,9 @@ export default function SearchUserTable({ value }: SearchUserProps) {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Si el prop cambia, actualizar el input
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value) setNumAcount(value);
|
if (value) setNumAcount(value);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
@@ -37,8 +40,8 @@ export default function SearchUserTable({ value }: SearchUserProps) {
|
|||||||
setAlumno(null);
|
setAlumno(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Llamada a la API con Axios
|
const { data } = await axios.get<Alumno>(`/api/alumnos/${numAcount}`, { headers },
|
||||||
const { data } = await axios.get<Alumno>(`/api/alumnos/${numAcount}`);
|
);
|
||||||
setAlumno(data);
|
setAlumno(data);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setAlumno(null);
|
setAlumno(null);
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import toast from "react-hot-toast";
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
export default function ShowError({ message }: { message: string }) {
|
export default function ShowError({ message }: { message: string }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (message) {
|
if (message) {
|
||||||
toast.error(message);
|
Swal.fire({
|
||||||
|
title: message,
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [message]);
|
}, [message]);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { useState, ReactNode } from "react";
|
|||||||
interface ToggleOption {
|
interface ToggleOption {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
content: ReactNode;
|
content: ReactNode | (() => ReactNode);
|
||||||
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToggleProps {
|
interface ToggleProps {
|
||||||
@@ -46,7 +47,15 @@ export default function Toggle({ options, defaultView }: ToggleProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="padding toggleContent">
|
<div className="padding toggleContent">
|
||||||
{options.find((opt) => opt.key === view)?.content}
|
{(() => {
|
||||||
|
const active = options.find((opt) => opt.key === view)?.content;
|
||||||
|
|
||||||
|
if (typeof active === "function") {
|
||||||
|
return active();
|
||||||
|
}
|
||||||
|
|
||||||
|
return active;
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import apiClient from "@/app/lib/apiClient";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export default function selectArea() {
|
|
||||||
const [area, setArea] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchArea = async () => {
|
|
||||||
const response = await apiClient.get("/area-ubicacion");
|
|
||||||
setArea(response.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchArea();
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<label className="label">Seleccione el área</label>
|
|
||||||
<select>
|
|
||||||
{area.map((area) => (
|
|
||||||
<option>{area}</option>
|
|
||||||
))}
|
|
||||||
<option value="0">windows</option>
|
|
||||||
<option value="1">ADOBE CREATIVE SUITE</option>
|
|
||||||
<option value="2">mmmmm</option>
|
|
||||||
<option value="3">mmmmm</option>
|
|
||||||
<option value="4">mmmmm</option>
|
|
||||||
<option value="5">mmmmm</option>
|
|
||||||
</select>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
.tableContainer {
|
.tableContainer {
|
||||||
margin-top: 1rem;
|
margin-top: 2rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
@@ -43,4 +43,17 @@
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-height:800px) {
|
||||||
|
.tableContainer {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width:800px) {
|
||||||
|
.tableContainer {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import Cookies from "js-cookie";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { PostImpressions } from "@/app/lib/postImpressions";
|
import { PostImpressions } from "@/app/lib/postImpressions";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
interface CostOption {
|
interface CostOption {
|
||||||
@@ -19,10 +18,11 @@ interface ImpressionsProps {
|
|||||||
|
|
||||||
function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) {
|
function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) {
|
||||||
const [pages, setPages] = useState("");
|
const [pages, setPages] = useState("");
|
||||||
const [cost, setCost] = useState("");
|
const [cost, setCost] = useState(String(costs[0].value));
|
||||||
|
|
||||||
|
const total = Number(cost) * Number(pages);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
Cookies.remove("token");
|
Cookies.remove("token");
|
||||||
Cookies.remove("session");
|
Cookies.remove("session");
|
||||||
@@ -30,25 +30,40 @@ function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handlePayment = async () => {
|
const handlePayment = async () => {
|
||||||
if (!numAcount) {
|
if (numAcount == null) {
|
||||||
toast.error("busca de nuevo al estudiante");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "busca de nuevo al estudiante!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pages) {
|
if (!pages) {
|
||||||
toast.error("Ingresa el numero de hojas a imprimir");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Ingresa el numero de hojas a imprimir!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cost) {
|
if (!cost) {
|
||||||
toast.error("Selecciona un costo");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Selecciona un costo!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await PostImpressions({
|
const result = await PostImpressions({
|
||||||
id_cuenta: numAcount,
|
id_cuenta: numAcount,
|
||||||
numero_hojas: parseInt(pages),
|
numero_hojas: parseInt(pages),
|
||||||
monto: parseInt(cost) * parseInt(pages),
|
monto: total,
|
||||||
id_servicio,
|
id_servicio,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,16 +71,20 @@ function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) {
|
|||||||
if (result.message === "Token inválido") {
|
if (result.message === "Token inválido") {
|
||||||
handleLogout();
|
handleLogout();
|
||||||
} else {
|
} else {
|
||||||
toast.error(result.message);
|
Swal.fire({
|
||||||
|
title: result.message,
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPages("");
|
setPages("");
|
||||||
setCost("");
|
setCost(String(costs[0].value));
|
||||||
toast.success("Impresion cobrada correctamente");
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Cobro exitoso!",
|
title: "Impresion cobrada correctamente!",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
draggable: true
|
draggable: true
|
||||||
});
|
});
|
||||||
@@ -110,14 +129,13 @@ function Impressions({ costs, numAcount, id_servicio }: ImpressionsProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="groupInput"
|
className="groupInput total"
|
||||||
style={{ height: "35px", flexWrap: "nowrap" }}
|
|
||||||
>
|
>
|
||||||
<label className="label">Total:</label>
|
<label className="label">Total:</label>
|
||||||
<label
|
<label
|
||||||
style={{ width: "100%", minWidth: "200px", maxWidth: "500px" }}
|
style={{ width: "100%", minWidth: "200px", maxWidth: "500px" }}
|
||||||
>
|
>
|
||||||
{pages && cost && `$${parseInt(cost) * parseInt(pages)}.00`}
|
{pages && cost && `$${total}.00`}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,8 @@
|
|||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import Swal from "sweetalert2";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
interface Equipos {
|
|
||||||
id_equipo: number;
|
|
||||||
id_plataforma: number;
|
|
||||||
id_area_ubicacion: number;
|
|
||||||
nombre_equipo: string;
|
|
||||||
ubicacion: string;
|
|
||||||
activo: boolean;
|
|
||||||
ip: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Programa {
|
interface Programa {
|
||||||
id_programa: number;
|
id_programa: number;
|
||||||
@@ -23,19 +14,20 @@ export default function ProgramSelector({
|
|||||||
ubicacion,
|
ubicacion,
|
||||||
id,
|
id,
|
||||||
}: {
|
}: {
|
||||||
ubicacion?: string | null;
|
ubicacion?: number | null;
|
||||||
id?: number | null;
|
id?: number | null;
|
||||||
}) {
|
}) {
|
||||||
const [equipoSelection, setEquipoSelection] = useState<string>("");
|
|
||||||
const [roomSelection, setRoomSelection] = useState<number>();
|
|
||||||
const [programas, setProgramas] = useState<Programa[]>([]);
|
const [programas, setProgramas] = useState<Programa[]>([]);
|
||||||
const [checkboxes, setCheckboxes] = useState<Record<number, boolean>>({});
|
const [checkboxes, setCheckboxes] = useState<Record<number, boolean>>({});
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const [programasRes] = await Promise.all([
|
const [programasRes] = await Promise.all([
|
||||||
axios.get(`${envConfig.apiUrl}/programa`),
|
axios.get(`${envConfig.apiUrl}/programa`, { headers },
|
||||||
]);
|
)]);
|
||||||
|
|
||||||
setProgramas(programasRes.data);
|
setProgramas(programasRes.data);
|
||||||
|
|
||||||
@@ -64,31 +56,55 @@ export default function ProgramSelector({
|
|||||||
.map(Number);
|
.map(Number);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (ubicacion && ubicacion !== "0") {
|
if (ubicacion) {
|
||||||
await axios.patch(
|
await axios.patch(
|
||||||
`${envConfig.apiUrl}/programa-equipo/equipo/${ubicacion}`,
|
`${envConfig.apiUrl}/programa-equipo/equipo/${ubicacion}`,
|
||||||
{
|
{
|
||||||
programas: programasSeleccionados,
|
programas: programasSeleccionados,
|
||||||
},
|
},
|
||||||
|
{ headers },
|
||||||
);
|
);
|
||||||
|
|
||||||
toast.success("Programas actualizados en la máquina");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Programas actualizados en la máquina!",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
await axios.patch(`${envConfig.apiUrl}/programa-equipo/sala`, {
|
await axios.patch(`${envConfig.apiUrl}/programa-equipo/sala`,
|
||||||
sala: id,
|
{
|
||||||
programas: programasSeleccionados,
|
sala: id,
|
||||||
});
|
programas: programasSeleccionados,
|
||||||
|
},
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
toast.success("Programas actualizados en la sala");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Programas actualizados en la sala",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.error("No se pudo determinar el destino");
|
Swal.fire({
|
||||||
|
title: "No se pudo determinar el destino!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Error al guardar cambios");
|
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al guardar cambios!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,15 +121,18 @@ export default function ProgramSelector({
|
|||||||
try {
|
try {
|
||||||
let res;
|
let res;
|
||||||
|
|
||||||
if (ubicacion && ubicacion !== "0") {
|
if (ubicacion) {
|
||||||
res = await axios.get(
|
res = await axios.get(
|
||||||
`${envConfig.apiUrl}/programa-equipo/${ubicacion}`,
|
`${envConfig.apiUrl}/programa-equipo/${ubicacion}`,
|
||||||
|
{ headers },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
res = await axios.get(
|
res = await axios.get(
|
||||||
`${envConfig.apiUrl}/area-ubicacion/programs/${id}`,
|
`${envConfig.apiUrl}/area-ubicacion/programs/${id}`,
|
||||||
|
{ headers },
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,12 +142,28 @@ export default function ProgramSelector({
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
if (error.response?.status === 404) {
|
if (error.response?.status === 404) {
|
||||||
toast.error("Sin programas asignados");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Sin programas asignados!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.error("Error al cargar programas");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error al cargar programas!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.error("Error inesperado");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error inesperado!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +175,7 @@ export default function ProgramSelector({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="form-container" onSubmit={handleSubmit}>
|
<form className="form-container" onSubmit={handleSubmit}>
|
||||||
<div className="checkbox-grid">
|
<div className="checkbox-grid" style={{ gridAutoFlow: "column" }}>
|
||||||
{programas.map((prog) => (
|
{programas.map((prog) => (
|
||||||
<label key={prog.id_programa}>
|
<label key={prog.id_programa}>
|
||||||
<input
|
<input
|
||||||
@@ -159,4 +194,4 @@ export default function ProgramSelector({
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//IO
|
//IO
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { envConfig } from "@/app/lib/config";
|
import { envConfig } from "@/app/lib/config";
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
import "../../(private)/AsignacionEquipo/asignacion.css";
|
import "../../(private)/AsignacionEquipo/asignacion.css";
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ interface Equipos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onSearch: (ubicacion: string) => void;
|
onSearch: (id_equipo: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SelectorEquipo({ onSearch }: Props) {
|
export default function SelectorEquipo({ onSearch }: Props) {
|
||||||
@@ -33,10 +35,16 @@ export default function SelectorEquipo({ onSearch }: Props) {
|
|||||||
const [loadingEquipos, setLoadingEquipos] = useState(false);
|
const [loadingEquipos, setLoadingEquipos] = useState(false);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setLoadingEquipos(true);
|
setLoadingEquipos(true);
|
||||||
const response = await axios.get(`${envConfig.apiUrl}/equipo`);
|
const response = await axios.get(`${envConfig.apiUrl}/equipo`, { headers },
|
||||||
|
);
|
||||||
setEquipos(response.data);
|
setEquipos(response.data);
|
||||||
setLoadingEquipos(false);
|
setLoadingEquipos(false);
|
||||||
};
|
};
|
||||||
@@ -49,11 +57,20 @@ export default function SelectorEquipo({ onSearch }: Props) {
|
|||||||
const eq = equipos.find(e => e.id_equipo === paramEquipo);
|
const eq = equipos.find(e => e.id_equipo === paramEquipo);
|
||||||
if (eq) {
|
if (eq) {
|
||||||
setSelected(eq);
|
setSelected(eq);
|
||||||
onSearch(eq.ubicacion);
|
onSearch(eq.id_equipo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [paramEquipo, equipos]);
|
}, [paramEquipo, equipos]);
|
||||||
|
|
||||||
|
const equiposFiltrados = equipos.filter((eq) => {
|
||||||
|
const texto = search.toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
eq.ubicacion?.toString().toLowerCase().includes(texto) ||
|
||||||
|
eq.plataforma?.nombre?.toLowerCase().includes(texto) ||
|
||||||
|
eq.nombre_equipo?.toLowerCase().includes(texto)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="form-container">
|
<form className="form-container">
|
||||||
@@ -64,14 +81,20 @@ export default function SelectorEquipo({ onSearch }: Props) {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`selectHeader ${selected && selected.plataforma?.nombre?.toUpperCase()}`}
|
className={`selectHeader ${selected && selected.plataforma?.nombre?.toUpperCase()}`}
|
||||||
onClick={() => !loadingEquipos && setOpen(!open)}
|
onClick={() => !loadingEquipos && setOpen(!open)} >
|
||||||
>
|
{open ? (
|
||||||
{selected ? (
|
<input
|
||||||
<span
|
autoFocus
|
||||||
>
|
type="text"
|
||||||
{selected.ubicacion}{" "}
|
placeholder="Buscar equipo..."
|
||||||
{selected.nombre_equipo}{" "}
|
value={search}
|
||||||
{selected.areaUbicacion.area}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="inputSelect"
|
||||||
|
/>
|
||||||
|
) : selected ? (
|
||||||
|
<span>
|
||||||
|
{selected.ubicacion} {selected.nombre_equipo}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="placeholder">
|
<span className="placeholder">
|
||||||
@@ -85,14 +108,14 @@ export default function SelectorEquipo({ onSearch }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{open && (
|
{open && (
|
||||||
<div className="options" style={{ bottom: "auto"}}>
|
<div className="options" style={{ bottom: "auto" }}>
|
||||||
{equipos.length === 0 && (
|
{equiposFiltrados.length === 0 && (
|
||||||
<div className="option disabled">
|
<div className="option disabled">
|
||||||
No hay equipos disponibles
|
No hay equipos disponibles
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{equipos.map((eq) => (
|
{equiposFiltrados.map((eq) => (
|
||||||
<div
|
<div
|
||||||
key={eq.id_equipo}
|
key={eq.id_equipo}
|
||||||
className={`option ${eq.plataforma?.nombre?.toUpperCase()}`}
|
className={`option ${eq.plataforma?.nombre?.toUpperCase()}`}
|
||||||
@@ -108,6 +131,8 @@ export default function SelectorEquipo({ onSearch }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router.push(`${pathname}?${params.toString()}`);
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
|
setSearch("");
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{eq.ubicacion} {eq.nombre_equipo} {eq.areaUbicacion.area}
|
{eq.ubicacion} {eq.nombre_equipo} {eq.areaUbicacion.area}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { envConfig } from "@/app/lib/config";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface Area {
|
interface Area {
|
||||||
id_area_ubicacion: number;
|
id_area_ubicacion: number;
|
||||||
@@ -45,8 +46,13 @@ export default function SelectorSala({ onSearch }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const response = await axios.get(`${envConfig.apiUrl}/area-ubicacion`);
|
const response = await axios.get(`${envConfig.apiUrl}/area-ubicacion`, { headers },
|
||||||
|
);
|
||||||
setSala(response.data);
|
setSala(response.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onChange: (desde: string, hasta: string) => void;
|
||||||
|
initialYear?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MonthYearPicker({
|
||||||
|
onChange,
|
||||||
|
initialYear,
|
||||||
|
}: Props) {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
|
const [mes, setMes] = useState<number | null>(null);
|
||||||
|
const [anio, setAnio] = useState<number>(
|
||||||
|
initialYear || currentYear
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generar rango automáticamente
|
||||||
|
useEffect(() => {
|
||||||
|
if (mes !== null && anio !== null) {
|
||||||
|
const inicio = new Date(anio, mes, 1);
|
||||||
|
const fin = new Date(anio, mes + 1, 0);
|
||||||
|
|
||||||
|
const formato = (fecha: Date) =>
|
||||||
|
fecha.toISOString().split("T")[0];
|
||||||
|
|
||||||
|
onChange(formato(inicio), formato(fin));
|
||||||
|
}
|
||||||
|
}, [mes, anio, onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-3 mb-5">
|
||||||
|
{/* MES */}
|
||||||
|
<select
|
||||||
|
className="border rounded-lg px-3 py-2"
|
||||||
|
onChange={(e) => setMes(Number(e.target.value))}
|
||||||
|
defaultValue=""
|
||||||
|
>
|
||||||
|
<option value="" disabled>
|
||||||
|
Selecciona mes
|
||||||
|
</option>
|
||||||
|
<option value={0}>Enero</option>
|
||||||
|
<option value={1}>Febrero</option>
|
||||||
|
<option value={2}>Marzo</option>
|
||||||
|
<option value={3}>Abril</option>
|
||||||
|
<option value={4}>Mayo</option>
|
||||||
|
<option value={5}>Junio</option>
|
||||||
|
<option value={6}>Julio</option>
|
||||||
|
<option value={7}>Agosto</option>
|
||||||
|
<option value={8}>Septiembre</option>
|
||||||
|
<option value={9}>Octubre</option>
|
||||||
|
<option value={10}>Noviembre</option>
|
||||||
|
<option value={11}>Diciembre</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select
|
||||||
|
className="border rounded-lg px-3 py-2"
|
||||||
|
value={anio}
|
||||||
|
onChange={(e) => setAnio(Number(e.target.value))}
|
||||||
|
>
|
||||||
|
{Array.from(
|
||||||
|
{ length: currentYear - 2000 + 1 },
|
||||||
|
(_, i) => 2000 + i
|
||||||
|
).map((year) => (
|
||||||
|
<option key={year} value={year}>
|
||||||
|
{year}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
interface Periodo {
|
||||||
|
id_periodo: number;
|
||||||
|
semestre: string;
|
||||||
|
fecha_inicio_servicio: string;
|
||||||
|
fecha_fin_servicio: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PeriodoPicker({
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
onChange: (p1: Periodo | null, p2: Periodo | null) => void;
|
||||||
|
}) {
|
||||||
|
const [periodos, setPeriodos] = useState<Periodo[]>([]);
|
||||||
|
const [p1, setP1] = useState<number | null>(null);
|
||||||
|
const [p2, setP2] = useState<number | null>(null);
|
||||||
|
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPeriodos = async () => {
|
||||||
|
const res = await axios.get(`${envConfig.apiUrl}/periodo`, { headers });
|
||||||
|
setPeriodos(res.data);
|
||||||
|
};
|
||||||
|
fetchPeriodos();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const periodo1 = periodos.find(p => p.id_periodo === p1) || null;
|
||||||
|
const periodo2 = periodos.find(p => p.id_periodo === p2) || null;
|
||||||
|
|
||||||
|
onChange(periodo1, periodo2);
|
||||||
|
}, [p1, p2]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-4">
|
||||||
|
{/* Periodo 1 */}
|
||||||
|
<select onChange={(e) => setP1(Number(e.target.value))}>
|
||||||
|
<option value="">Periodo 1</option>
|
||||||
|
{periodos.map(p => (
|
||||||
|
<option key={p.id_periodo} value={p.id_periodo}>
|
||||||
|
{p.semestre}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{/* Periodo 2 */}
|
||||||
|
<select onChange={(e) => setP2(Number(e.target.value))}>
|
||||||
|
<option value="">Periodo 2</option>
|
||||||
|
{periodos.map(p => (
|
||||||
|
<option key={p.id_periodo} value={p.id_periodo}>
|
||||||
|
{p.semestre}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,75 +1,64 @@
|
|||||||
interface Alumno {
|
"use client";
|
||||||
id_cuenta: number;
|
|
||||||
nombre: string;
|
|
||||||
credito: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Sancion {
|
import { envConfig } from "@/app/lib/config";
|
||||||
id_sancion: number;
|
import { useEffect, useState } from "react";
|
||||||
sancion: string;
|
import { GetSancionByStudent } from "@/app/lib/getSancionByStudent";
|
||||||
duracion: number;
|
import { Alumno, AlumnoSancion } from "@/app/types/sanction";
|
||||||
}
|
import TableSanction from "../BitacoraSanciones/TableSanction";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface AlumnoSancion {
|
function QuitarSancion({ id_cuenta }: { id_cuenta: number }) {
|
||||||
id_alumno_sancion: number;
|
const [alumno, setAlumno] = useState<Alumno>();
|
||||||
fecha_inicio: string;
|
const [alumnoSanciones, setAlumnoSanciones] = useState<AlumnoSancion[]>([]);
|
||||||
sancion: Sancion;
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
}
|
|
||||||
|
|
||||||
interface DataProps {
|
const fetchAlumnoSanciones = async () => {
|
||||||
student: Alumno;
|
try {
|
||||||
alusancion: AlumnoSancion[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
const res = await GetSancionByStudent(id_cuenta);
|
||||||
data: DataProps | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function QuitarSancion({ data }: Props) {
|
setAlumno(res.student ?? null);
|
||||||
const calcularFechaFin = (fechaInicio: string, duracionSemanas: number) => {
|
setAlumnoSanciones(res.alusancion ?? []);
|
||||||
const fecha = new Date(fechaInicio);
|
} catch {
|
||||||
fecha.setDate(fecha.getDate() + duracionSemanas * 7);
|
}
|
||||||
return fecha.toLocaleDateString();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id_cuenta) return;
|
||||||
|
|
||||||
|
fetchAlumnoSanciones();
|
||||||
|
|
||||||
|
}, [id_cuenta]);
|
||||||
|
|
||||||
|
const handleButton = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
await axios.delete(`${envConfig.apiUrl}/alumno-sancion/${id_cuenta}`, { headers })
|
||||||
|
setAlumnoSanciones([])
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="containerSection">
|
<section className="containerSection">
|
||||||
<div>
|
<TableSanction alumnoSanciones={alumnoSanciones} alumno={alumno} />
|
||||||
<table>
|
{(alumnoSanciones?.length > 0) &&
|
||||||
<thead>
|
<button
|
||||||
<tr>
|
className="button buttonSearch"
|
||||||
<th>ID</th>
|
style={{ margin: "1rem 0" }}
|
||||||
<th>Nombre</th>
|
onClick={handleButton}
|
||||||
<th>Motivo Sanción</th>
|
disabled={loading}
|
||||||
<th>Duración (semanas)</th>
|
>
|
||||||
<th>Fecha Sanción</th>
|
Quitar sancion
|
||||||
<th>Podra utilizar el servicio hasta</th>
|
</button>
|
||||||
</tr>
|
}
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data && data.alusancion.length !== 0 ? (
|
|
||||||
data.alusancion.map((item) => (
|
|
||||||
<tr key={item.id_alumno_sancion}>
|
|
||||||
<td>{item.id_alumno_sancion}</td>
|
|
||||||
<td>{data.student.nombre}</td>
|
|
||||||
<td>{item.sancion.sancion}</td>
|
|
||||||
<td>{item.sancion.duracion}</td>
|
|
||||||
<td>{new Date(item.fecha_inicio).toLocaleDateString()}</td>
|
|
||||||
<td>
|
|
||||||
{calcularFechaFin(item.fecha_inicio, item.sancion.duracion)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={6}>No hay sanciones registradas</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QuitarSancion;
|
export default QuitarSancion;
|
||||||
|
//IO
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import { envConfig } from "@/app/lib/config";
|
||||||
|
|
||||||
import Selection from "../Selection/Selection";
|
import Selection from "../Selection/Selection";
|
||||||
import { envConfig } from "@/app/lib/config";
|
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
import "./Receipt.css";
|
import "./Receipt.css";
|
||||||
import Swal from "sweetalert2";
|
|
||||||
|
|
||||||
interface ReceiptsProps {
|
interface ReceiptsProps {
|
||||||
numAcount: number | null;
|
numAcount: number | null;
|
||||||
@@ -33,11 +32,13 @@ export default function Inscripcion({
|
|||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const [folio, setFolio] = useState("");
|
const [folio, setFolio] = useState("");
|
||||||
const [amount, setAmount] = useState("");
|
const [amount, setAmount] = useState("");
|
||||||
const [lock, setLock] = useState<boolean>(true);
|
const [lock, setLock] = useState<boolean>(true);
|
||||||
const todayISO = new Date().toISOString().split("T")[0];
|
const todayISO = new Date().toLocaleDateString("en-CA");
|
||||||
const [date, setDate] = useState(todayISO);
|
const [date, setDate] = useState(todayISO);
|
||||||
|
|
||||||
//restrict this month//
|
//restrict this month//
|
||||||
@@ -46,33 +47,63 @@ export default function Inscripcion({
|
|||||||
const month = day.getMonth();
|
const month = day.getMonth();
|
||||||
const today = day.getDate();
|
const today = day.getDate();
|
||||||
|
|
||||||
const minFecha = new Date(year, month, 1).toISOString().split("T")[0];
|
let minMount = month
|
||||||
|
if(!lock){
|
||||||
|
minMount = month-1
|
||||||
|
}
|
||||||
|
|
||||||
|
const minFecha = new Date(year, minMount, 1).toISOString().split("T")[0];
|
||||||
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
|
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
|
||||||
//restrict this month//
|
//restrict this month//
|
||||||
|
|
||||||
const handleSaveReceipt = async () => {
|
const handleSaveReceipt = async () => {
|
||||||
if (!numAcount) {
|
if (numAcount == null) {
|
||||||
toast.error("busca de nuevo al estudiante");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "busca de nuevo al estudiante!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!folio) {
|
if (!folio) {
|
||||||
toast.error("Ingresa el folio del ticket");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Ingresa el folio del ticket!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!amount) {
|
if (!amount) {
|
||||||
toast.error("Coloca el monto a depositar");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Coloca el monto a depositar!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!date) {
|
if (!date) {
|
||||||
toast.error("Coloca la fecha");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Coloca la fecha!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!plataformaSeleccionada) {
|
if (!plataformaSeleccionada) {
|
||||||
toast.error("Selecciona una plataforma");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Selecciona una plataforma!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,58 +126,88 @@ export default function Inscripcion({
|
|||||||
{ headers },
|
{ headers },
|
||||||
);
|
);
|
||||||
|
|
||||||
toast.success("Alumno con pago");
|
Swal.fire({
|
||||||
Swal.fire("Alumno con pago");
|
title: "Alumno inscrito con pago",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
setFolio("");
|
setFolio("");
|
||||||
setAmount("");
|
setAmount("");
|
||||||
setDate(todayISO);
|
setDate(todayISO);
|
||||||
|
|
||||||
router.refresh();
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
} catch (err: any) {
|
params.delete("numAcount");
|
||||||
toast.error(String(err));
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
const msg =
|
||||||
|
error.response?.data?.message ||
|
||||||
|
error.message ||
|
||||||
|
"Error desconocido al crear recibo";
|
||||||
|
Swal.fire({
|
||||||
|
title: msg,
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInscripcionSinPago = async () => {
|
const handleInscripcionSinPago = async () => {
|
||||||
if (!numAcount) {
|
if (numAcount == null) {
|
||||||
toast.error("busca de nuevo al estudiante");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "busca de nuevo al estudiante!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!plataformaSeleccionada) {
|
if (!plataformaSeleccionada) {
|
||||||
toast.error("Selecciona una plataforma");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Selecciona una plataforma!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id_plataforma = PLATAFORMA_MAP[plataformaSeleccionada];
|
const id_plataforma = PLATAFORMA_MAP[plataformaSeleccionada];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${envConfig.apiUrl}/alumno-inscrito`, {
|
|
||||||
method: "POST",
|
const token = Cookies.get("token");
|
||||||
headers: { "Content-Type": "application/json" },
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
body: JSON.stringify({
|
|
||||||
|
await axios.post(
|
||||||
|
`${envConfig.apiUrl}/alumno-inscrito`,
|
||||||
|
{
|
||||||
id_cuenta: numAcount,
|
id_cuenta: numAcount,
|
||||||
id_plataforma,
|
id_plataforma,
|
||||||
realizo_pago: false,
|
realizo_pago: false,
|
||||||
}),
|
},
|
||||||
});
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
Swal.fire("Error");
|
|
||||||
throw new Error(data.message || "Error al inscribir");
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success("Alumno inscrito sin pago");
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Alumno inscrito sin pago!",
|
title: "Alumno inscrito sin pago!",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
draggable: true
|
draggable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
router.refresh();
|
router.refresh();
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast.error(err.message);
|
|
||||||
|
const message =
|
||||||
|
err.response?.data?.message || "Error al inscribir";
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: message,
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,7 +222,6 @@ export default function Inscripcion({
|
|||||||
value={conPago}
|
value={conPago}
|
||||||
onChange={(e) => setConPago(e.target.value as "con" | "sin")}
|
onChange={(e) => setConPago(e.target.value as "con" | "sin")}
|
||||||
style={{
|
style={{
|
||||||
marginBottom: "1rem",
|
|
||||||
maxWidth: "120px",
|
maxWidth: "120px",
|
||||||
minWidth: "120px",
|
minWidth: "120px",
|
||||||
}}
|
}}
|
||||||
@@ -206,7 +266,25 @@ export default function Inscripcion({
|
|||||||
const numericValue = parseFloat(value);
|
const numericValue = parseFloat(value);
|
||||||
|
|
||||||
if (lock && numericValue > 1000) {
|
if (lock && numericValue > 1000) {
|
||||||
toast.error("El monto no puede superar $1000.00");
|
|
||||||
|
Swal.fire({
|
||||||
|
title:
|
||||||
|
"El monto no puede superar $1000.00 Desbloquea el candado !",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (numericValue > 10000) {
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title:
|
||||||
|
"El monto no puede superar $10000.00 pesos",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,16 +307,16 @@ export default function Inscripcion({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="containerButton" style={{ position: 'relative' }}>
|
<div className="containerButton" style={{ position: "relative" }}>
|
||||||
|
<button className="button buttonSearch" disabled={!plataformaSeleccionada}
|
||||||
<button className="button buttonSearch">Inscribir</button>
|
>Inscribir</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`button buttonCancel ${lock ? "buttonLock" : "buttonOpenLock"
|
className={`button buttonCancel ${lock ? "buttonLock" : "buttonOpenLock"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLock((prev) => !prev)
|
setLock((prev) => !prev);
|
||||||
setAmount('');
|
setAmount("");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -246,7 +324,6 @@ export default function Inscripcion({
|
|||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
{conPago === "sin" && (
|
{conPago === "sin" && (
|
||||||
|
|
||||||
<div className="containerButton">
|
<div className="containerButton">
|
||||||
<button
|
<button
|
||||||
className="button buttonSearch"
|
className="button buttonSearch"
|
||||||
|
|||||||
@@ -23,21 +23,21 @@
|
|||||||
background-color: #cfcfcf;
|
background-color: #cfcfcf;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonLock{
|
.buttonLock {
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 100px;
|
||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonOpenLock{
|
.buttonOpenLock {
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 100px;
|
||||||
background-color: #8cd32f;
|
background-color: #8cd32f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonOpenLock:hover{
|
.buttonOpenLock:hover {
|
||||||
background-color: #78b32c;
|
background-color: #78b32c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,4 +61,10 @@
|
|||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-height:800px) {
|
||||||
|
.buttonLock {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { PostReceipt } from "@/app/lib/postReceipt";
|
import { PostReceipt } from "@/app/lib/postReceipt";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import "./Receipt.css";
|
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
import "./Receipt.css";
|
||||||
|
|
||||||
interface ReceiptsProps {
|
interface ReceiptsProps {
|
||||||
numAcount: number | null;
|
numAcount: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Receipt({ numAcount }: ReceiptsProps) {
|
export default function Receipt({ numAcount }: ReceiptsProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [folio, setFolio] = useState("");
|
const [folio, setFolio] = useState("");
|
||||||
const [amount, setAmount] = useState("");
|
const [amount, setAmount] = useState("");
|
||||||
const [lock, setLock] = useState<boolean>(true);
|
const [lock, setLock] = useState<boolean>(true);
|
||||||
const todayISO = new Date().toISOString().split("T")[0];
|
const todayISO = new Date().toLocaleDateString("en-CA");
|
||||||
const [date, setDate] = useState(todayISO);
|
const [date, setDate] = useState(todayISO);
|
||||||
|
|
||||||
//restrict this month//
|
//restrict this month//
|
||||||
@@ -27,28 +26,53 @@ function Receipt({ numAcount }: ReceiptsProps) {
|
|||||||
const month = day.getMonth();
|
const month = day.getMonth();
|
||||||
const today = day.getDate();
|
const today = day.getDate();
|
||||||
|
|
||||||
const minFecha = new Date(year, month, 1).toISOString().split("T")[0];
|
let minMount = month
|
||||||
|
if(!lock){
|
||||||
|
minMount = month-1
|
||||||
|
}
|
||||||
|
|
||||||
|
const minFecha = new Date(year, minMount, 1).toISOString().split("T")[0];
|
||||||
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
|
const maxFecha = new Date(year, month, today).toISOString().split("T")[0];
|
||||||
//restrict this month//
|
//restrict this month//
|
||||||
|
|
||||||
const handleSaveReceipt = async () => {
|
const handleSaveReceipt = async () => {
|
||||||
if (!numAcount) {
|
if (numAcount == null) {
|
||||||
toast.error("busca de nuevo al estudiante");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "busca de nuevo al estudiante!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!folio) {
|
if (!folio) {
|
||||||
toast.error("Ingresa el folio del ticket");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Ingresa el folio del ticket!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!amount) {
|
if (!amount) {
|
||||||
toast.error("Coloca el monto a depositar");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Coloca el monto a depositar!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!date) {
|
if (!date) {
|
||||||
toast.error("Coloca la fecha");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Coloca la fecha!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,16 +88,20 @@ function Receipt({ numAcount }: ReceiptsProps) {
|
|||||||
setAmount("");
|
setAmount("");
|
||||||
setDate(todayISO);
|
setDate(todayISO);
|
||||||
|
|
||||||
toast.success("Recibo guardado");
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Recibo Guardado!",
|
title: "Recibo Guardado Correctamente!",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
draggable: true
|
draggable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
router.refresh();
|
router.refresh();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast.error(String(err));
|
Swal.fire({
|
||||||
|
title: err,
|
||||||
|
icon: "error",
|
||||||
|
draggable: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,7 +142,24 @@ function Receipt({ numAcount }: ReceiptsProps) {
|
|||||||
const numericValue = parseFloat(value);
|
const numericValue = parseFloat(value);
|
||||||
|
|
||||||
if (lock && numericValue > 1000) {
|
if (lock && numericValue > 1000) {
|
||||||
toast.error("El monto no puede superar $1000.00");
|
|
||||||
|
Swal.fire({
|
||||||
|
title:
|
||||||
|
"El monto no puede superar $1000.00 Desbloquea el candado !",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numericValue > 10000) {
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title:
|
||||||
|
"El monto no puede superar $10000.00 pesos",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +199,4 @@ function Receipt({ numAcount }: ReceiptsProps) {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Receipt;
|
|
||||||
//IO
|
//IO
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import DownloadReporteXLSX from "../Dowload/Reporte";
|
import DownloadReporteXLSX from "../Dowload/Reporte";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface Recibo {
|
interface Recibo {
|
||||||
id_recibo: number;
|
id_recibo: number;
|
||||||
@@ -20,19 +21,31 @@ interface Props {
|
|||||||
hasta: string | null;
|
hasta: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PorRecibos({ desde, hasta }: Props) {
|
export default function PorRecibos({ desde, hasta }: Props) {
|
||||||
const [recibos, setRecibos] = useState<Recibo[]>([]);
|
const [recibos, setRecibos] = useState<Recibo[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ header: "Folio", key: "folio", width: 15 },
|
||||||
|
{ header: "Monto", key: "monto", width: 15 },
|
||||||
|
{ header: "Fecha Recibo", key: "fechaRecibo", width: 18 },
|
||||||
|
{ header: "Fecha Registro", key: "fechaRegistro", width: 22 },
|
||||||
|
{ header: "Usuario", key: "usuario", width: 20 },
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!desde || !hasta) return;
|
if (!desde || !hasta) return;
|
||||||
|
|
||||||
const fetchRecibos = async () => {
|
const fetchRecibos = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
const res = await axios.post(
|
const res = await axios.post(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/recibo/rango`,
|
`${process.env.NEXT_PUBLIC_API_URL}/recibo/rango`,
|
||||||
{ desde, hasta }
|
{ desde, hasta },
|
||||||
|
{ headers },
|
||||||
);
|
);
|
||||||
|
|
||||||
setRecibos(res.data);
|
setRecibos(res.data);
|
||||||
@@ -61,7 +74,8 @@ function PorRecibos({ desde, hasta }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
{recibos.length > 0 && <DownloadReporteXLSX />}
|
{recibos.length > 0 && <DownloadReporteXLSX data={recibos} columns={columns} />}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
@@ -105,5 +119,4 @@ function PorRecibos({ desde, hasta }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
export default PorRecibos;
|
|
||||||
@@ -2,85 +2,271 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import DownloadReporteXLSX from "../Dowload/Reporte";
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
import {
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Line,
|
||||||
|
LineChart,
|
||||||
|
} from "recharts";
|
||||||
|
|
||||||
interface ServicioReporte {
|
interface Periodo {
|
||||||
id_servicio: number;
|
id_periodo: number;
|
||||||
servicio: string;
|
semestre: string;
|
||||||
total: string;
|
fecha_inicio_servicio: string;
|
||||||
|
fecha_fin_servicio: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
desde: string | null;
|
periodo1: Periodo | null;
|
||||||
hasta: string | null;
|
periodo2: Periodo | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PorServicio({ desde, hasta }: Props) {
|
export default function PorServicio({ periodo1, periodo2 }: Props) {
|
||||||
const [servicios, setServicios] = useState<ServicioReporte[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
|
const [periodos, setPeriodos] = useState<Periodo[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [serviciosSeleccionados, setServiciosSeleccionados] = useState<string[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!desde || !hasta) return;
|
if (!periodo1 || !periodo2) return;
|
||||||
|
|
||||||
const fetchServicios = async () => {
|
const fetchServicios = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.post(
|
const token = Cookies.get("token");
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/detalle-servicio/rango`,
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
{ desde, hasta }
|
|
||||||
|
// 🔥 1. traer TODOS los periodos en rango
|
||||||
|
const resPeriodos = await axios.get(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/periodo/range?p1=${periodo1.id_periodo}&p2=${periodo2.id_periodo}`,
|
||||||
|
{ headers }
|
||||||
);
|
);
|
||||||
|
|
||||||
setServicios(res.data);
|
const periodosRango = resPeriodos.data;
|
||||||
|
setPeriodos(periodosRango);
|
||||||
|
|
||||||
|
// 🔥 2. hacer requests por cada periodo
|
||||||
|
const resultados = await Promise.all(
|
||||||
|
periodosRango.map((p: Periodo) =>
|
||||||
|
axios.post(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/detalle-servicio/rango`,
|
||||||
|
{
|
||||||
|
desde: p.fecha_inicio_servicio,
|
||||||
|
hasta: p.fecha_fin_servicio,
|
||||||
|
},
|
||||||
|
{ headers }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 🔥 3. merge dinámico
|
||||||
|
const map = new Map();
|
||||||
|
|
||||||
|
periodosRango.forEach((p: Periodo, index: number) => {
|
||||||
|
resultados[index].data.forEach((item: any) => {
|
||||||
|
if (!map.has(item.servicio)) {
|
||||||
|
map.set(item.servicio, {
|
||||||
|
servicio: item.servicio,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
map.get(item.servicio)[p.semestre] = Number(item.total);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalData = Array.from(map.values());
|
||||||
|
setData(finalData);
|
||||||
|
setServiciosSeleccionados(finalData.map((item: any) => item.servicio));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error al obtener reporte por servicio", error);
|
console.error("Error comparando servicios", error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchServicios();
|
fetchServicios();
|
||||||
}, [desde, hasta]);
|
}, [periodo1, periodo2]);
|
||||||
|
|
||||||
|
// 🔥 TRANSFORMAR DATA PARA GRAFICA
|
||||||
|
const transformDataForChart = () => {
|
||||||
|
if (!data.length || !periodos.length) return [];
|
||||||
|
|
||||||
|
return periodos.map((p) => {
|
||||||
|
const row: any = { periodo: p.semestre };
|
||||||
|
|
||||||
|
data.forEach((servicio) => {
|
||||||
|
row[servicio.servicio] = servicio[p.semestre] || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// FORMATO DE NUMEROS
|
||||||
|
const formatNumber = (value: number) => {
|
||||||
|
return value.toLocaleString("en-US", {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// TOOLTIP ORDENADO
|
||||||
|
const CustomTooltip = ({ active, payload, label }: any) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
const sorted = [...payload]
|
||||||
|
.filter((item) => item.value > 0)
|
||||||
|
.sort((a, b) => b.value - a.value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: "white",
|
||||||
|
padding: "10px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ fontWeight: "bold" }}>
|
||||||
|
Periodo: {label}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{sorted.map((entry: any, index: number) => (
|
||||||
|
<div key={index} style={{ color: entry.color }}>
|
||||||
|
{entry.name}: ${formatNumber(entry.value)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartData = transformDataForChart();
|
||||||
|
|
||||||
|
const colors = ["black", "red", "blue", "orange", "purple", "green"];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: "30px" }}>
|
||||||
{servicios.length > 0 && <DownloadReporteXLSX />}
|
|
||||||
|
|
||||||
<div
|
{/* TABLA */}
|
||||||
style={{
|
<div style={{ overflow: "auto", maxHeight: "300px" }}>
|
||||||
overflow: "auto",
|
|
||||||
height: "270px",
|
|
||||||
scrollbarColor: "#2563eb white",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{loading && <p>Cargando...</p>}
|
{loading && <p>Cargando...</p>}
|
||||||
|
|
||||||
<table>
|
<table style={{ width: "100%" }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Servicio</th>
|
<th>Servicio</th>
|
||||||
<th>Monto total</th>
|
|
||||||
|
{periodos.map((p) => (
|
||||||
|
<th key={p.id_periodo}>{p.semestre}</th>
|
||||||
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{!loading && servicios.length === 0 && (
|
{!loading && data.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={2}>No hay servicios en este rango</td>
|
<td colSpan={periodos.length + 1}>
|
||||||
|
No hay datos en este rango
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{servicios.map((servicio) => (
|
{data.map((row) => (
|
||||||
<tr key={servicio.id_servicio}>
|
<tr key={row.servicio}>
|
||||||
<td>{servicio.servicio}</td>
|
<td>{row.servicio}</td>
|
||||||
<td>${Number(servicio.total).toFixed(2)}</td>
|
|
||||||
|
{periodos.map((p) => (
|
||||||
|
<td key={p.id_periodo}>
|
||||||
|
${formatNumber(row[p.semestre] || 0)}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: "10px",
|
||||||
|
marginBottom: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.map((servicio) => (
|
||||||
|
<label
|
||||||
|
key={servicio.servicio}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={serviciosSeleccionados.includes(servicio.servicio)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setServiciosSeleccionados((prev) => [
|
||||||
|
...prev,
|
||||||
|
servicio.servicio,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
setServiciosSeleccionados((prev) =>
|
||||||
|
prev.filter((s) => s !== servicio.servicio)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{servicio.servicio}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* GRAFICA */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minWidth: "700px",
|
||||||
|
background: "white",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chartData.length > 0 && (
|
||||||
|
<ResponsiveContainer width="100%" height={400}>
|
||||||
|
<LineChart data={chartData}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis dataKey="periodo" />
|
||||||
|
<YAxis tickFormatter={(value) => value.toLocaleString()} />
|
||||||
|
|
||||||
|
<Tooltip content={<CustomTooltip />} />
|
||||||
|
|
||||||
|
<Legend />
|
||||||
|
|
||||||
|
{data
|
||||||
|
.filter((servicio) =>
|
||||||
|
serviciosSeleccionados.includes(servicio.servicio)
|
||||||
|
)
|
||||||
|
.map((servicio, i) => (
|
||||||
|
<Line
|
||||||
|
key={servicio.servicio}
|
||||||
|
dataKey={servicio.servicio}
|
||||||
|
stroke={colors[i % colors.length]}
|
||||||
|
type="monotone"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PorServicio;
|
|
||||||
@@ -1,29 +1,49 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
function SearchDate() {
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
const [date, setDate] = useState("");
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export default function SearchDate() {
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const todayISO = new Date().toLocaleDateString("en-CA");
|
||||||
|
const [date, setDate] = useState(todayISO);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const dateParam = searchParams.get("date");
|
||||||
|
if (dateParam) {
|
||||||
|
setDate(dateParam);
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
|
||||||
|
if (date) {
|
||||||
|
params.set("date", date);
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<form className="containerForm" onSubmit={handleSubmit}>
|
||||||
<form className="containerForm">
|
<label className="label">Fecha</label>
|
||||||
<label className="label">Fecha</label>
|
<div className="groupInput">
|
||||||
<div className="groupInput">
|
<input
|
||||||
<input
|
type="date"
|
||||||
type="date"
|
value={date}
|
||||||
value={date}
|
onChange={(e) => setDate(e.target.value)}
|
||||||
onChange={(e) => setDate(e.target.value)}
|
/>
|
||||||
placeholder="Selecciona una fecha..."
|
<button className="button buttonSearch" type="submit">
|
||||||
/>
|
Buscar
|
||||||
<button className="button buttonSearch" type="submit">
|
</button>
|
||||||
Buscar
|
</div>
|
||||||
</button>
|
</form>
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SearchDate;
|
|
||||||
|
|
||||||
//By Tyrannuss
|
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ interface Props {
|
|||||||
onSearch: (desde: string, hasta: string) => void;
|
onSearch: (desde: string, hasta: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SearchDateBetween({ onSearch }: Props) {
|
export default function SearchDateBetween({ onSearch }: Props) {
|
||||||
const [startDate, setStartDate] = useState("");
|
const [startDate, setStartDate] = useState("");
|
||||||
const [endDate, setEndDate] = useState("");
|
const todayISO = new Date().toLocaleDateString("en-CA");
|
||||||
|
const [endDate, setEndDate] = useState(todayISO);
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -54,5 +55,4 @@ function SearchDateBetween({ onSearch }: Props) {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
export default SearchDateBetween;
|
|
||||||
@@ -1,63 +1,28 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import axios from "axios";
|
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface Mesa {
|
interface urlProp {
|
||||||
id_mesa: number;
|
value: string | null;
|
||||||
nombre?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
export default function SearchMesa(props: urlProp) {
|
||||||
id_cuenta: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Mesas({ id_cuenta }: Props) {
|
|
||||||
const [mesas, setMesas] = useState<Mesa[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!id_cuenta) return;
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
axios
|
|
||||||
.get(`/cuenta/${id_cuenta}`)
|
|
||||||
.then((res) => {
|
|
||||||
setMesas(res.data);
|
|
||||||
})
|
|
||||||
.catch((err) => console.error(err))
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}, [id_cuenta]);
|
|
||||||
|
|
||||||
if (loading) return <p>Cargando mesas...</p>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3>Mesas asignadas</h3>
|
|
||||||
|
|
||||||
{mesas.length === 0 ? (
|
|
||||||
<p>No hay mesas</p>
|
|
||||||
) : (
|
|
||||||
<ul>
|
|
||||||
{mesas.map((mesa) => (
|
|
||||||
<li key={mesa.id_mesa}>
|
|
||||||
Mesa {mesa.id_mesa} {mesa.nombre && `- ${mesa.nombre}`}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SearchMesa() {
|
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [idCuenta, setIdCuenta] = useState<number | null>(null);
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.value) {
|
||||||
|
setInputValue(props.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(props.value===null){
|
||||||
|
setInputValue("")
|
||||||
|
}
|
||||||
|
}, [props.value]);
|
||||||
|
|
||||||
const buscar = (e: React.FormEvent) => {
|
const buscar = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
@@ -74,7 +39,7 @@ export default function SearchMesa() {
|
|||||||
|
|
||||||
<div className="groupInput">
|
<div className="groupInput">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="text"
|
||||||
placeholder="Coloca el numero de mesa"
|
placeholder="Coloca el numero de mesa"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import axios from "axios";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { envConfig } from "../lib/config";
|
import { envConfig } from "../lib/config";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
if (!envConfig.apiUrl) {
|
if (!envConfig.apiUrl) {
|
||||||
console.error("API URL is not defined in envConfig");
|
console.error("API URL is not defined in envConfig");
|
||||||
@@ -15,9 +16,14 @@ export default function SelectAreas(props: proms) {
|
|||||||
const [areas, setAreas] = useState([]);
|
const [areas, setAreas] = useState([]);
|
||||||
const [selectedArea, setSelectedArea] = useState("");
|
const [selectedArea, setSelectedArea] = useState("");
|
||||||
|
|
||||||
|
const token = Cookies.get("token");
|
||||||
|
const headers = { Authorization: `Bearer ${token}` };
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios
|
axios
|
||||||
.get(`${envConfig.apiUrl}/area-ubicacion`)
|
.get(`${envConfig.apiUrl}/area-ubicacion`, { headers }
|
||||||
|
)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setAreas(data.data);
|
setAreas(data.data);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectionItem {
|
.selectionItem {
|
||||||
@@ -14,6 +15,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.buttonSelection {
|
.buttonSelection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
@@ -23,6 +28,13 @@
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.imageSelection {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
position: relative;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
.buttonSelection:hover {
|
.buttonSelection:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
background-color: #d0e1ff;
|
background-color: #d0e1ff;
|
||||||
@@ -40,3 +52,31 @@
|
|||||||
color: #333;
|
color: #333;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media(max-height:550px) {
|
||||||
|
.buttonLabel {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-height:550px) {
|
||||||
|
.selection {
|
||||||
|
margin-bottom: 0rem;
|
||||||
|
gap: 2rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonSelection {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageSelection {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
import "./Selection.css";
|
import "./Selection.css";
|
||||||
|
|
||||||
function Selection({
|
export default function Selection({
|
||||||
plataformasInscritas = [],
|
plataformasInscritas = [],
|
||||||
onSelect,
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
@@ -24,11 +25,11 @@ function Selection({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MACINTOSH",
|
name: "MACINTOSH",
|
||||||
img: "apple.png",
|
img: "/apple.png",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PROFESORES",
|
name: "PROFESORES",
|
||||||
img: "teacher.png",
|
img: "/teacher.png",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -45,16 +46,16 @@ function Selection({
|
|||||||
onClick={() => handleSelect(option.name)}
|
onClick={() => handleSelect(option.name)}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className={`buttonSelection ${
|
className={`buttonSelection ${selected === option.name ? "active" : ""
|
||||||
selected === option.name ? "active" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<img
|
<div className="imageSelection">
|
||||||
src={option.img}
|
<Image
|
||||||
alt={option.name.toLowerCase()}
|
src={option.img}
|
||||||
height={30}
|
alt={option.name.toLowerCase()}
|
||||||
width={30}
|
fill
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<span className="buttonLabel">{option.name}</span>
|
<span className="buttonLabel">{option.name}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,5 +69,4 @@ function Selection({
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
export default Selection;
|
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import "./Selection.css";
|
import "./Selection.css";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
function SelectionCo({
|
export default function SelectionCo({
|
||||||
plataformasInscritas = [],
|
plataformasInscritas = [],
|
||||||
onSelect,
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
@@ -19,11 +20,11 @@ function SelectionCo({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MACINTOSH",
|
name: "MACINTOSH",
|
||||||
img: "apple.png",
|
img: "/apple.png",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PROFESORES",
|
name: "PROFESORES",
|
||||||
img: "teacher.png",
|
img: "/teacher.png",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -46,16 +47,16 @@ function SelectionCo({
|
|||||||
onClick={() => handleSelect(option.name)}
|
onClick={() => handleSelect(option.name)}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className={`buttonSelection ${
|
className={`buttonSelection ${selected === option.name ? "active" : ""
|
||||||
selected === option.name ? "active" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<img
|
<div className="imageSelection">
|
||||||
src={option.img}
|
<Image
|
||||||
alt={option.name.toLowerCase()}
|
src={option.img}
|
||||||
height={30}
|
alt={option.name.toLowerCase()}
|
||||||
width={30}
|
fill
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<span className="buttonLabel">{option.name}</span>
|
<span className="buttonLabel">{option.name}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,5 +70,4 @@ function SelectionCo({
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//IO
|
||||||
export default SelectionCo;
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import apiClient from "@/app/lib/apiClient";
|
import apiClient from "@/app/lib/apiClient";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
export default function ChangePassword() {
|
export default function ChangePassword() {
|
||||||
const [password, setPass] = useState("");
|
const [password, setPass] = useState("");
|
||||||
@@ -12,16 +12,26 @@ export default function ChangePassword() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const data = { password, newPassword };
|
const data = { password, newPassword };
|
||||||
|
|
||||||
try{
|
try {
|
||||||
const response = await apiClient.post("/user/changePassword", data);
|
const response = await apiClient.post("/user/changePassword", data);
|
||||||
toast.success( `${response.data.message}`)
|
Swal.fire({
|
||||||
|
title: response.data.message,
|
||||||
|
icon: "success",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
setPass('');
|
setPass('');
|
||||||
setNewPass('');
|
setNewPass('');
|
||||||
setconfirmNewPass('');
|
setconfirmNewPass('');
|
||||||
}catch{
|
} catch {
|
||||||
toast.error( `No es la contraseña actual`)
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "No es tu contraseña actual!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -85,4 +95,3 @@ export default function ChangePassword() {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//IO
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { loginUser } from "@/app/lib/login";
|
import { loginUser } from "@/app/lib/login";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import toast from "react-hot-toast";
|
import Swal from "sweetalert2";
|
||||||
import "./Login.css";
|
import "./Login.css";
|
||||||
|
|
||||||
function Login() {
|
export default function Login() {
|
||||||
const [user, setUser] = useState("");
|
const [user, setUser] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
@@ -16,30 +17,46 @@ function Login() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
toast.error("Coloca un usuario");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Coloca un usuario!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
toast.error("Coloca una contraseña");
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Coloca una contraseña!",
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await loginUser(user, password);
|
const data = await loginUser(user, password);
|
||||||
|
|
||||||
if ("error" in data) {
|
if ("error" in data) {
|
||||||
toast.error(data.error);
|
Swal.fire({
|
||||||
|
title: data.error,
|
||||||
|
icon: "error",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const token = data.access_token;
|
const token = data.access_token;
|
||||||
const payload = JSON.parse(atob(token.split(".")[1]));
|
const payload = JSON.parse(atob(token.split(".")[1]));
|
||||||
const usuario = payload.usuario;
|
const usuario = payload.usuario;
|
||||||
const role = payload.role;
|
|
||||||
|
|
||||||
document.cookie = `token=${token}; path=/; SameSite=Strict`;
|
document.cookie = `token=${token}; path=/; SameSite=Strict`;
|
||||||
document.cookie = `user=${usuario}; path=/; SameSite=Strict`;
|
document.cookie = `user=${usuario}; path=/; SameSite=Strict`;
|
||||||
document.cookie = `role=${role}; path=/; SameSite=Strict`;
|
|
||||||
|
|
||||||
toast.success("Inicio de sesión exitoso");
|
Swal.fire({
|
||||||
|
title: "Inicio de sesión exitoso!",
|
||||||
|
icon: "success",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
router.push("/Impresiones");
|
router.push("/Impresiones");
|
||||||
router.refresh();
|
router.refresh();
|
||||||
}
|
}
|
||||||
@@ -85,6 +102,4 @@ function Login() {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Login;
|
|
||||||
//IO
|
//IO
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
background-color: #d12020;
|
background-color: #d12020;
|
||||||
}
|
}
|
||||||
|
|
||||||
.session{
|
.session {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
@@ -19,11 +19,19 @@
|
|||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userName{
|
.userName {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
outline: 1px solid #cfcfcf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-height: 800px) {
|
||||||
|
.session {
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
@@ -57,16 +65,17 @@
|
|||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.userName{
|
.userName {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
right: 50px;
|
right: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media(max-width:450px){
|
|
||||||
.userName{
|
@media(max-width:450px) {
|
||||||
|
.userName {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
.backgroundContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 725px;
|
||||||
|
height: 615px;
|
||||||
|
z-index: -1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgImage {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
transition: opacity 2s ease-in-out;
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgImage.fade {
|
||||||
|
z-index: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgImage.fade.active {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgImage::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(to right, #f9f9f9, rgba(128, 128, 128, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
|
||||||
|
.backgroundContainer {
|
||||||
|
background-image: url("/rock.jpg");
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
width: 600px;
|
||||||
|
height: 650px;
|
||||||
|
right: 50%;
|
||||||
|
transform: translateX(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgImage {
|
||||||
|
display: none;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backgroundContainer::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(to right, #f9f9f9, rgba(128, 128, 128, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
.backgroundContainer::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(to left, #f9f9f9, rgba(128, 128, 128, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import "./Background.css";
|
||||||
|
|
||||||
|
const images = [
|
||||||
|
"/rock2.jpg",
|
||||||
|
"/CEDETEC.jpg",
|
||||||
|
"/FES-ACATLAN.jpg",
|
||||||
|
"/DSC.jpeg",
|
||||||
|
"/Cedetec.jpeg",
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function BackgroundRotator() {
|
||||||
|
const [current, setCurrent] = useState(0);
|
||||||
|
const [next, setNext] = useState(1);
|
||||||
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setIsTransitioning(true);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setCurrent(next);
|
||||||
|
setNext((next + 1) % images.length);
|
||||||
|
setIsTransitioning(false);
|
||||||
|
}, 2000);
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [next]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="backgroundContainer">
|
||||||
|
<div
|
||||||
|
className="bgImage"
|
||||||
|
style={{ backgroundImage: `url(${images[current]})` }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`bgImage fade ${isTransitioning ? "active" : ""}`}
|
||||||
|
style={{ backgroundImage: `url(${images[next]})` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
transition:
|
transition:
|
||||||
background-color 0.3s ease,
|
background-color 0.3s ease,
|
||||||
border 0.3s ease;
|
border 0.3s ease;
|
||||||
font-size: 1.5rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.barNavigation li:hover {
|
.barNavigation li:hover {
|
||||||
@@ -107,15 +107,28 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #545454;
|
color: #545454;
|
||||||
font-size: 1.5rem;
|
font-size: 1.4rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subMenu:hover > span,
|
.subMenu:hover>span,
|
||||||
.subMenu > a:hover > span {
|
.subMenu>a:hover>span {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subMenu.active span {
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.subMenu.active {
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
.containerLinks {
|
.containerLinks {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
@@ -129,7 +142,7 @@
|
|||||||
color: white;
|
color: white;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 1.5rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead,
|
thead,
|
||||||
@@ -137,6 +150,12 @@ tbody {
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media(max-height:550px) {
|
||||||
|
.barNavigation {
|
||||||
|
max-height: 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 1001px) {
|
@media (min-width: 1001px) {
|
||||||
.subMenu:hover ul {
|
.subMenu:hover ul {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -144,7 +163,7 @@ tbody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width: 1100px) {
|
||||||
.menuToggle {
|
.menuToggle {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -158,7 +177,7 @@ tbody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.barNavigation {
|
.barNavigation {
|
||||||
font-size: 15px;
|
font-size: 1.4rem;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +185,7 @@ tbody {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 76px;
|
top: 76px;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
top: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -235,13 +255,13 @@ tbody {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.subMenu.open ul a{
|
.subMenu.open ul a {
|
||||||
display: flex;
|
display: flex;
|
||||||
pointer-events:auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead,
|
thead,
|
||||||
tbody {
|
tbody {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,21 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
import "./BarNavigation.css";
|
import "./BarNavigation.css";
|
||||||
|
|
||||||
export default function BarNavigation() {
|
export default function BarNavigation() {
|
||||||
const [openMenu, setOpenMenu] = useState(false);
|
const [openMenu, setOpenMenu] = useState(false);
|
||||||
const [openSubMenu, setOpenSubMenu] = useState<number | null>(null);
|
const [openSubMenu, setOpenSubMenu] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const isActive = (paths: string[]) => {
|
||||||
|
return paths.some((path) => pathname.startsWith(path));
|
||||||
|
};
|
||||||
|
|
||||||
const toggleMenu = () => setOpenMenu(!openMenu);
|
const toggleMenu = () => setOpenMenu(!openMenu);
|
||||||
const toggleSubMenu = (index: number) => {
|
const toggleSubMenu = (index: number) => {
|
||||||
if (typeof window !== "undefined" && window.innerWidth <= 1000) {
|
if (typeof window !== "undefined" && window.innerWidth <= 1000) {
|
||||||
@@ -28,8 +37,20 @@ export default function BarNavigation() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className={openMenu ? "active" : ""}>
|
<ul className={openMenu ? "active" : ""}>
|
||||||
<li className={`subMenu ${openSubMenu === 0 ? "open" : ""}`}>
|
<li
|
||||||
<span onClick={() => toggleSubMenu(0)}>Inscripciones</span>
|
className={`subMenu
|
||||||
|
${openSubMenu === 0 ? "open" : ""}
|
||||||
|
${isActive(["/Inscripcion", "/AgregarTiempo"]) ? "active" : ""}
|
||||||
|
`}>
|
||||||
|
<span onClick={() => toggleSubMenu(0)}>
|
||||||
|
<Image
|
||||||
|
src="/user-plus.svg"
|
||||||
|
alt="Logo FES"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
color="#545454"
|
||||||
|
/>
|
||||||
|
Inscripciones</span>
|
||||||
<ul onClick={toggleMenu}>
|
<ul onClick={toggleMenu}>
|
||||||
<Link
|
<Link
|
||||||
href="/Inscripcion"
|
href="/Inscripcion"
|
||||||
@@ -54,8 +75,20 @@ export default function BarNavigation() {
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className={`subMenu ${openSubMenu === 1 ? "open" : ""}`}>
|
<li
|
||||||
<span onClick={() => toggleSubMenu(1)}>Servicios</span>
|
className={`subMenu
|
||||||
|
${openSubMenu === 1 ? "open" : ""}
|
||||||
|
${isActive(["/Impresiones", "/AsignacionMesas", "/AsignacionEquipo", "/Monitor"]) ? "active" : ""}
|
||||||
|
`}>
|
||||||
|
<span onClick={() => toggleSubMenu(1)}>
|
||||||
|
<Image
|
||||||
|
src="/briefcase.svg"
|
||||||
|
alt="Logo FES"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
color="#545454"
|
||||||
|
/>
|
||||||
|
Servicios</span>
|
||||||
<ul onClick={toggleMenu}>
|
<ul onClick={toggleMenu}>
|
||||||
<Link
|
<Link
|
||||||
href="/Impresiones"
|
href="/Impresiones"
|
||||||
@@ -100,8 +133,20 @@ export default function BarNavigation() {
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className={`subMenu ${openSubMenu === 2 ? "open" : ""}`}>
|
<li
|
||||||
<span onClick={() => toggleSubMenu(2)}>Equipo</span>
|
className={`subMenu
|
||||||
|
${openSubMenu === 2 ? "open" : ""}
|
||||||
|
${isActive(["/InformacionEquipo", "/ActivosMantenimiento", "/Mensajes", "/Programas"]) ? "active" : ""}
|
||||||
|
`}>
|
||||||
|
<span onClick={() => toggleSubMenu(2)}>
|
||||||
|
<Image
|
||||||
|
src="/computer.svg"
|
||||||
|
alt="Logo FES"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
color="#545454"
|
||||||
|
/>
|
||||||
|
Equipo</span>
|
||||||
<ul onClick={toggleMenu}>
|
<ul onClick={toggleMenu}>
|
||||||
<Link
|
<Link
|
||||||
href="/InformacionEquipo"
|
href="/InformacionEquipo"
|
||||||
@@ -123,7 +168,7 @@ export default function BarNavigation() {
|
|||||||
>
|
>
|
||||||
<li>Activos y en Mantenimiento</li>
|
<li>Activos y en Mantenimiento</li>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
{/* <Link
|
||||||
href="/Mensajes"
|
href="/Mensajes"
|
||||||
className="links"
|
className="links"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -132,7 +177,7 @@ export default function BarNavigation() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<li>Mensajes</li>
|
<li>Mensajes</li>
|
||||||
</Link>
|
</Link> */}
|
||||||
<Link
|
<Link
|
||||||
href="/Programas"
|
href="/Programas"
|
||||||
className="links"
|
className="links"
|
||||||
@@ -146,8 +191,21 @@ export default function BarNavigation() {
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className={`subMenu ${openSubMenu === 3 ? "open" : ""}`}>
|
<li
|
||||||
<span onClick={() => toggleSubMenu(3)}>Reportes</span>
|
className={`subMenu
|
||||||
|
${openSubMenu === 3 ? "open" : ""}
|
||||||
|
${isActive(["/Reportes", "/Inscritos"]) ? "active" : ""}
|
||||||
|
`}>
|
||||||
|
<span onClick={() => toggleSubMenu(3)}>
|
||||||
|
<Image
|
||||||
|
src="/chart.svg"
|
||||||
|
alt="Logo FES"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
color="#545454"
|
||||||
|
/>
|
||||||
|
Reportes</span>
|
||||||
|
|
||||||
<ul onClick={toggleMenu}>
|
<ul onClick={toggleMenu}>
|
||||||
<Link
|
<Link
|
||||||
href="/Reportes"
|
href="/Reportes"
|
||||||
@@ -171,7 +229,11 @@ export default function BarNavigation() {
|
|||||||
</Link>
|
</Link>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li className="subMenu" onClick={toggleMenu}>
|
<li
|
||||||
|
className={`subMenu
|
||||||
|
${isActive(["/BitacoraSanciones"]) ? "active" : ""}
|
||||||
|
`}
|
||||||
|
onClick={toggleMenu}>
|
||||||
<Link
|
<Link
|
||||||
href="/BitacoraSanciones"
|
href="/BitacoraSanciones"
|
||||||
className="links"
|
className="links"
|
||||||
@@ -180,10 +242,22 @@ export default function BarNavigation() {
|
|||||||
closeAllMenus();
|
closeAllMenus();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>Bitácora y sanciones</span>
|
<span>
|
||||||
|
<Image
|
||||||
|
src="/clipboard.svg"
|
||||||
|
alt="Logo FES"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
color="#545454"
|
||||||
|
/>
|
||||||
|
Bitácora y sanciones</span>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="subMenu" onClick={toggleMenu}>
|
<li
|
||||||
|
className={`subMenu
|
||||||
|
${isActive(["/CambiarPass"]) ? "active" : ""}
|
||||||
|
`}
|
||||||
|
onClick={toggleMenu}>
|
||||||
<Link
|
<Link
|
||||||
href="/CambiarPass"
|
href="/CambiarPass"
|
||||||
className="links"
|
className="links"
|
||||||
@@ -192,7 +266,15 @@ export default function BarNavigation() {
|
|||||||
closeAllMenus();
|
closeAllMenus();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span> Cambiar contraseña</span>
|
<span>
|
||||||
|
<Image
|
||||||
|
src="/key.svg"
|
||||||
|
alt="Logo FES"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
color="#545454"
|
||||||
|
/>
|
||||||
|
Cambiar contraseña</span>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user