diff --git a/package-lock.json b/package-lock.json index 87d4e89..ac8fccb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "censo-frontend", "version": "0.1.0", "dependencies": { + "@zxing/library": "^0.21.3", "next": "15.5.6", "react": "19.1.0", "react-dom": "19.1.0" @@ -19,6 +20,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.5.6", + "sass": "^1.93.2", "typescript": "^5" } }, @@ -884,6 +886,330 @@ "node": ">=12.4.0" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1526,6 +1852,28 @@ "win32" ] }, + "node_modules/@zxing/library": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.21.3.tgz", + "integrity": "sha512-hZHqFe2JyH/ZxviJZosZjV+2s6EDSY0O24R+FQmlWZBZXP9IqMo7S3nb3+2LBWxodJQkSurdQGnqE7KXqrYgow==", + "license": "MIT", + "dependencies": { + "ts-custom-error": "^3.2.1" + }, + "engines": { + "node": ">= 10.4.0" + }, + "optionalDependencies": { + "@zxing/text-encoding": "~0.9.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "license": "(Unlicense OR Apache-2.0)", + "optional": true + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1940,6 +2288,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3206,6 +3570,13 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3989,6 +4360,14 @@ } } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4358,6 +4737,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4533,6 +4926,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sass": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", + "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -5032,6 +5446,15 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-custom-error": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", + "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", diff --git a/package.json b/package.json index e7391b3..c3ab5f8 100644 --- a/package.json +++ b/package.json @@ -9,17 +9,19 @@ "lint": "eslint" }, "dependencies": { + "@zxing/library": "^0.21.3", + "next": "15.5.6", "react": "19.1.0", - "react-dom": "19.1.0", - "next": "15.5.6" + "react-dom": "19.1.0" }, "devDependencies": { - "typescript": "^5", + "@eslint/eslintrc": "^3", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.5.6", - "@eslint/eslintrc": "^3" + "sass": "^1.93.2", + "typescript": "^5" } } diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..a04e1f4 --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import BarcodeScanner from "../../components/BarcodeScanner"; +import "../styles/components/dashboard.scss"; + +type Equipo = { + id: string; + nombre: string; + estado: string; +}; + +const initialEquipos: Equipo[] = [ + { id: "001", nombre: "PC Aula 1", estado: "Disponible" }, + { id: "002", nombre: "PC Aula 2", estado: "En reparación" }, +]; + +export default function Dashboard() { + const router = useRouter(); + const [equipos, setEquipos] = useState(initialEquipos); + const [search, setSearch] = useState(""); + const [lastScan, setLastScan] = useState(""); + + useEffect(() => { + const loggedIn = localStorage.getItem("loggedIn"); + if (!loggedIn) router.push("/"); + }, [router]); + + const buscarEquipo = () => { + const resultado = equipos.filter( + e => e.id.includes(search) || e.nombre.toLowerCase().includes(search.toLowerCase()) + ); + setEquipos(resultado); + }; + + const cerrarSesion = () => { + localStorage.removeItem("loggedIn"); + router.push("/"); + }; + + const handleScan = (code: string) => { + setLastScan(code); + + const encontrado = equipos.find(e => e.id === code); + if (encontrado) { + alert(`Equipo encontrado: ${encontrado.nombre} (${encontrado.estado})`); + } else { + const nombre = prompt("Equipo no registrado. Ingresa el nombre:"); + if (nombre) { + setEquipos([...equipos, { id: code, nombre, estado: "Disponible" }]); + alert("Equipo agregado al inventario"); + } + } + }; + + return ( +
+
+

Dashboard Inventario Escolar

+ +
+ +
+

Buscar equipo

+
+ setSearch(e.target.value)} /> + +
+
+ +
+

Escanear código de barras

+ + {lastScan &&

Último código escaneado: {lastScan}

} +
+ +
+

Equipos

+
    + {equipos.map((e) => ( +
  • + {e.id} - {e.nombre} + {e.estado} +
  • + ))} +
+
+
+ ); +} diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/src/app/favicon.ico and /dev/null differ diff --git a/src/app/globals.css b/src/app/globals.css deleted file mode 100644 index e3734be..0000000 --- a/src/app/globals.css +++ /dev/null @@ -1,42 +0,0 @@ -:root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -a { - color: inherit; - text-decoration: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 42fc323..ba1a783 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,32 +1,14 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; +import Header from "../components/header"; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { +export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - - - {children} + + +
+ {children} - ); + ) } diff --git a/src/app/page.module.css b/src/app/page.module.css deleted file mode 100644 index 58c71af..0000000 --- a/src/app/page.module.css +++ /dev/null @@ -1,167 +0,0 @@ -.page { - --gray-rgb: 0, 0, 0; - --gray-alpha-200: rgba(var(--gray-rgb), 0.08); - --gray-alpha-100: rgba(var(--gray-rgb), 0.05); - - --button-primary-hover: #383838; - --button-secondary-hover: #f2f2f2; - - display: grid; - grid-template-rows: 20px 1fr 20px; - align-items: center; - justify-items: center; - min-height: 100svh; - padding: 80px; - gap: 64px; - font-family: var(--font-geist-sans); -} - -@media (prefers-color-scheme: dark) { - .page { - --gray-rgb: 255, 255, 255; - --gray-alpha-200: rgba(var(--gray-rgb), 0.145); - --gray-alpha-100: rgba(var(--gray-rgb), 0.06); - - --button-primary-hover: #ccc; - --button-secondary-hover: #1a1a1a; - } -} - -.main { - display: flex; - flex-direction: column; - gap: 32px; - grid-row-start: 2; -} - -.main ol { - font-family: var(--font-geist-mono); - padding-left: 0; - margin: 0; - font-size: 14px; - line-height: 24px; - letter-spacing: -0.01em; - list-style-position: inside; -} - -.main li:not(:last-of-type) { - margin-bottom: 8px; -} - -.main code { - font-family: inherit; - background: var(--gray-alpha-100); - padding: 2px 4px; - border-radius: 4px; - font-weight: 600; -} - -.ctas { - display: flex; - gap: 16px; -} - -.ctas a { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: 1px solid transparent; - transition: - background 0.2s, - color 0.2s, - border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; -} - -a.primary { - background: var(--foreground); - color: var(--background); - gap: 8px; -} - -a.secondary { - border-color: var(--gray-alpha-200); - min-width: 158px; -} - -.footer { - grid-row-start: 3; - display: flex; - gap: 24px; -} - -.footer a { - display: flex; - align-items: center; - gap: 8px; -} - -.footer img { - flex-shrink: 0; -} - -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - a.primary:hover { - background: var(--button-primary-hover); - border-color: transparent; - } - - a.secondary:hover { - background: var(--button-secondary-hover); - border-color: transparent; - } - - .footer a:hover { - text-decoration: underline; - text-underline-offset: 4px; - } -} - -@media (max-width: 600px) { - .page { - padding: 32px; - padding-bottom: 80px; - } - - .main { - align-items: center; - } - - .main ol { - text-align: center; - } - - .ctas { - flex-direction: column; - } - - .ctas a { - font-size: 14px; - height: 40px; - padding: 0 16px; - } - - a.secondary { - min-width: auto; - } - - .footer { - flex-wrap: wrap; - align-items: center; - justify-content: center; - } -} - -@media (prefers-color-scheme: dark) { - .logo { - filter: invert(); - } -} diff --git a/src/app/page.tsx b/src/app/page.tsx index 84af2cb..ddcba40 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,95 +1,40 @@ -import Image from "next/image"; -import styles from "./page.module.css"; +"use client"; // <-- OBLIGATORIO + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import "../app/styles/components/login.scss"; +export default function Login() { + const router = useRouter(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + + const mockUsers = [ + { email: "admin@escuela.com", password: "1234" }, + { email: "usuario@escuela.com", password: "abcd" }, + ]; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const user = mockUsers.find(u => u.email === email && u.password === password); + + if (user) { + localStorage.setItem("loggedIn", "true"); + router.push("/dashboard"); + } else { + setError("Usuario o contraseña incorrectos"); + } + }; -export default function Home() { return ( -
-
- Next.js logo -
    -
  1. - Get started by editing src/app/page.tsx. -
  2. -
  3. Save and see your changes instantly.
  4. -
- - -
- +
+

Iniciar sesión

+ {error &&

{error}

} +
+ setEmail(e.target.value)} required /> + setPassword(e.target.value)} required /> + +
); -} +} \ No newline at end of file diff --git a/src/app/styles/_globals.scss b/src/app/styles/_globals.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/styles/components/BarNavigation.scss b/src/app/styles/components/BarNavigation.scss new file mode 100644 index 0000000..dd3dc50 --- /dev/null +++ b/src/app/styles/components/BarNavigation.scss @@ -0,0 +1,191 @@ +.barNavigation { + text-align: center; + background-color: #ffffff; + display: flex; + width: 100%; + top: 0; + height: 45px; + max-height: 40px; +} + +.barNavigation ul { + display: flex; + justify-content: space-around; + width: 100%; + gap: 10px; +} + +.barNavigation li { + transition: all 0.3s ease; + cursor: pointer; + color: white; + width: 100%; + transition: background-color 0.3s ease; + font-size: 1.5rem; +} + +.barNavigation li:hover { + background-color: #bd8c01; +} + +.barNavigation ul.active { + display: flex; + width: 100%; +} + +.menuToggle { + display: none; + flex-direction: column; + align-items: center; + gap: 4px; + cursor: pointer; + padding: 10px; + margin-right: 10px; +} + +.menuToggle div { + width: 25px; + height: 3px; + background-color: black; + border-radius: 4px; +} + +.subMenu { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + position: relative; + z-index: 1; + border-radius: 4px 4px 0 0; +} + +.subMenu ul { + opacity: 0; + max-height: 0; + position: absolute; + flex-direction: column; + top: 100%; + width: 100%; + background-color: #003e79; + transition: color 0.3s ease; + transition: opacity 0.4s ease, max-height 0.4s ease; +} + +.subMenu ul a { + display: none; +} + +.subMenu.open ul { + display: flex; + color: rgba(0, 61, 121, 1); +} + +.subMenu ul:hover { + color: #5b8cc9; +} + +.subMenu:hover ul { + opacity: 1; + max-height: 500px; +} + +.subMenu:hover ul a { + display: flex; +} + +.subMenu li { + padding: 10px 0; +} + +.subMenu span { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + color: #383838; + font-size: 1.5rem; +} + +.subMenu:hover > span, +.subMenu > a:hover > span { + color: white; +} + +.containerLinks { + padding: 0 !important; + border-radius: 0 0 4px 4px; +} + +.links { + display: flex; + justify-content: center; + align-items: center; + color: white; + width: 100%; + height: 100%; + font-size: 1.5rem; +} + +thead, +tbody { + font-size: 1.25rem; +} + +@media (max-width: 800px) { + .menuToggle { + display: flex; + position: relative; + align-items: end; + transition: transform 0.3s ease; + } + .barNavigation { + width: 100vw; + } + + .menuToggle.center { + align-items: center; + margin: 0; + } + + .barNavigation { + font-size: 15px; + display: block; + } + + .barNavigation ul { + position: absolute; + background-color: #003e79; + flex-direction: column; + padding: 10px 0; + align-items: center; + display: none; + z-index: 1; + height: auto; + } + + .barNavigation { + padding: 0; + } + + .barNavigation li:hover { + background-color: #d59f0f; + color: white; + border-radius: 0; + border: none; + } + + .subMenu ul { + width: 100%; + border-radius: 4px; + position: relative; + background-color: rgb(1, 92, 184); + } + + thead, + tbody { + font-size: 1rem; + } +} \ No newline at end of file diff --git a/src/app/styles/components/card.scss b/src/app/styles/components/card.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/styles/components/dashboard.scss b/src/app/styles/components/dashboard.scss new file mode 100644 index 0000000..86331ee --- /dev/null +++ b/src/app/styles/components/dashboard.scss @@ -0,0 +1,83 @@ +.dashboard-container { + max-width: 900px; + margin: 50px auto; + padding: 20px; + background: #f8f9fa; + border-radius: 15px; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); + font-family: Arial, sans-serif; +} + +h2 { + color: #004aad; // azul marino + font-size: 2rem; + margin-bottom: 15px; +} + +button { + background-color: #d4af37; // dorado + color: white; + border: none; + padding: 8px 16px; + border-radius: 8px; + cursor: pointer; + font-weight: bold; + transition: background 0.3s; +} + +button:hover { + background-color: #b5952e; +} + +input { + padding: 8px 12px; + border-radius: 8px; + border: 1px solid #ccc; + flex: 1; + font-size: 1rem; +} + +.search-section, .scanner-section, .equipos-section { + margin-top: 30px; +} + +.search-section input, .search-section button { + margin-right: 10px; +} + +.scanner-section video { + border: 2px solid #004aad; + border-radius: 10px; + max-width: 400px; + width: 100%; +} + +.equipos-section ul { + list-style: none; + padding: 0; +} + +.equipos-section li { + display: flex; + justify-content: space-between; + padding: 10px 15px; + border-radius: 8px; + border: 1px solid #ccc; + margin-bottom: 10px; + background: white; + transition: background 0.3s; +} + +.equipos-section li:hover { + background: #e6f0ff; +} + +.estado-disponible { + color: green; + font-weight: bold; +} + +.estado-reparacion { + color: red; + font-weight: bold; +} diff --git a/src/app/styles/components/footer.scss b/src/app/styles/components/footer.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/styles/components/form.scss b/src/app/styles/components/form.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/styles/components/header.module.scss b/src/app/styles/components/header.module.scss new file mode 100644 index 0000000..2f2d107 --- /dev/null +++ b/src/app/styles/components/header.module.scss @@ -0,0 +1,73 @@ +.logo { + position: relative; + filter: invert(1); + z-index: 3; + margin: 5px; +} + +.cedetecContainer { + position: absolute; + top: 0; + right: 0; + width: 300px; + height: 100%; + z-index: -1; +} + +.cedetecContainer::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(to right, #f9f9f9, rgba(128, 128, 128, 0)); +} + +.yellowPart { + position: absolute; + content: ""; + top: 0; + left: -20px; + width: 65%; + height: 30px; + min-width: 300px; + background: #bd8c01; + transform: skew(-45deg); + z-index: 0; +} +.header { + display: grid; + grid-template-columns: auto 1fr; + padding: 0 40px; + position: relative; + z-index: 1; + height: 90px; +} + +.header::after { + content: ""; + top: 0; + left: -40px; + position: absolute; + background-color: #003e79; + height: 100%; + min-width: 300px; + transform: skew(45deg); + z-index: 2; +} + +.header::before { + content: ""; + top: 0; + left: -20px; + position: absolute; + background-color: rgb(1, 92, 184); + height: 100%; + width: 10%; + min-width: 300px; + transform: skew(45deg); + z-index: 2; +} +@media (max-width: 800px) { + .yellowPart { + background: #bd8c01; + } +} diff --git a/src/app/styles/components/login.scss b/src/app/styles/components/login.scss new file mode 100644 index 0000000..70631b9 --- /dev/null +++ b/src/app/styles/components/login.scss @@ -0,0 +1,31 @@ +.login-container { + width: 300px; + margin: 100px auto; + padding: 30px; + border: 1px solid #ccc; + border-radius: 10px; + background: #f8f9fa; + text-align: center; + + h2 { margin-bottom: 20px; color: #004aad; } + + input { + width: 100%; + padding: 10px; + margin-bottom: 15px; + border-radius: 5px; + border: 1px solid #ccc; + } + + button { + width: 100%; + padding: 10px; + background-color: #004aad; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + } + + .error { color: red; margin-bottom: 10px; } +} diff --git a/src/app/styles/variables.scss b/src/app/styles/variables.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/BarNavigation.tsx b/src/components/BarNavigation.tsx new file mode 100644 index 0000000..de8233e --- /dev/null +++ b/src/components/BarNavigation.tsx @@ -0,0 +1,104 @@ +"use client"; +import { useState } from "react"; +import "../app/styles/components/BarNavigation.scss"; +import Link from "next/link"; + +function BarNavigation() { + const [openMenu, setOpenMenu] = useState(false); + const [openSubMenu, setOpenSubMenu] = useState(null); + + const toggleMenu = () => setOpenMenu(!openMenu); + const toggleSubMenu = (index: number) => { + if (typeof window !== "undefined" && window.innerWidth <= 800) { + setOpenSubMenu(openSubMenu === index ? null : index); + } + }; + + return ( + + ); +} + +export default BarNavigation; +//IO \ No newline at end of file diff --git a/src/components/BarcodeScanner.tsx b/src/components/BarcodeScanner.tsx new file mode 100644 index 0000000..d324592 --- /dev/null +++ b/src/components/BarcodeScanner.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import { BrowserMultiFormatReader } from "@zxing/library"; + +type Props = { onDetected: (code: string) => void; }; + +export default function BarcodeScanner({ onDetected }: Props) { + const videoRef = useRef(null); + + useEffect(() => { + const codeReader = new BrowserMultiFormatReader(); + if (!videoRef.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; + + // Inicia la cámara inmediatamente + codeReader.decodeFromVideoDevice(deviceId, videoRef.current!, (result) => { + if (result) onDetected(result.getText()); + }); + }).catch(console.error); + + return () => { codeReader.reset(); }; + }, [onDetected]); + + return ( +