Merge pull request 'feat: add modal to display user contador and update attendance confirmation flow' (#35) from develop into master

Reviewed-on: #35
This commit was merged in pull request #35.
This commit is contained in:
2026-05-14 19:07:27 -06:00
2 changed files with 86 additions and 36 deletions
@@ -11,6 +11,10 @@ import { useParams } from 'next/navigation';
import React, { useEffect } from 'react';
import toast from 'react-hot-toast';
import { useEvento } from '@/context/evento';
import dynamic from 'next/dynamic';
import Button from '@/components/button';
const Modal = dynamic(() => import('@/components/modal'), { ssr: false });
type Params = {
id_evento: string;
@@ -27,6 +31,7 @@ export default function Page() {
const [loadingAsistencia, setLoadingAsistencia] = React.useState<
Record<number, boolean>
>({});
const [contadorModal, setContadorModal] = React.useState<number | null>(null);
const { data: participantes, setData } = useGetApi<ParticipacionEvento[]>(
`/participante-evento/evento/${params.id_cuestionario}`
@@ -45,12 +50,15 @@ export default function Page() {
) => {
setLoadingAsistencia((prev) => ({ ...prev, [id_participante]: true }));
try {
await axiosInstance.post(
const response = await axiosInstance.post(
`/participante-evento/asistencia/${id_participante}/${id_evento}`
);
if (response.data?.contador != null) {
setContadorModal(response.data.contador);
}
toast.success('Asistencia confirmada');
// Actualiza lista
const { data } = await axiosInstance.get<ParticipacionEvento[]>(
`/participante-evento/evento/${id_evento}`
);
@@ -188,6 +196,33 @@ export default function Page() {
</div>
</>
)}
<Modal
isVisible={contadorModal !== null}
size="sm"
onClose={() => setContadorModal(null)}
closeButton
className={{ content: 'border-0 rounded-3', body: 'text-center' }}
>
<div className="modal-header bg-primary text-white border-0 rounded-top-3">
<h5 className="modal-title mb-0 fw-bold">
<i className="bi bi-hash me-2"></i>
Contador
</h5>
</div>
<div className="modal-body p-4">
<p className="display-4 fw-bold text-primary mb-1">{contadorModal}</p>
<p className="text-muted mb-4">
Este es el numero de estampillas a entregar al usuario
</p>
<Button
variant="primary"
onClick={() => setContadorModal(null)}
className="px-4 py-2 rounded-pill"
>
Cerrar
</Button>
</div>
</Modal>
</div>
);
}
+49 -34
View File
@@ -15,6 +15,7 @@ export default function Page() {
const [statusMessage, setStatusMessage] = useState('');
const [participante, setParticipante] = useState('');
const [contador, setContador] = useState(0);
const [message, setMessage] = useState('');
const [token, setToken] = useState('');
const [tokenValid, setTokenValid] = useState(false);
@@ -29,7 +30,10 @@ export default function Page() {
}
try {
const response = await axiosInstance.post('/participante-evento/decode-token', { token: rawValue });
const response = await axiosInstance.post(
'/participante-evento/decode-token',
{ token: rawValue }
);
if (response.data) {
// Token válido
@@ -37,6 +41,7 @@ export default function Page() {
setTokenValid(true);
setShowModal(true);
setEnableScan(false);
setContador(response.data.contador);
} else {
// Token caducado o cualquier otro error
setStatusMessage(`❌ Error el código qr ya caduco`);
@@ -53,22 +58,22 @@ export default function Page() {
}
};
const handleConfirm = async () => {
if (!tokenValid) return;
try {
const response = await axiosInstance.post(
`/participante-evento/asistencia`, {
token: token
`/participante-evento/asistencia`,
{
token: token,
}
);
//Validacion
if(response.data.success === false) {
console.error("El usuario ya se registro");
if (response.data.success === false) {
console.error('El usuario ya se registro');
setMessage(response.data.message);
toast.error("❌ Error el usaurio ya se registro previamente.")
toast.error('❌ Error el usaurio ya se registro previamente.');
} else {
setParticipante(response.data.data.participante);
setMessage(response.data.message);
@@ -137,7 +142,9 @@ export default function Page() {
<div className="mb-4">
<div className="alert alert-success border-0 rounded-3 d-flex align-items-center">
<i className="bi bi-person-check-fill me-2"></i>
<span>Ultimo participante registrado: {participante}</span>
<span>
Ultimo participante registrado: {participante}
</span>
</div>
</div>
)}
@@ -191,8 +198,8 @@ export default function Page() {
statusMessage.includes('❌')
? 'alert-danger'
: statusMessage.includes('⚠️')
? 'alert-warning'
: 'alert-success'
? 'alert-warning'
: 'alert-success'
}`}
>
<div className="me-2">
@@ -240,34 +247,42 @@ export default function Page() {
<div className="modal-body p-4">
{tokenValid ? (
// Modal de confirmación de asistencia
<div className="d-grid gap-2 d-md-flex justify-content-center">
<Button
variant="success"
onClick={handleConfirm}
className="px-4 py-2 rounded-pill fw-semibold"
>
<i className="bi bi-check2 me-2"></i>
Confirmar Asistencia
</Button>
<Button
variant="outline-secondary"
onClick={() => {
setShowModal(false);
setEnableScan(true);
setTokenValid(false);
setToken('');
}}
className="px-4 py-2 rounded-pill"
>
<i className="bi bi-x-lg me-2"></i>
Cancelar
</Button>
</div>
<>
<p className="text-muted mb-3">
Contador del usuario: <strong>{contador}</strong>
</p>
<div className="d-grid gap-2 d-md-flex justify-content-center">
<Button
variant="success"
onClick={handleConfirm}
className="px-4 py-2 rounded-pill fw-semibold"
>
<i className="bi bi-check2 me-2"></i>
Confirmar Asistencia
</Button>
<Button
variant="outline-secondary"
onClick={() => {
setShowModal(false);
setEnableScan(true);
setTokenValid(false);
setToken('');
}}
className="px-4 py-2 rounded-pill"
>
<i className="bi bi-x-lg me-2"></i>
Cancelar
</Button>
</div>
</>
) : (
// Modal de error
<div className="py-4">
<div className="mb-3">
<i className="bi bi-exclamation-triangle-fill text-warning" style={{ fontSize: '2.5rem' }}></i>
<i
className="bi bi-exclamation-triangle-fill text-warning"
style={{ fontSize: '2.5rem' }}
></i>
</div>
<h6 className="text-danger mb-0">{statusMessage}</h6>
<div className="mt-3">