Merge branch 'Sarabia' of https://github.com/jls846/front-censo into Lino
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import "../../styles/layout/agregarEquipo.scss";
|
||||
import "./editar.css";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
export default function Home() {
|
||||
const searchParams = useSearchParams();
|
||||
const equipoId = searchParams.get("equipoId"); // 🔹 Obtener ID del equipo
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
serie: "",
|
||||
marca: "",
|
||||
@@ -18,12 +23,19 @@ export default function Home() {
|
||||
responsable: "",
|
||||
});
|
||||
|
||||
// Si viene un equipoId, precargarlo
|
||||
useEffect(() => {
|
||||
if (equipoId) {
|
||||
setFormData((prev) => ({ ...prev, serie: equipoId }));
|
||||
}
|
||||
}, [equipoId]);
|
||||
|
||||
// Estado para saber si mostramos los campos de SO y Procesador
|
||||
const mostrarCamposComputadora = formData.tipoEquipo !== "Impresora";
|
||||
|
||||
const handleGuardar = () => {
|
||||
console.log("Formulario enviado:", formData);
|
||||
alert("Equipo guardado (vista solo)");
|
||||
alert(`Equipo ${formData.serie} guardado (vista solo)`);
|
||||
};
|
||||
|
||||
const handleCancelar = () => {
|
||||
@@ -34,10 +46,11 @@ export default function Home() {
|
||||
<div className="agregarEquipoContainer">
|
||||
<div className="innerContainer">
|
||||
<h2 className="information">
|
||||
<span></span>
|
||||
{equipoId ? `Editar Equipo (${equipoId})` : "Agregar Equipo"}
|
||||
<span>Inventario:</span>
|
||||
<span>Fecha de censo:</span>
|
||||
</h2>
|
||||
|
||||
<form className="equipoForm">
|
||||
{/* Columna 1 */}
|
||||
<div className="column">
|
||||
@@ -113,39 +126,43 @@ export default function Home() {
|
||||
|
||||
{/* Columna 2 */}
|
||||
<div className="column">
|
||||
<div className="formGroup">
|
||||
<label>Procesador</label>
|
||||
<select
|
||||
value={formData.procesador}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, procesador: e.target.value })
|
||||
}
|
||||
>
|
||||
<option value="">Selecciona procesador</option>
|
||||
<option value="Ryzen 3">Ryzen 3</option>
|
||||
<option value="Intel i5">Intel i5</option>
|
||||
<option value="AMD Ryzen 5">AMD Ryzen 5</option>
|
||||
</select>
|
||||
</div>
|
||||
{mostrarCamposComputadora && (
|
||||
<>
|
||||
<div className="formGroup">
|
||||
<label>Procesador</label>
|
||||
<select
|
||||
value={formData.procesador}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, procesador: e.target.value })
|
||||
}
|
||||
>
|
||||
<option value="">Selecciona procesador</option>
|
||||
<option value="Ryzen 3">Ryzen 3</option>
|
||||
<option value="Intel i5">Intel i5</option>
|
||||
<option value="AMD Ryzen 5">AMD Ryzen 5</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="formGroup">
|
||||
<label>Sistema operativo</label>
|
||||
<select
|
||||
value={formData.sistemaOperativo}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
sistemaOperativo: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="">Selecciona sistema operativo</option>
|
||||
<option value="Windows 10">Windows 10</option>
|
||||
<option value="Windows 11">Windows 11</option>
|
||||
<option value="Linux">Linux</option>
|
||||
<option value="macOS">macOS</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="formGroup">
|
||||
<label>Sistema operativo</label>
|
||||
<select
|
||||
value={formData.sistemaOperativo}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
sistemaOperativo: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="">Selecciona sistema operativo</option>
|
||||
<option value="Windows 10">Windows 10</option>
|
||||
<option value="Windows 11">Windows 11</option>
|
||||
<option value="Linux">Linux</option>
|
||||
<option value="macOS">macOS</option>
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="formGroup">
|
||||
<label>Adscripción</label>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import "../../styles/layout/escaner.scss"; // Importación global
|
||||
import BarcodeScanner from "@/components/BarcodeScanner";
|
||||
|
||||
// Tipos
|
||||
type Equipo = {
|
||||
id: string;
|
||||
nombre: string;
|
||||
@@ -18,59 +18,82 @@ const initialEquipos: Equipo[] = [
|
||||
|
||||
export default function Dashboard() {
|
||||
const router = useRouter();
|
||||
const [equipos, setEquipos] = useState<Equipo[]>(initialEquipos);
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const [equipos, setEquipos] = useState(initialEquipos);
|
||||
const [search, setSearch] = useState("");
|
||||
const [lastScan, setLastScan] = useState("");
|
||||
const [lastScan, setLastScan] = useState<string | null>(null);
|
||||
|
||||
// 🔍 Buscar equipo manualmente
|
||||
const buscarEquipo = () => {
|
||||
const resultado = equipos.filter(
|
||||
const encontrado = equipos.find(
|
||||
(e) =>
|
||||
e.id.includes(search) ||
|
||||
e.nombre.toLowerCase().includes(search.toLowerCase())
|
||||
e.id === search.trim() ||
|
||||
e.nombre.toLowerCase() === search.trim().toLowerCase()
|
||||
);
|
||||
setEquipos(resultado);
|
||||
};
|
||||
|
||||
const cerrarSesion = () => {
|
||||
localStorage.removeItem("loggedIn");
|
||||
router.push("/");
|
||||
|
||||
if (encontrado) {
|
||||
// Redirige a la vista de edición con el ID del equipo
|
||||
router.push(`/Editar?equipoId=${encontrado.id}`);
|
||||
} else {
|
||||
alert("Equipo no encontrado.");
|
||||
}
|
||||
};
|
||||
|
||||
// 📷 Cuando se escanea un código de barras
|
||||
const handleScan = (code: string) => {
|
||||
setLastScan(code);
|
||||
setIsScanning(false);
|
||||
|
||||
const encontrado = equipos.find((e) => e.id === code);
|
||||
|
||||
if (encontrado) {
|
||||
alert(`Equipo encontrado: ${encontrado.nombre} (${encontrado.estado})`);
|
||||
// ✅ Si el equipo existe, redirige directamente
|
||||
router.push(`/Editar?equipoId=${encontrado.id}`);
|
||||
} else {
|
||||
const codigo = prompt("Equipo no registrado. Ingresa el codigo:");
|
||||
if (codigo) {
|
||||
setEquipos([
|
||||
...equipos,
|
||||
{ id: code, nombre: codigo, estado: "Disponible" },
|
||||
]);
|
||||
// 🚫 Si no existe, se puede registrar
|
||||
const nombre = prompt("Equipo no registrado. Ingresa el nombre:");
|
||||
if (nombre) {
|
||||
const nuevoEquipo = { id: code, nombre, estado: "Disponible" };
|
||||
setEquipos([...equipos, nuevoEquipo]);
|
||||
alert("Equipo agregado al inventario");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 🔒 Cerrar sesión
|
||||
const cerrarSesion = () => {
|
||||
localStorage.removeItem("loggedIn");
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dashboardContainer">
|
||||
{/* VISTA DE ESCANEO */}
|
||||
<main className="scanView">
|
||||
<h3>Escanear Inventario</h3>
|
||||
|
||||
<div className="scannerFrame">
|
||||
<img src="/barcode_scanner.png" alt="Marco de escaneo" />
|
||||
</div>
|
||||
{isScanning ? (
|
||||
<BarcodeScanner onScan={handleScan} />
|
||||
) : (
|
||||
<div className="scannerFrame">
|
||||
<img src="/barcode_scanner.png" alt="Marco de escaneo" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="instructions">
|
||||
Enfoca el código de barras dentro del marco
|
||||
{isScanning
|
||||
? "Enfoca el código de barras dentro del marco"
|
||||
: "Presiona el botón para escanear"}
|
||||
</p>
|
||||
|
||||
<button className="scanButton">
|
||||
<img src="/photo_camera.png" alt="camara" /> Escanear
|
||||
<button
|
||||
className="scanButton"
|
||||
onClick={() => setIsScanning(!isScanning)}
|
||||
>
|
||||
<img src="/photo_camera.png" alt="cámara" />
|
||||
{isScanning ? "Cancelar" : "Escanear"}
|
||||
</button>
|
||||
|
||||
{/* 🔍 Búsqueda manual */}
|
||||
<div className="searchBox">
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -1,42 +1,117 @@
|
||||
// components/BarcodeScanner.tsx
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { BrowserMultiFormatReader } from "@zxing/library";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import Quagga from "@ericblade/quagga2";
|
||||
|
||||
type Props = { onDetected: (code: string) => void; };
|
||||
// Tipos mínimos para el resultado de Quagga
|
||||
interface CodeResult {
|
||||
code: string;
|
||||
format: string;
|
||||
}
|
||||
|
||||
export default function BarcodeScanner({ onDetected }: Props) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
interface QuaggaResult {
|
||||
codeResult: CodeResult | null;
|
||||
}
|
||||
|
||||
// Tipos para la configuración (opcional, pero mejora legibilidad)
|
||||
interface QuaggaConfig {
|
||||
inputStream: {
|
||||
name: string;
|
||||
type: string;
|
||||
target: HTMLElement;
|
||||
constraints: MediaTrackConstraints;
|
||||
};
|
||||
decoder: {
|
||||
readers: string[];
|
||||
};
|
||||
locate: boolean;
|
||||
}
|
||||
|
||||
interface BarcodeScannerProps {
|
||||
onScan: (code: string) => void;
|
||||
}
|
||||
|
||||
export default function BarcodeScanner({ onScan }: BarcodeScannerProps) {
|
||||
const scannerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const codeReader = new BrowserMultiFormatReader();
|
||||
if (!videoRef.current) return;
|
||||
if (!scannerRef.current) return;
|
||||
|
||||
// Obtener la cámara trasera si existe
|
||||
codeReader.listVideoInputDevices().then((devices) => {
|
||||
const backDevice = devices.find(d => d.label.toLowerCase().includes("back"));
|
||||
const deviceId = backDevice ? backDevice.deviceId : devices[0].deviceId;
|
||||
// ✅ Sin `any`: usamos un objeto literal tipado implícitamente
|
||||
const config: QuaggaConfig = {
|
||||
inputStream: {
|
||||
name: "Live",
|
||||
type: "LiveStream",
|
||||
target: scannerRef.current,
|
||||
constraints: {
|
||||
width: 640,
|
||||
height: 480,
|
||||
facingMode: "environment",
|
||||
},
|
||||
},
|
||||
decoder: {
|
||||
readers: [
|
||||
"code_128_reader",
|
||||
"ean_reader",
|
||||
"ean_8_reader",
|
||||
"code_39_reader",
|
||||
"upc_reader",
|
||||
],
|
||||
},
|
||||
locate: true,
|
||||
};
|
||||
|
||||
// Inicia la cámara inmediatamente
|
||||
codeReader.decodeFromVideoDevice(deviceId, videoRef.current!, (result) => {
|
||||
if (result) onDetected(result.getText());
|
||||
});
|
||||
}).catch(console.error);
|
||||
// El callback de error: tipamos `err` como `unknown`
|
||||
const initCallback = (err: unknown) => {
|
||||
if (err) {
|
||||
console.error("Error al iniciar el escáner:", err);
|
||||
return;
|
||||
}
|
||||
Quagga.start();
|
||||
};
|
||||
|
||||
return () => { codeReader.reset(); };
|
||||
}, [onDetected]);
|
||||
Quagga.init(config as unknown as Record<string, unknown>, initCallback);
|
||||
|
||||
// Handler de detección: `data` es `unknown`, lo validamos
|
||||
const handleDetected = (data: unknown) => {
|
||||
// Validación segura
|
||||
if (
|
||||
data != null &&
|
||||
typeof data === "object" &&
|
||||
"codeResult" in data &&
|
||||
data.codeResult != null &&
|
||||
typeof data.codeResult === "object" &&
|
||||
"code" in data.codeResult &&
|
||||
typeof data.codeResult.code === "string"
|
||||
) {
|
||||
const code = data.codeResult.code.trim();
|
||||
if (code !== "") {
|
||||
onScan(code);
|
||||
Quagga.stop(); // Opcional
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Quagga.onDetected(handleDetected);
|
||||
|
||||
return () => {
|
||||
Quagga.offDetected(handleDetected);
|
||||
Quagga.stop();
|
||||
};
|
||||
}, [onScan]);
|
||||
|
||||
return (
|
||||
<video
|
||||
ref={videoRef}
|
||||
<div
|
||||
ref={scannerRef}
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "600px", // Más grande
|
||||
border: "2px solid #004aad",
|
||||
borderRadius: "10px"
|
||||
maxWidth: "500px",
|
||||
height: "300px",
|
||||
borderRadius: "12px",
|
||||
overflow: "hidden",
|
||||
border: "2px solid #0056b3",
|
||||
}}
|
||||
autoPlay
|
||||
muted
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user