Cambios sin modificar
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ const nextConfig: NextConfig = {
|
||||
{
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: '4200',
|
||||
port: '3342', //4200
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
|
||||
Generated
+220
-9
@@ -11,13 +11,15 @@
|
||||
"@uiw/react-md-editor": "^4.0.7",
|
||||
"@yudiel/react-qr-scanner": "^2.2.1",
|
||||
"axios": "^1.8.4",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "15.3.3",
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
@@ -891,6 +893,75 @@
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/ssr": {
|
||||
"version": "3.9.10",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz",
|
||||
"integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/hooks": {
|
||||
"version": "0.4.16",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
|
||||
"integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz",
|
||||
"integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@react-aria/ssr": "^3.5.0",
|
||||
"@restart/hooks": "^0.5.0",
|
||||
"@types/warning": "^3.0.3",
|
||||
"dequal": "^2.0.3",
|
||||
"dom-helpers": "^5.2.0",
|
||||
"uncontrollable": "^8.0.4",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui/node_modules/@restart/hooks": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz",
|
||||
"integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui/node_modules/uncontrollable": {
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
|
||||
"integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rtsao/scc": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||
@@ -1032,6 +1103,12 @@
|
||||
"integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz",
|
||||
@@ -1051,12 +1128,27 @@
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/warning": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
|
||||
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz",
|
||||
@@ -2002,9 +2094,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
||||
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
||||
"version": "5.3.8",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
|
||||
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2268,6 +2360,12 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
@@ -2607,6 +2705,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -4083,6 +4191,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-alphabetical": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
|
||||
@@ -4592,7 +4709,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@@ -4739,7 +4855,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
@@ -5791,7 +5906,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -6132,7 +6246,6 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
@@ -6140,6 +6253,19 @@
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types-extra": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
|
||||
"integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-is": "^16.3.2",
|
||||
"warning": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/property-information": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
|
||||
@@ -6196,6 +6322,37 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-bootstrap": {
|
||||
"version": "2.10.10",
|
||||
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz",
|
||||
"integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.7",
|
||||
"@restart/hooks": "^0.4.9",
|
||||
"@restart/ui": "^1.9.4",
|
||||
"@types/prop-types": "^15.7.12",
|
||||
"@types/react-transition-group": "^4.4.6",
|
||||
"classnames": "^2.3.2",
|
||||
"dom-helpers": "^5.2.1",
|
||||
"invariant": "^2.2.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"prop-types-extra": "^1.1.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"uncontrollable": "^7.2.1",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.14.8",
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
@@ -6225,11 +6382,25 @@
|
||||
"react-dom": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
@@ -6259,6 +6430,22 @@
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@@ -7509,6 +7696,21 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/uncontrollable": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
|
||||
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.6.3",
|
||||
"@types/react": ">=16.9.11",
|
||||
"invariant": "^2.2.4",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
@@ -7693,6 +7895,15 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/web-namespaces": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||
|
||||
+3
-1
@@ -14,13 +14,15 @@
|
||||
"@uiw/react-md-editor": "^4.0.7",
|
||||
"@yudiel/react-qr-scanner": "^2.2.1",
|
||||
"axios": "^1.8.4",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "15.3.3",
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
'use client'
|
||||
import axiosInstance from "@/utils/api-config";
|
||||
import React, { useState } from "react";
|
||||
import { Button, Form, ProgressBar } from "react-bootstrap";
|
||||
import { FaUpload } from "react-icons/fa";
|
||||
|
||||
export default function CargaMasiva() {
|
||||
const [xlsx, setXlsx] = useState<File | null>(null);
|
||||
const [progress, setProgress] = useState<number>(0);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const validarExt = (file: File | null) => {
|
||||
if (!file) return;
|
||||
const extPermitidas = /(.xlsx)$/i;
|
||||
if (!extPermitidas.exec(file.name)) {
|
||||
console.error("Asegúrate de ingresar un archivo .xlsx");
|
||||
setXlsx(null);
|
||||
} else {
|
||||
setXlsx(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0] || null;
|
||||
validarExt(file);
|
||||
};
|
||||
|
||||
const enviarCargaMasiva = async () => {
|
||||
if (!xlsx) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", xlsx);
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setProgress(0);
|
||||
|
||||
const res = await axiosInstance.post(
|
||||
"/cuestionario/carga-masiva",
|
||||
formData,
|
||||
{
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: (event) => {
|
||||
if (event.total) {
|
||||
const porcentaje = Math.round(
|
||||
(event.loaded * 100) / event.total
|
||||
);
|
||||
setProgress(porcentaje);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
//Falta hacer validacion
|
||||
|
||||
console.log("Carga masiva exitosa", res.data);
|
||||
setTimeout(() => {
|
||||
setProgress(100);
|
||||
setLoading(false);
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error("Carga masiva fallida", error);
|
||||
setLoading(false);
|
||||
setProgress(0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container-fluid my-4">
|
||||
<div className="row mb-4">
|
||||
<div className="col-12">
|
||||
<div className="p-4 border rounded bg-light">
|
||||
<h2 className="mb-2">Carga Masiva de Eventos</h2>
|
||||
<p className="mb-0 text-muted">
|
||||
Sube tu archivo excel para poder crear los eventos.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Archivo Excel</Form.Label>
|
||||
<div
|
||||
className="border p-4 text-center rounded"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => document.getElementById("xlsxInput")?.click()}
|
||||
>
|
||||
<FaUpload size={40} className="mb-2" />
|
||||
<p className="mb-1">
|
||||
{xlsx?.name ||
|
||||
"Arrastra aquí tu archivo o da click aquí para buscar"}
|
||||
</p>
|
||||
<p className="is-size-6">Tamaño máximo 20MB</p>
|
||||
<p className="is-size-7">
|
||||
Si al momento de elegir un archivo este no se selecciona,
|
||||
haga click en cancelar en la ventana emergente e intente de
|
||||
nuevo.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
id="xlsxInput"
|
||||
type="file"
|
||||
accept=".xlsx"
|
||||
style={{ display: "none" }}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<div className="d-flex gap-2 mt-3">
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
disabled={!xlsx || loading}
|
||||
onClick={enviarCargaMasiva}
|
||||
>
|
||||
{loading ? "Subiendo..." : "Enviar archivo"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Barra de progreso */}
|
||||
{loading && (
|
||||
<div className="mt-3">
|
||||
<ProgressBar
|
||||
now={progress}
|
||||
label={`${progress}%`}
|
||||
animated
|
||||
striped
|
||||
variant="info"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Button, Image } from "react-bootstrap";
|
||||
import { FaUpload } from "react-icons/fa";
|
||||
import axiosInstance from "@/utils/api-config";
|
||||
|
||||
//Para poder subir las imagenes
|
||||
interface UploadedImage {
|
||||
originalName: string;
|
||||
filename: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export default function BannerUpload() {
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [uploaded, setUploaded] = useState<UploadedImage[]>([]);
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
setFiles(Array.from(e.target.files));
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (files.length === 0) {
|
||||
alert("Selecciona al menos un archivo");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
files.forEach((file) => {
|
||||
formData.append("banners", file);
|
||||
});
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setProgress(0);
|
||||
|
||||
const res = await axiosInstance.post("/cuestionario/banners/bulk", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: (progressEvent) => {
|
||||
if (progressEvent.total) {
|
||||
setProgress(
|
||||
Math.round((progressEvent.loaded * 100) / progressEvent.total)
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
//Falta hacer validacion
|
||||
|
||||
setUploaded(res.data);
|
||||
alert("Banners subidos correctamente ✅");
|
||||
setFiles([]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert(error || "Error al subir archivos");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container-fluid my-4">
|
||||
<div className="row mb-4">
|
||||
<div className="col-12">
|
||||
<div className="p-4 border rounded bg-light">
|
||||
<h2 className="mb-2">Subir Banners</h2>
|
||||
<p className="mb-0 text-muted">
|
||||
Arrastra o selecciona imágenes para subir tus banners (jpg, png, webp).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Área Drag & Drop */}
|
||||
<div
|
||||
className="border p-4 text-center rounded"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => document.getElementById("bannerInput")?.click()}
|
||||
>
|
||||
<FaUpload size={40} className="mb-2 text-muted" />
|
||||
<p className="mb-1">
|
||||
Arrastra aquí tus imágenes o haz clic para buscar
|
||||
</p>
|
||||
<p className="is-size-6">Tamaño máximo 20MB</p>
|
||||
</div>
|
||||
|
||||
<input
|
||||
id="bannerInput"
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png,.webp"
|
||||
multiple
|
||||
style={{ display: "none" }}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
|
||||
{/* Previsualización */}
|
||||
{files.length > 0 && (
|
||||
<div className="d-flex flex-wrap gap-3 mt-4">
|
||||
{files.map((file, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="border rounded overflow-hidden shadow-sm"
|
||||
style={{ width: "350px", height: "350px" }}
|
||||
>
|
||||
<Image
|
||||
src={URL.createObjectURL(file)}
|
||||
alt={file.name}
|
||||
className="img-fluid h-75 w-100 object-fit-cover"
|
||||
/>
|
||||
<p className="small text-center bg-light p-2 m-0">{file.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Botón */}
|
||||
<div className="d-flex gap-2 mt-3">
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
disabled={loading}
|
||||
onClick={handleUpload}
|
||||
>
|
||||
{loading ? "Subiendo..." : "Subir Banners"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Barra de progreso */}
|
||||
{loading && (
|
||||
<div className="progress mt-3" style={{ height: "8px" }}>
|
||||
<div
|
||||
className="progress-bar progress-bar-striped bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: `${progress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Archivos subidos */}
|
||||
{uploaded.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h5 className="mb-2 text-muted">Archivos subidos:</h5>
|
||||
<ul className="list-group">
|
||||
{uploaded.map((file, i) => (
|
||||
<li key={i} className="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span>{file.originalName}</span>
|
||||
<a href={`/${file.path}`} target="_blank" className="text-primary">
|
||||
Ver
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,34 +23,37 @@ export default function Page() {
|
||||
console.log('QR Escaneado:', rawValue);
|
||||
if (!enableScan) return;
|
||||
|
||||
try {
|
||||
if (rawValue) {
|
||||
try {
|
||||
await axiosInstance.post(
|
||||
`/participante-evento/decode-token`, {
|
||||
token: rawValue
|
||||
}
|
||||
);
|
||||
if (!rawValue) {
|
||||
setStatusMessage('⚠️ El QR no contiene los campos requeridos');
|
||||
return;
|
||||
}
|
||||
|
||||
setToken(rawValue);
|
||||
setTokenValid(true);
|
||||
setShowModal(true);
|
||||
setEnableScan(false);
|
||||
} catch (err) {
|
||||
console.warn('Participante no registrado en el cuestionario:', err);
|
||||
setStatusMessage(
|
||||
'❌ El participante no está registrado en este evento.'
|
||||
);
|
||||
}
|
||||
try {
|
||||
const response = await axiosInstance.post('/participante-evento/decode-token', { token: rawValue });
|
||||
|
||||
if (response.data) {
|
||||
// Token válido
|
||||
setToken(rawValue);
|
||||
setTokenValid(true);
|
||||
setShowModal(true);
|
||||
setEnableScan(false);
|
||||
} else {
|
||||
setStatusMessage('⚠️ El QR no contiene los campos requeridos');
|
||||
// Token caducado o cualquier otro error
|
||||
setStatusMessage(`❌ Error el codigo qr ya caduco`);
|
||||
setTokenValid(false);
|
||||
setShowModal(true); // mostramos modal con el mensaje de error
|
||||
setEnableScan(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('QR malformado:', err);
|
||||
setStatusMessage('❌ Error al leer el QR: formato inválido');
|
||||
console.error('Error al decodificar el QR:', err);
|
||||
setStatusMessage('❌ Error al leer el QR');
|
||||
setTokenValid(false);
|
||||
setShowModal(true);
|
||||
setEnableScan(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!tokenValid) return;
|
||||
|
||||
@@ -61,10 +64,17 @@ export default function Page() {
|
||||
}
|
||||
);
|
||||
|
||||
setParticipante(response.data.data.participante);
|
||||
setMessage(response.data.message);
|
||||
console.log('Asistencia registrada:', response.data);
|
||||
toast.success('✅ Asistencia registrada correctamente');
|
||||
//Validacion
|
||||
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.")
|
||||
} else {
|
||||
setParticipante(response.data.data.participante);
|
||||
setMessage(response.data.message);
|
||||
console.log('Asistencia registrada:', response.data);
|
||||
toast.success('✅ Asistencia registrada correctamente');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error en la petición:', err);
|
||||
setStatusMessage('❌ Error de red al registrar asistencia');
|
||||
@@ -229,40 +239,50 @@ export default function Page() {
|
||||
|
||||
<div className="modal-body p-4">
|
||||
{tokenValid ? (
|
||||
<>
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-center">
|
||||
// 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>
|
||||
) : (
|
||||
// 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>
|
||||
</div>
|
||||
<h6 className="text-danger mb-0">{statusMessage}</h6>
|
||||
<div className="mt-3">
|
||||
<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"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowModal(false);
|
||||
setEnableScan(true);
|
||||
setTokenValid(false);
|
||||
setToken('');
|
||||
setStatusMessage('');
|
||||
}}
|
||||
className="px-4 py-2 rounded-pill"
|
||||
>
|
||||
<i className="bi bi-x-lg me-2"></i>
|
||||
Cancelar
|
||||
Cerrar
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="py-4">
|
||||
<div className="mb-3">
|
||||
<i
|
||||
className="bi bi-exclamation-triangle-fill text-warning"
|
||||
style={{ fontSize: '2.5rem' }}
|
||||
></i>
|
||||
</div>
|
||||
<h6 className="text-danger mb-0">QR inválido o malformado</h6>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,158 @@
|
||||
'use client';
|
||||
import Button from '@/components/button';
|
||||
import Table, { Header } from '@/components/table';
|
||||
const Modal = dynamic(() => import('@/components/modal'), { ssr: false });
|
||||
import { useGetApi } from '@/hooks/use-get-api';
|
||||
import { Administrador } from '@/types/administradores';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import axiosInstance from '@/utils/api-config';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
export default function Page() {
|
||||
const { data } = useGetApi<Administrador[]>('/administrador');
|
||||
const { data, setData } = useGetApi<Administrador[]>('/administrador');
|
||||
|
||||
// Estado del modal
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [modalMode, setModalMode] = useState<'add' | 'edit'>('add');
|
||||
const [selectedUser, setSelectedUser] = useState<Administrador | null>(null);
|
||||
|
||||
// Estado del modal de eliminación
|
||||
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
|
||||
const [userToDelete, setUserToDelete] = useState<Administrador | null>(null);
|
||||
|
||||
// Estado del formulario
|
||||
const [formData, setFormData] = useState({
|
||||
nombre_usuario: '',
|
||||
correo: '',
|
||||
password: '',
|
||||
id_tipo_user: '',
|
||||
});
|
||||
|
||||
// Handlers del modal
|
||||
const handleOpenAddModal = () => {
|
||||
setModalMode('add');
|
||||
setSelectedUser(null);
|
||||
setFormData({
|
||||
nombre_usuario: '',
|
||||
correo: '',
|
||||
password: '',
|
||||
id_tipo_user: '',
|
||||
});
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleOpenEditModal = (user: Administrador) => {
|
||||
setModalMode('edit');
|
||||
setSelectedUser(user);
|
||||
setFormData({
|
||||
nombre_usuario: user.nombre_usuario,
|
||||
correo: user.correo,
|
||||
password: '',
|
||||
id_tipo_user: user.tipoUser.id.toString(),
|
||||
});
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setIsModalVisible(false);
|
||||
setSelectedUser(null);
|
||||
};
|
||||
|
||||
// Handlers del modal de eliminación
|
||||
const handleOpenDeleteModal = (user: Administrador) => {
|
||||
setUserToDelete(user);
|
||||
setIsDeleteModalVisible(true);
|
||||
};
|
||||
|
||||
const handleCloseDeleteModal = () => {
|
||||
setIsDeleteModalVisible(false);
|
||||
setUserToDelete(null);
|
||||
};
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
// Aquí iría la lógica para eliminar el usuario
|
||||
try {
|
||||
if (userToDelete) {
|
||||
// Lógica para eliminar el usuario
|
||||
const response = await axiosInstance.delete(
|
||||
`/administrador/${userToDelete.id_administrador}`
|
||||
);
|
||||
|
||||
console.log('Usuario eliminado:', response.data);
|
||||
// Actualizar la lista de usuarios en el estado
|
||||
if (data) {
|
||||
const updatedData = data.filter(
|
||||
(user) => user.id_administrador !== userToDelete.id_administrador
|
||||
);
|
||||
setData(updatedData);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error al eliminar el usuario:', error);
|
||||
}
|
||||
handleCloseDeleteModal();
|
||||
};
|
||||
|
||||
// Handler para envío del formulario
|
||||
const handleSubmitForm = async () => {
|
||||
try {
|
||||
if (modalMode === 'add') {
|
||||
// Crear nuevo usuario
|
||||
const payload = {
|
||||
nombre_usuario: formData.nombre_usuario,
|
||||
correo: formData.correo,
|
||||
password: formData.password,
|
||||
id_tipo_user: parseInt(formData.id_tipo_user),
|
||||
};
|
||||
|
||||
const response = await axiosInstance.post('/administrador', payload);
|
||||
console.log('Usuario creado:', response.data);
|
||||
|
||||
// Actualizar la lista de usuarios
|
||||
if (data) {
|
||||
setData([
|
||||
...data,
|
||||
{
|
||||
correo: formData.correo,
|
||||
id_administrador: response.data.id_administrador,
|
||||
nombre_usuario: formData.nombre_usuario,
|
||||
tipoUser: {
|
||||
id: parseInt(formData.id_tipo_user),
|
||||
tipo: formData.id_tipo_user === '1' ? 'Administrador' : 'Staff',
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
} else if (modalMode === 'edit' && selectedUser) {
|
||||
// Editar usuario existente
|
||||
const payload = {
|
||||
nombre_usuario: formData.nombre_usuario,
|
||||
correo: formData.correo,
|
||||
id_tipo_user: parseInt(formData.id_tipo_user),
|
||||
};
|
||||
|
||||
const response = await axiosInstance.put(
|
||||
`/administrador/${selectedUser.id_administrador}`,
|
||||
payload
|
||||
);
|
||||
console.log('Usuario actualizado:', response.data);
|
||||
|
||||
// Actualizar la lista de usuarios
|
||||
if (data) {
|
||||
const updatedData = data.map((user) =>
|
||||
user.id_administrador === selectedUser.id_administrador
|
||||
? { ...user, ...response.data }
|
||||
: user
|
||||
);
|
||||
setData(updatedData);
|
||||
}
|
||||
}
|
||||
|
||||
handleCloseModal();
|
||||
} catch (error) {
|
||||
console.error('Error al guardar el usuario:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const headers: Header<Administrador>[] = [
|
||||
{ key: 'id_administrador', label: 'ID' },
|
||||
@@ -19,12 +165,16 @@ export default function Page() {
|
||||
},
|
||||
{
|
||||
key: 'tipoUser',
|
||||
render: () => (
|
||||
render: (_, row) => (
|
||||
<div className="d-flex gap-2">
|
||||
<Button className="w-100" disabled>
|
||||
<Button className="w-100" onClick={() => handleOpenEditModal(row)}>
|
||||
Editar
|
||||
</Button>
|
||||
<Button variant="danger" className="w-100" disabled>
|
||||
<Button
|
||||
variant="danger"
|
||||
className="w-100"
|
||||
onClick={() => handleOpenDeleteModal(row)}
|
||||
>
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
@@ -37,9 +187,166 @@ export default function Page() {
|
||||
<div>
|
||||
<div className="d-flex justify-content-between align-items-center my-3">
|
||||
<h1>Administradores</h1>
|
||||
<Button variant="primary">Agregar Administrador</Button>
|
||||
<Button variant="primary" onClick={handleOpenAddModal}>
|
||||
Agregar Administrador
|
||||
</Button>
|
||||
</div>
|
||||
{data && <Table headers={headers} data={data} />}
|
||||
|
||||
<Modal
|
||||
isVisible={isModalVisible}
|
||||
onClose={handleCloseModal}
|
||||
size="lg"
|
||||
closeButton={true}
|
||||
>
|
||||
<div className="p-3">
|
||||
<h3 className="mb-4">
|
||||
{modalMode === 'add'
|
||||
? 'Agregar Nuevo Administrador'
|
||||
: 'Editar Administrador'}
|
||||
</h3>
|
||||
|
||||
{modalMode === 'edit' && selectedUser && (
|
||||
<div className="mb-3">
|
||||
<p>
|
||||
<strong>ID:</strong> {selectedUser.id_administrador}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Usuario actual:</strong> {selectedUser.nombre_usuario}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Correo actual:</strong> {selectedUser.correo}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Tipo:</strong> {selectedUser.tipoUser.tipo}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="nombre_usuario" className="form-label">
|
||||
Nombre de Usuario
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="nombre_usuario"
|
||||
value={formData.nombre_usuario}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
nombre_usuario: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="Ingrese el nombre de usuario"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="correo" className="form-label">
|
||||
Correo Electrónico
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
className="form-control"
|
||||
id="correo"
|
||||
value={formData.correo}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
correo: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="Ingrese el correo electrónico"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{modalMode === 'add' && (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="password" className="form-label">
|
||||
Contraseña
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="form-control"
|
||||
id="password"
|
||||
value={formData.password}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
password: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="Ingrese la contraseña"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="tipoUser" className="form-label">
|
||||
Tipo de Usuario
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="tipoUser"
|
||||
value={formData.id_tipo_user}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
id_tipo_user: e.target.value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="">Seleccione un tipo</option>
|
||||
<option value="1">Administrador</option>
|
||||
<option value="2">Staff</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="d-flex gap-2 justify-content-end">
|
||||
<Button variant="secondary" onClick={handleCloseModal}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSubmitForm}>
|
||||
{modalMode === 'add' ? 'Crear Administrador' : 'Guardar Cambios'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* Modal de confirmación de eliminación */}
|
||||
<Modal
|
||||
isVisible={isDeleteModalVisible}
|
||||
onClose={handleCloseDeleteModal}
|
||||
size="md"
|
||||
closeButton={true}
|
||||
>
|
||||
<div className="p-4 text-center">
|
||||
<div className="mb-4">
|
||||
<i className="bi bi-exclamation-triangle text-warning display-1"></i>
|
||||
</div>
|
||||
|
||||
<h4 className="mb-3">¿Está seguro de eliminar al usuario?</h4>
|
||||
|
||||
{userToDelete && (
|
||||
<div className="mb-4">
|
||||
<p className="fs-5 fw-bold text-danger">
|
||||
{userToDelete.nombre_usuario}
|
||||
</p>
|
||||
<p className="text-muted">Esta acción no se puede deshacer</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="d-flex gap-3 justify-content-center">
|
||||
<Button variant="secondary" onClick={handleCloseDeleteModal}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button variant="danger" onClick={handleConfirmDelete}>
|
||||
Sí, Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+53
-19
@@ -3,26 +3,43 @@ import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
type NavChild = {
|
||||
href: string; // siempre string
|
||||
label: string;
|
||||
};
|
||||
|
||||
type NavItem =
|
||||
| { href: string; label: string } // items normales
|
||||
| { label: string; children: NavChild[] } // items con submenu
|
||||
|
||||
|
||||
const Navbar: React.FC = () => {
|
||||
const pathname = usePathname();
|
||||
const tipo_usuario = sessionStorage.getItem('axxxd')?.replace(/"/g, '') || '';
|
||||
|
||||
const navItemsAdmin = [
|
||||
const navItemsAdmin: NavItem[] = [
|
||||
{ href: '/', label: 'Inicio' },
|
||||
{ href: '/user/eventos', label: 'Eventos' },
|
||||
{ href: '/user/qr', label: 'Lector de QRs' },
|
||||
{ href: '/user/eventos/crear', label: 'Crear Evento' },
|
||||
{
|
||||
label: 'Carga Masiva',
|
||||
children: [
|
||||
{ href: '/user/eventos/carga_masiva', label: 'Carga Masiva Eventos' },
|
||||
{ href: '/user/eventos/carga_masiva_banner', label: 'Carga Masiva Imagenes' },
|
||||
],
|
||||
},
|
||||
{ href: '/user/usuarios', label: 'Usuarios' },
|
||||
{ href: '/login', label: 'Cerrar sesión' },
|
||||
];
|
||||
|
||||
const navItemsUser = [
|
||||
const navItemsUser: NavItem[] = [
|
||||
{ href: '/user/eventos', label: 'Eventos' },
|
||||
{ href: '/user/qr', label: 'Lector de QRs' },
|
||||
{ href: '/login', label: 'Cerrar sesión' },
|
||||
];
|
||||
|
||||
const navItems = tipo_usuario === "administrador" ? navItemsAdmin : navItemsUser;
|
||||
const navItems = tipo_usuario === 'administrador' ? navItemsAdmin : navItemsUser;
|
||||
|
||||
return (
|
||||
<nav className="navbar navbar-expand-lg navbar-dark bg-azul">
|
||||
@@ -54,24 +71,41 @@ const Navbar: React.FC = () => {
|
||||
></button>
|
||||
</div>
|
||||
<div className="offcanvas-body align-items-center justify-content-between">
|
||||
<h2 className="h5 mb-0 text-white">
|
||||
Sistema de Registro de Eventos
|
||||
</h2>
|
||||
<h2 className="h5 mb-0 text-white">Sistema de Registro de Eventos</h2>
|
||||
<ul className="navbar-nav gap-lg-5">
|
||||
{navItems.map((item, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`nav-item`}
|
||||
data-bs-dismiss="offcanvas"
|
||||
>
|
||||
<Link
|
||||
className={`nav-link ${
|
||||
pathname === item.href ? 'text-white' : ''
|
||||
}`}
|
||||
href={item.href}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
<li key={index} className="nav-item" data-bs-dismiss="offcanvas">
|
||||
{'href' in item ? (
|
||||
<Link
|
||||
className={`nav-link ${
|
||||
pathname === item.href ? 'text-white' : ''
|
||||
}`}
|
||||
href={item.href}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
) : (
|
||||
<div className="nav-item dropdown">
|
||||
<a
|
||||
className="nav-link dropdown-toggle"
|
||||
href="#"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
<ul className="dropdown-menu">
|
||||
{item.children.map((child: NavChild, idx: number) => (
|
||||
<li key={idx}>
|
||||
<Link className="dropdown-item" href={child.href}>
|
||||
{child.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user