diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ca926ac --- /dev/null +++ b/.dockerignore @@ -0,0 +1,66 @@ +# Dependencias +node_modules +npm-debug.log +yarn-debug.log +yarn-error.log + +# Archivos de compilación +/dist +/tmp +/out-tsc +/bazel-out + +# Archivos de IDE y editores +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace +.vscode/* + +# Archivos del sistema +.DS_Store +Thumbs.db + +# Archivos de entorno (excepto los de ejemplo) +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Archivos de Docker (para evitar recursión) +Dockerfile +Dockerfile.* +docker-compose.yml +docker-compose.*.yml + +# Archivos Git +.git +.gitignore + +# Archivos de test y coverage +/coverage +/tests +*.spec.ts + +# Archivos de configuración +.angular/cache +.sass-cache/ +connect.lock +libpeerconnection.log +testem.log +/typings + +# Archivos de build temporales +.build +.buildinfo +.cache + +# Otros archivos innecesarios +README.md +CHANGELOG.md +*.md +LICENSE \ No newline at end of file diff --git a/.gitignore b/.gitignore index cc7b141..519cd44 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,18 @@ /tmp /out-tsc /bazel-out +/build +backend/dist +backend/build # Node /node_modules +backend/node_modules npm-debug.log yarn-error.log +pnpm-debug.log* +yarn-debug.log* +lerna-debug.log* # IDEs and editors .idea/ @@ -33,10 +40,36 @@ yarn-error.log .sass-cache/ /connect.lock /coverage +backend/coverage +backend/.nyc_output /libpeerconnection.log testem.log /typings +.temp +.tmp # System files .DS_Store Thumbs.db +.sonarlint +.vscode +.scannerwork +package-lock.json +backend/package-lock.json +**/.claude/settings.local.json + +# Environment variables +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json \ No newline at end of file diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..7147036 --- /dev/null +++ b/.htaccess @@ -0,0 +1,67 @@ +# Habilitar compresión GZIP + + # Comprimir HTML, CSS, JavaScript, Text, XML y fonts + AddOutputFilterByType DEFLATE application/javascript + AddOutputFilterByType DEFLATE application/rss+xml + AddOutputFilterByType DEFLATE application/vnd.ms-fontobject + AddOutputFilterByType DEFLATE application/x-font + AddOutputFilterByType DEFLATE application/x-font-opentype + AddOutputFilterByType DEFLATE application/x-font-otf + AddOutputFilterByType DEFLATE application/x-font-truetype + AddOutputFilterByType DEFLATE application/x-font-ttf + AddOutputFilterByType DEFLATE application/x-javascript + AddOutputFilterByType DEFLATE application/xhtml+xml + AddOutputFilterByType DEFLATE application/xml + AddOutputFilterByType DEFLATE font/opentype + AddOutputFilterByType DEFLATE font/otf + AddOutputFilterByType DEFLATE font/ttf + AddOutputFilterByType DEFLATE image/svg+xml + AddOutputFilterByType DEFLATE image/x-icon + AddOutputFilterByType DEFLATE text/css + AddOutputFilterByType DEFLATE text/html + AddOutputFilterByType DEFLATE text/javascript + AddOutputFilterByType DEFLATE text/plain + AddOutputFilterByType DEFLATE text/xml + + # Eliminar bugs de navegadores antiguos + BrowserMatch ^Mozilla/4 gzip-only-text/html + BrowserMatch ^Mozilla/4\.0[678] no-gzip + BrowserMatch \bMSIE !no-gzip !gzip-only-text/html + + # No comprimir imágenes (ya están comprimidas) + SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp)$ no-gzip + + +# Habilitar caché de navegador + + ExpiresActive On + + # Imágenes + ExpiresByType image/jpeg "access plus 1 year" + ExpiresByType image/gif "access plus 1 year" + ExpiresByType image/png "access plus 1 year" + ExpiresByType image/webp "access plus 1 year" + ExpiresByType image/svg+xml "access plus 1 year" + ExpiresByType image/x-icon "access plus 1 year" + + # Video + ExpiresByType video/mp4 "access plus 1 year" + ExpiresByType video/mpeg "access plus 1 year" + + # CSS, JavaScript + ExpiresByType text/css "access plus 1 month" + ExpiresByType text/javascript "access plus 1 month" + ExpiresByType application/javascript "access plus 1 month" + + # Otros + ExpiresByType application/pdf "access plus 1 month" + ExpiresByType application/x-shockwave-flash "access plus 1 month" + + +# Reglas de SPA para Angular +RewriteEngine On +RewriteBase / +RewriteRule ^index\.html$ - [L] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . /index.html [L] \ No newline at end of file diff --git a/README.md b/README.md index 981baaf..c119f1c 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,178 @@ -# CronogramasPrimeng +# Cronogramas PrimeNG Application -This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.9. +Este proyecto es una aplicación Angular 19 utilizando PrimeNG para la gestión de cronogramas. + +## Instalación + +### Requisitos Previos + +- Node.js (versión 20.x recomendada) +- npm (incluido con Node.js) +- Git + +### Pasos de Instalación + +1. **Clonar el Repositorio** + + ```bash + git clone + cd cronogramas-primeng + ``` + +2. **Instalar Dependencias** + + ```bash + npm install + ``` + +3. **Iniciar el Servidor de Desarrollo** + + ```bash + npm start + ``` + + La aplicación estará disponible en `http://localhost:4200/` + +### Dependencias Principales + +El proyecto utiliza las siguientes bibliotecas clave: + +- **Angular 19**: Framework base + ```bash + npm install @angular/core @angular/common @angular/forms @angular/router @angular/compiler + ``` + +- **PrimeNG 19.1.0**: Biblioteca de componentes UI + ```bash + npm install primeng @primeng/themes + ``` + +- **PrimeFlex 4.0.0**: Sistema de CSS flexible + ```bash + npm install primeflex + ``` + +- **PrimeIcons 7.0.0**: Conjunto de iconos + ```bash + npm install primeicons + ``` + +- **ExcelJS 4.4.0**: Para exportación a Excel + ```bash + npm install exceljs + ``` + +- **File-Saver 2.0.5**: Para guardar archivos en el cliente + ```bash + npm install file-saver @types/file-saver + ``` + +## Estructura del Proyecto + +``` +/cronogramas-primeng/ +├── src/ # Código fuente +│ ├── app/ # Componentes Angular +│ │ ├── components/ # Componentes compartidos +│ │ ├── guards/ # Guardias de ruta +│ │ ├── interceptors/ # Interceptores HTTP +│ │ ├── models/ # Interfaces de datos +│ │ ├── pages/ # Componentes de página +│ │ ├── services/ # Servicios +│ │ └── utils/ # Utilidades +│ ├── index.html # HTML principal +│ ├── main.ts # Punto de entrada +│ └── styles.scss # Estilos globales +├── public/ # Recursos estáticos +├── angular.json # Configuración de Angular +├── package.json # Dependencias y scripts +├── tsconfig.json # Configuración TypeScript +├── Dockerfile # Configuración de Docker +└── docker-compose.yml # Configuración Docker Compose +``` + +## Estructura de Interfaces + +Se han creado las siguientes interfaces para los modelos de datos: + +- **Cronograma**: Modelo base para todos los cronogramas +- **Empresa**: Modelo para empresas +- **TipoCarga**: Modelo para tipos de carga +- **EstadoAprobacion**: Modelo para estados de aprobación +- **ActualizacionPd**: Modelo para actualizaciones de PD +- **AjustePd**: Modelo para ajustes de PD +- **UnidadInformacion**: Modelo para unidades de información + +## Servicios + +Los servicios implementados permiten conectarse a un backend mediante HTTP: + +- **CronogramaService**: CRUD para cronogramas +- **EmpresaService**: CRUD para empresas +- **ActualizacionPdService**: CRUD para actualizaciones de PD +- **AjustePdService**: CRUD para ajustes de PD +- **UnidadInformacionService**: CRUD para unidades de información +- **TipoCargaService**: Consulta de tipos de carga +- **EstadoAprobacionService**: Consulta de estados de aprobación +- **AuthService**: Autenticación y gestión de tokens + +## Seguridad + +La aplicación incluye: + +- Interceptor HTTP para añadir tokens de autenticación +- Guard para proteger rutas +- Login y sistema de autenticación + +## Comandos Disponibles + +| Comando | Descripción | +|---------|-------------| +| `npm start` | Inicia servidor de desarrollo | +| `npm run build` | Compila el proyecto | +| `npm run build:prod` | Compila para producción | +| `npm run watch` | Compila en modo observador | +| `npm test` | Ejecuta pruebas unitarias | + +## Despliegue con Docker + +El proyecto incluye archivos Docker para facilitar el despliegue: + +1. Construir y ejecutar con Docker Compose: + +```bash +docker-compose up --build +``` + +Esto expone la aplicación en `http://localhost:80`. + +2. Construir manualmente con Docker: + +```bash +docker build -t cronogramas-primeng . +docker run -p 80:80 cronogramas-primeng +``` ## Development server -To start a local development server, run: +Para iniciar un servidor de desarrollo local, ejecute: ```bash ng serve ``` -Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. - -## Code scaffolding - -Angular CLI includes powerful code scaffolding tools. To generate a new component, run: - -```bash -ng generate component component-name -``` - -For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: - -```bash -ng generate --help -``` +Una vez que el servidor esté en funcionamiento, abra su navegador y navegue a `http://localhost:4200/`. La aplicación se recargará automáticamente cuando modifique cualquiera de los archivos fuente. ## Building -To build the project run: +Para compilar el proyecto ejecute: ```bash ng build ``` -This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. - -## Running unit tests - -To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command: - -```bash -ng test -``` - -## Running end-to-end tests - -For end-to-end (e2e) testing, run: - -```bash -ng e2e -``` - -Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. +Esto compilará su proyecto y almacenará los artefactos de compilación en el directorio `dist/`. Por defecto, la compilación para producción optimiza su aplicación para el rendimiento y la velocidad. ## Additional Resources -For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. +Para más información sobre el uso de Angular CLI, incluyendo referencias detalladas de comandos, visite la página [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli). \ No newline at end of file diff --git a/angular.json b/angular.json index 8718c26..bc6ae55 100644 --- a/angular.json +++ b/angular.json @@ -3,7 +3,7 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "cronogramas-primeng": { + "cronogramas": { "projectType": "application", "schematics": { "@schematics/angular:component": { @@ -17,7 +17,7 @@ "build": { "builder": "@angular-devkit/build-angular:application", "options": { - "outputPath": "dist/cronogramas-primeng", + "outputPath": "dist/cronogramas", "index": "src/index.html", "browser": "src/main.ts", "polyfills": [ @@ -29,29 +29,44 @@ { "glob": "**/*", "input": "public" + }, + { + "glob": ".htaccess", + "input": ".", + "output": "/" } ], "styles": [ "src/styles.scss", - "node_modules/animate.css/animate.min.css", + { + "input": "node_modules/animate.css/animate.min.css", + "bundleName": "animate", + "inject": true + }, + { + "input": "node_modules/primeflex/primeflex.css", + "bundleName": "primeflex", + "inject": true + }, + { + "input": "node_modules/primeicons/primeicons.css", + "bundleName": "primeicons", + "inject": true + } ], - "scripts": [] + "scripts": [], + "allowedCommonJsDependencies": [ + "file-saver", + "exceljs" + ] }, "configurations": { "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kB", - "maximumError": "1MB" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "4kB", - "maximumError": "8kB" - } - ], - "outputHashing": "all" + "outputHashing": "all", + "optimization": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true }, "development": { "optimization": false, @@ -65,10 +80,10 @@ "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { - "buildTarget": "cronogramas-primeng:build:production" + "buildTarget": "cronogramas:build:production" }, "development": { - "buildTarget": "cronogramas-primeng:build:development" + "buildTarget": "cronogramas:build:development" } }, "defaultConfiguration": "development" @@ -89,6 +104,11 @@ { "glob": "**/*", "input": "public" + }, + { + "glob": ".htaccess", + "input": ".", + "output": "/" } ], "styles": [ @@ -99,5 +119,8 @@ } } } + }, + "cli": { + "analytics": false } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 038f269..7090208 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { - "name": "cronogramas-primeng", - "version": "0.0.0", + "name": "cronogramas", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "cronogramas-primeng", - "version": "0.0.0", + "name": "cronogramas", + "version": "0.1.0", + "hasInstallScript": true, "dependencies": { "@angular/common": "^19.2.0", "@angular/compiler": "^19.2.0", @@ -17,6 +18,9 @@ "@angular/router": "^19.2.0", "@primeng/themes": "^19.1.0", "animate.css": "^4.1.1", + "exceljs": "^4.4.0", + "file-saver": "^2.0.5", + "pdfmake": "^0.2.19", "primeflex": "^4.0.0", "primeicons": "^7.0.0", "primeng": "^19.1.0", @@ -28,7 +32,10 @@ "@angular-devkit/build-angular": "^19.2.9", "@angular/cli": "^19.2.9", "@angular/compiler-cli": "^19.2.0", + "@types/file-saver": "^2.0.7", "@types/jasmine": "~5.1.0", + "@types/pdfmake": "^0.2.11", + "@types/xlsx": "^0.0.35", "jasmine-core": "~5.6.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -2824,6 +2831,98 @@ "node": ">=18" } }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@foliojs-fork/fontkit": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/fontkit/-/fontkit-1.9.2.tgz", + "integrity": "sha512-IfB5EiIb+GZk+77TRB86AHroVaqfq8JRFlUbz0WEwsInyCG0epX2tCPOy+UfaWPju30DeVoUAXfzWXmhn753KA==", + "license": "MIT", + "dependencies": { + "@foliojs-fork/restructure": "^2.0.2", + "brotli": "^1.2.0", + "clone": "^1.0.4", + "deep-equal": "^1.0.0", + "dfa": "^1.2.0", + "tiny-inflate": "^1.0.2", + "unicode-properties": "^1.2.2", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/@foliojs-fork/linebreak": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/linebreak/-/linebreak-1.1.2.tgz", + "integrity": "sha512-ZPohpxxbuKNE0l/5iBJnOAfUaMACwvUIKCvqtWGKIMv1lPYoNjYXRfhi9FeeV9McBkBLxsMFWTVVhHJA8cyzvg==", + "license": "MIT", + "dependencies": { + "base64-js": "1.3.1", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/@foliojs-fork/linebreak/node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "license": "MIT" + }, + "node_modules/@foliojs-fork/pdfkit": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/@foliojs-fork/pdfkit/-/pdfkit-0.15.3.tgz", + "integrity": "sha512-Obc0Wmy3bm7BINFVvPhcl2rnSSK61DQrlHU8aXnAqDk9LCjWdUOPwhgD8Ywz5VtuFjRxmVOM/kQ/XLIBjDvltw==", + "license": "MIT", + "dependencies": { + "@foliojs-fork/fontkit": "^1.9.2", + "@foliojs-fork/linebreak": "^1.1.1", + "crypto-js": "^4.2.0", + "jpeg-exif": "^1.1.4", + "png-js": "^1.0.0" + } + }, + "node_modules/@foliojs-fork/restructure": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/restructure/-/restructure-2.0.2.tgz", + "integrity": "sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==", + "license": "MIT" + }, "node_modules/@inquirer/checkbox": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.5.tgz", @@ -5151,6 +5250,13 @@ "@types/send": "*" } }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -5209,6 +5315,27 @@ "@types/node": "*" } }, + "node_modules/@types/pdfkit": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.13.9.tgz", + "integrity": "sha512-RDG8Yb1zT7I01FfpwK7nMSA433XWpblMqSCtA5vJlSyavWZb303HUYPCel6JTiDDFqwGLvtAnYbH8N/e0Cb89g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pdfmake": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@types/pdfmake/-/pdfmake-0.2.11.tgz", + "integrity": "sha512-gglgMQhnG6C2kco13DJlvokqTxL+XKxHwCejElH8fSCNF9ZCkRK6Mzo011jQ0zuug+YlIgn6BpcpZrARyWdW3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/pdfkit": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.18", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", @@ -5283,6 +5410,13 @@ "@types/node": "*" } }, + "node_modules/@types/xlsx": { + "version": "0.0.35", + "resolved": "https://registry.npmjs.org/@types/xlsx/-/xlsx-0.0.35.tgz", + "integrity": "sha512-s0x3DYHZzOkxtjqOk/Nv1ezGzpbN7I8WX+lzlV/nFfTDOv7x4d8ZwGHcnaiB8UCx89omPsftQhS5II3jeWePxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.2.0.tgz", @@ -5713,6 +5847,75 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5727,6 +5930,12 @@ "dev": true, "license": "MIT" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -5839,14 +6048,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -5900,6 +6107,15 @@ "node": ">=14.0.0" } }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -5910,6 +6126,19 @@ "node": "*" } }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -5927,7 +6156,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -5935,6 +6163,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -5999,7 +6233,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6019,6 +6252,15 @@ "node": ">=8" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -6056,7 +6298,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -6077,6 +6318,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -6084,6 +6334,23 @@ "dev": true, "license": "MIT" }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -6242,11 +6509,28 @@ "node": ">=18" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6260,7 +6544,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6304,6 +6587,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6512,7 +6807,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -6587,6 +6881,21 @@ "dev": true, "license": "ISC" }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -6650,7 +6959,6 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/connect": { @@ -6799,7 +7107,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, "license": "MIT" }, "node_modules/cors": { @@ -6843,6 +7150,31 @@ } } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6874,6 +7206,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/css-loader": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", @@ -6970,6 +7308,12 @@ "node": ">=4.0" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -6988,6 +7332,26 @@ } } }, + "node_modules/deep-equal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "license": "MIT", + "dependencies": { + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.5.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/default-browser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", @@ -7031,6 +7395,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -7044,6 +7425,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7083,6 +7481,12 @@ "dev": true, "license": "MIT" }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, "node_modules/di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -7179,7 +7583,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -7190,6 +7593,45 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -7263,6 +7705,15 @@ "node": ">=0.10.0" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/engine.io": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", @@ -7413,7 +7864,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7423,7 +7873,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7440,7 +7889,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -7604,6 +8052,35 @@ "node": ">=0.8.x" } }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exceljs/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/exponential-backoff": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", @@ -7746,6 +8223,19 @@ "node": ">=4" } }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7838,6 +8328,12 @@ } } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -8023,6 +8519,12 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -8055,7 +8557,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -8073,11 +8574,48 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8120,7 +8658,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -8145,7 +8682,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -8160,7 +8696,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -8232,7 +8767,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8245,7 +8779,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/handle-thing": { @@ -8265,11 +8798,22 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8282,7 +8826,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -8298,7 +8841,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -8562,7 +9104,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -8642,6 +9183,12 @@ "node": ">=0.10.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immutable": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", @@ -8681,7 +9228,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -8692,7 +9238,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -8729,6 +9274,22 @@ "node": ">= 10" } }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -8765,6 +9326,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -8896,7 +9473,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8951,7 +9527,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, "license": "MIT" }, "node_modules/isbinaryfile": { @@ -9129,6 +9704,12 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jpeg-exif": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz", + "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9226,6 +9807,48 @@ ], "license": "MIT" }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", @@ -9584,6 +10207,48 @@ "shell-quote": "^1.8.1" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/less": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz", @@ -9707,6 +10372,15 @@ } } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9714,6 +10388,12 @@ "dev": true, "license": "MIT" }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, "node_modules/listr2": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", @@ -9847,6 +10527,85 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -10044,7 +10803,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10235,7 +10993,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -10248,7 +11005,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10411,7 +11167,6 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.6" @@ -10729,7 +11484,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10882,6 +11636,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -10916,7 +11695,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -11184,6 +11962,12 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11311,7 +12095,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11378,6 +12161,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pdfmake": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.2.19.tgz", + "integrity": "sha512-jVUILxOqAgcquxbGCz3Bo1/sGEuVLcReGYvo61oJ2EkkyfrlREd7TfLRF6jdF85aEQjxOj/6BD9uj0p+UfXNkw==", + "license": "MIT", + "dependencies": { + "@foliojs-fork/linebreak": "^1.1.2", + "@foliojs-fork/pdfkit": "^0.15.3", + "iconv-lite": "^0.6.3", + "xmldoc": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pdfmake/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -11435,6 +12245,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + }, "node_modules/postcss": { "version": "8.5.2", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", @@ -11596,14 +12411,12 @@ "node_modules/primeicons": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz", - "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==", - "license": "MIT" + "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==" }, "node_modules/primeng": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/primeng/-/primeng-19.1.0.tgz", "integrity": "sha512-xzPOvq6m0nyD3HQpqIgXmfTFqWiI4P9AIraRKjMdfwOruGzpimxf0iYJDgNNvJJvH32DwLZD6OnFTh3PaiP5Fg==", - "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@primeuix/styled": "^0.3.2", "@primeuix/utils": "^0.3.2", @@ -11634,7 +12447,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, "license": "MIT" }, "node_modules/promise-retry": { @@ -11777,7 +12589,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -11788,6 +12599,36 @@ "node": ">= 6" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -11853,6 +12694,26 @@ "dev": true, "license": "MIT" }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpu-core": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", @@ -12155,7 +13016,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -12194,7 +13054,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/sass": { @@ -12263,9 +13122,19 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "dev": true, + "license": "ISC" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", "license": "ISC", - "optional": true + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } }, "node_modules/schema-utils": { "version": "4.3.2", @@ -12516,6 +13385,44 @@ "node": ">= 0.8" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -13065,7 +13972,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -13249,6 +14155,22 @@ "node": ">=10" } }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -13406,6 +14328,12 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", @@ -13459,6 +14387,15 @@ "node": ">=0.6" } }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, "node_modules/tree-dump": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", @@ -13623,6 +14560,16 @@ "node": ">=4" } }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/unicode-property-aliases-ecmascript": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", @@ -13633,6 +14580,22 @@ "node": ">=4" } }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, "node_modules/unicorn-magic": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", @@ -13692,6 +14655,54 @@ "node": ">= 0.8" } }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -13727,7 +14738,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -13744,7 +14754,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "license": "MIT", "bin": { "uuid": "dist/bin/uuid" @@ -14765,7 +15774,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/ws": { @@ -14790,6 +15798,24 @@ } } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/xmldoc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-2.0.1.tgz", + "integrity": "sha512-sOOqgsjl3PU6iBw+fBUGAkTCE+JFK+sBaOL3pnZgzqk2/yvOD7RlFmZtDRJAEBzdpOYxSXyOQH4mjubdfs3MSg==", + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -14917,6 +15943,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/zone.js": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.0.tgz", diff --git a/package.json b/package.json index dc4a87b..cdf5c46 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,14 @@ { - "name": "cronogramas-primeng", - "version": "0.0.0", + "name": "cronogramas", + "version": "0.1.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", + "build:prod": "ng build --configuration production", "watch": "ng build --watch --configuration development", - "test": "ng test" + "test": "ng test", + "postinstall": "node setup-project.js" }, "private": true, "dependencies": { @@ -19,6 +21,9 @@ "@angular/router": "^19.2.0", "@primeng/themes": "^19.1.0", "animate.css": "^4.1.1", + "exceljs": "^4.4.0", + "file-saver": "^2.0.5", + "pdfmake": "^0.2.19", "primeflex": "^4.0.0", "primeicons": "^7.0.0", "primeng": "^19.1.0", @@ -30,7 +35,10 @@ "@angular-devkit/build-angular": "^19.2.9", "@angular/cli": "^19.2.9", "@angular/compiler-cli": "^19.2.0", + "@types/file-saver": "^2.0.7", "@types/jasmine": "~5.1.0", + "@types/pdfmake": "^0.2.11", + "@types/xlsx": "^0.0.35", "jasmine-core": "~5.6.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -38,5 +46,6 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.7.2" - } + }, + "description": "cronogramas - Proyecto generado desde template" } diff --git a/public/img/footer-logo.webp b/public/img/footer-logo.webp new file mode 100644 index 0000000..0b3df45 Binary files /dev/null and b/public/img/footer-logo.webp differ diff --git a/public/img/header2.webp b/public/img/header2.webp new file mode 100644 index 0000000..4328642 Binary files /dev/null and b/public/img/header2.webp differ diff --git a/setup-project.js b/setup-project.js new file mode 100644 index 0000000..3e97b04 --- /dev/null +++ b/setup-project.js @@ -0,0 +1,147 @@ +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +console.log('🚀 Configurando nuevo proyecto basado en el template...'); + +// Preguntar por el nombre del proyecto +rl.question('¿Cuál es el nombre del nuevo proyecto? ', (projectName) => { + + // Modificar package.json + const packageJsonPath = path.join(process.cwd(), 'package.json'); + try { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + // Guardar el nombre original para reportar cambios + const originalName = packageJson.name; + + // Actualizar el nombre del proyecto + packageJson.name = projectName; + + // Actualizar versión y descripción + packageJson.version = '0.1.0'; + packageJson.description = `${projectName} - Proyecto generado desde template`; + + // Guardar los cambios + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); + console.log(`✅ package.json actualizado: nombre cambiado de ${originalName} a ${projectName}`); + } catch (error) { + console.error('❌ Error al modificar package.json:', error); + } + + // Modificar angular.json + const angularJsonPath = path.join(process.cwd(), 'angular.json'); + try { + const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf8')); + + // Obtener el nombre del proyecto original (la primera clave en el objeto projects) + const originalProjectName = Object.keys(angularJson.projects)[0]; + + // Crear nuevo objeto con el nuevo nombre del proyecto + const projectConfig = angularJson.projects[originalProjectName]; + + // Buscar otros nombres de proyectos en el archivo (como en outputPath o en configuraciones) + // Este es un enfoque para encontrar nombres de proyectos "ocultos" en otras partes del archivo + let angularJsonString = JSON.stringify(angularJson); + const possibleProjectNames = new Set(); + + // Buscar patrones como "dist/nombre-proyecto" o "nombre-proyecto:build" + const outputPathRegex = /dist\/([a-zA-Z0-9-_]+)/g; + const buildTargetRegex = /([a-zA-Z0-9-_]+):build/g; + + let match; + while (match = outputPathRegex.exec(angularJsonString)) { + possibleProjectNames.add(match[1]); + } + + while (match = buildTargetRegex.exec(angularJsonString)) { + possibleProjectNames.add(match[1]); + } + + // Actualizar el outputPath + if (projectConfig.architect?.build?.options?.outputPath) { + const oldOutputPath = projectConfig.architect.build.options.outputPath; + projectConfig.architect.build.options.outputPath = `dist/${projectName}`; + console.log(`✅ Actualizado outputPath: de "${oldOutputPath}" a "dist/${projectName}"`); + } + + // Actualizar referencias en las configuraciones de serve + if (projectConfig.architect?.serve?.configurations) { + Object.keys(projectConfig.architect.serve.configurations).forEach(configKey => { + const config = projectConfig.architect.serve.configurations[configKey]; + if (config.buildTarget) { + // Buscar el patrón "nombreProyecto:build:config" + const parts = config.buildTarget.split(':'); + if (parts.length === 3) { + const oldValue = config.buildTarget; + config.buildTarget = `${projectName}:${parts[1]}:${parts[2]}`; + console.log(`✅ Actualizada configuración de serve '${configKey}': de "${oldValue}" a "${config.buildTarget}"`); + } + } + }); + } + + // Buscar y reemplazar todas las posibles ocurrencias de otros nombres de proyectos + possibleProjectNames.forEach(oldName => { + if (oldName !== originalProjectName && oldName !== projectName) { + // Convertir a string para hacer reemplazos globales + const jsonStr = JSON.stringify(projectConfig); + if (jsonStr.includes(oldName)) { + const updatedJsonStr = jsonStr.replace(new RegExp(oldName, 'g'), projectName); + // Convertir de vuelta a objeto + const updatedConfig = JSON.parse(updatedJsonStr); + // Reemplazar la configuración con la versión actualizada + Object.assign(projectConfig, updatedConfig); + console.log(`✅ Reemplazado nombre de proyecto adicional: "${oldName}" por "${projectName}"`); + } + } + }); + + // Eliminar la entrada original + delete angularJson.projects[originalProjectName]; + + // Agregar nueva entrada con el nombre del proyecto + angularJson.projects[projectName] = projectConfig; + + // Actualizar defaultProject si existe + if (angularJson.defaultProject === originalProjectName) { + angularJson.defaultProject = projectName; + } + + // Actualizar rutas dentro de la configuración + if (projectConfig.root === originalProjectName) { + projectConfig.root = projectName; + } + + // Guardar los cambios + fs.writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2)); + console.log(`✅ angular.json actualizado: nombre del proyecto cambiado de ${originalProjectName} a ${projectName}`); + } catch (error) { + console.error('❌ Error al modificar angular.json:', error); + } + + // Modificar también src/index.html para actualizar el título + try { + const indexHtmlPath = path.join(process.cwd(), 'src', 'index.html'); + let indexHtml = fs.readFileSync(indexHtmlPath, 'utf8'); + + // Reemplazar el título + indexHtml = indexHtml.replace(/.*<\/title>/, `<title>${projectName}`); + + // Guardar los cambios + fs.writeFileSync(indexHtmlPath, indexHtml); + console.log('✅ src/index.html actualizado: título actualizado'); + } catch (error) { + console.error('❌ Error al modificar src/index.html:', error); + } + + console.log('\n🎉 Configuración completada! El proyecto está listo para ser usado.'); + console.log('Ejecuta `ng serve` para iniciar el servidor de desarrollo.'); + + rl.close(); +}); \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..ac10cb9 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,47 @@ +# # Información del proyecto +# sonar.projectKey=Cronogramas-siis-angular-primeng +# sonar.projectName=Cronogramas-siis-angular-primeng +# sonar.projectVersion=1.0.0 + +# # Ruta del código fuente +# sonar.sources=src +# sonar.exclusions=**/node_modules/**,**/*.spec.ts,**/environments/**,**/assets/** + +# # Configuración TypeScript +# sonar.typescript.lcov.reportPaths=coverage/lcov.info +# sonar.javascript.lcov.reportPaths=coverage/lcov.info + +# # URL de SonarQube y token de autenticación +# sonar.host.url=https://sonar.valposystems.com/ +# sonar.token=sqp_487feb210e11c5b295651af4436a265b335cc063 +# sonar.scanner.responseTimeout=300 +# sonar.internal.analysis.failFast=false + +# sonar-scanner \ +# -Dsonar.projectKey=cronogramas-valposystems \ +# -Dsonar.sources=. \ +# -Dsonar.host.url=https://sonarqubelts-community-production-662c.up.railway.app \ +# -Dsonar.login=sqp_a371c9d9d6b0099fd6287be83496cd3c16b3674f + + +# Información del proyecto +sonar.projectKey=cronogramas-valposystems +sonar.projectName=cronogramas-valposystems +sonar.projectVersion=1.0.0 + +# Ruta del código fuente +sonar.sources=src +sonar.exclusions=**/node_modules/**,**/*.spec.ts,**/environments/**,**/assets/** + +# Configuración TypeScript +sonar.typescript.lcov.reportPaths=coverage/lcov.info +sonar.javascript.lcov.reportPaths=coverage/lcov.info + +# URL de SonarQube y token de autenticación +sonar.host.url=https://sonar.lcespedes.dev/ +# Usa sonar.token en lugar de sonar.login +sonar.login=sqp_a371c9d9d6b0099fd6287be83496cd3c16b3674f + +# Configuración de timeout y análisis +sonar.scanner.responseTimeout=300 +sonar.internal.analysis.failFast=false \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index f2dc9c1..7d0c903 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,2 +1,3 @@ + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 86a8115..b712096 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,11 +1,14 @@ import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; +import { ConfirmationService } from 'primeng/api'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterOutlet, ConfirmDialogModule], templateUrl: './app.component.html', - styleUrl: './app.component.scss' + styleUrl: './app.component.scss', + providers: [ConfirmationService] }) export class AppComponent { title = 'SACG - Sistema Administrador de Cronogramas'; diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 79b54c7..39d7ce9 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,20 +1,31 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router'; +import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http'; import { routes } from './app.routes'; import { provideAnimations } from '@angular/platform-browser/animations'; import { providePrimeNG } from 'primeng/config'; import Aura from '@primeng/themes/aura'; +import { authInterceptor } from './interceptors/auth.interceptor'; +import { MessageService } from 'primeng/api'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes), + provideRouter( + routes, + withPreloading(PreloadAllModules) + ), provideAnimations(), + provideHttpClient(withFetch(), withInterceptors([authInterceptor])), providePrimeNG({ theme: { - preset: Aura + preset: Aura, + options: { + darkModeSelector: false || 'none' + } } - }) + }), + MessageService ] }; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index a127a54..04c92d5 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,9 +1,54 @@ -import { Routes } from '@angular/router'; +import { Routes, PreloadAllModules } from '@angular/router'; import { LoginComponent } from './pages/login/login.component'; - +import { LayoutComponent } from './components/layout/layout.component'; +import { authGuard } from './guards/auth.guard'; +import { NotFoundComponent } from './pages/not-found/not-found.component'; export const routes: Routes = [ - { path: '', redirectTo: 'login', pathMatch: 'full' }, - { path: 'login', component: LoginComponent }, - { path: '**', redirectTo: 'login' } + { path: 'login', component: LoginComponent }, + { + path: '', + component: LayoutComponent, + canActivate: [authGuard], + children: [ + { path: '', redirectTo: 'inicio', pathMatch: 'full' }, + { + path: 'inicio', + loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent), + data: { title: 'Inicio' } + }, + { + path: 'unidad-concesiones', + loadComponent: () => import('./pages/concesiones/concesiones.component').then(m => m.ConcesionesComponent), + data: { title: 'Unidad de Concesiones' } + }, + { + path: 'ct-actualizacion', + loadComponent: () => import('./pages/actualizacion-pd/actualizacion-pd.component').then(m => m.ActualizacionPdComponent), + data: { title: 'Cronograma temporal por actualización de PD' } + }, + { + path: 'ct-ajuste', + loadComponent: () => import('./pages/ajuste-pd/ajuste-pd.component').then(m => m.AjustePdComponent), + data: { title: 'Cronograma temporal por ajuste de PD' } + }, + { + path: 'resumen', + loadComponent: () => import('./pages/resumen/resumen.component').then(m => m.ResumenComponent), + data: { title: 'Resumen' } + }, + { + path: 'unidad-informacion', + loadComponent: () => import('./pages/unidad-informacion/unidad-informacion.component').then(m => m.UnidadInformacionComponent), + data: { title: 'Unidad de Información' } + }, + { + path: '404', + component: NotFoundComponent, + data: { title: 'Error 404' } + }, + ] + }, + { path: '**', redirectTo: '404' } + ]; diff --git a/src/app/components/alert-dialog/alert-dialog.component.html b/src/app/components/alert-dialog/alert-dialog.component.html new file mode 100644 index 0000000..1198ea0 --- /dev/null +++ b/src/app/components/alert-dialog/alert-dialog.component.html @@ -0,0 +1,28 @@ + + +
+ +
{{ title }}
+
{{ message }}
+ +
+ + +
+
+
\ No newline at end of file diff --git a/src/app/components/alert-dialog/alert-dialog.component.scss b/src/app/components/alert-dialog/alert-dialog.component.scss new file mode 100644 index 0000000..912eda7 --- /dev/null +++ b/src/app/components/alert-dialog/alert-dialog.component.scss @@ -0,0 +1,11 @@ +:host ::ng-deep { + .alert-dialog { + .p-dialog-header { + padding-bottom: 0.5rem; + } + + .p-dialog-content { + overflow-y: visible; + } + } +} \ No newline at end of file diff --git a/src/app/components/alert-dialog/alert-dialog.component.ts b/src/app/components/alert-dialog/alert-dialog.component.ts new file mode 100644 index 0000000..9fd76e4 --- /dev/null +++ b/src/app/components/alert-dialog/alert-dialog.component.ts @@ -0,0 +1,75 @@ +import { Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { DialogModule } from 'primeng/dialog'; +import { ButtonModule } from 'primeng/button'; +import { Subject } from 'rxjs'; + +export interface AlertDialogOptions { + visible: boolean; + title: string; + message: string; + icon: 'success' | 'error' | 'warning' | 'info' | 'question'; + showConfirmButton: boolean; + confirmButtonText: string; + showCancelButton: boolean; + cancelButtonText: string; +} + +@Component({ + selector: 'app-alert-dialog', + standalone: true, + imports: [CommonModule, DialogModule, ButtonModule], + templateUrl: './alert-dialog.component.html', + styleUrls: ['./alert-dialog.component.scss'] +}) +export class AlertDialogComponent { + visible = false; + title = ''; + message = ''; + icon: 'success' | 'error' | 'warning' | 'info' | 'question' = 'success'; + showConfirmButton = true; + confirmButtonText = 'Aceptar'; + showCancelButton = false; + cancelButtonText = 'Cancelar'; + + private confirmSubject = new Subject(); + public onConfirm = this.confirmSubject.asObservable(); + + show(options: Partial = {}): void { + this.visible = true; + this.title = options.title || this.title; + this.message = options.message || this.message; + this.icon = options.icon || this.icon; + this.showConfirmButton = options.showConfirmButton !== undefined ? options.showConfirmButton : this.showConfirmButton; + this.confirmButtonText = options.confirmButtonText || this.confirmButtonText; + this.showCancelButton = options.showCancelButton !== undefined ? options.showCancelButton : this.showCancelButton; + this.cancelButtonText = options.cancelButtonText || this.cancelButtonText; + } + + confirm(): void { + this.visible = false; + this.confirmSubject.next(true); + } + + cancel(): void { + this.visible = false; + this.confirmSubject.next(false); + } + + getIconClass(): string { + switch (this.icon) { + case 'success': + return 'pi-check-circle text-green-500'; + case 'error': + return 'pi-times-circle text-red-500'; + case 'warning': + return 'pi-exclamation-triangle text-yellow-500'; + case 'info': + return 'pi-info-circle text-blue-500'; + case 'question': + return 'pi-question-circle text-purple-500'; + default: + return 'pi-check-circle text-green-500'; + } + } +} \ No newline at end of file diff --git a/src/app/components/footer/footer.component.html b/src/app/components/footer/footer.component.html new file mode 100644 index 0000000..e7f39f3 --- /dev/null +++ b/src/app/components/footer/footer.component.html @@ -0,0 +1,18 @@ + +
+ +
diff --git a/src/app/components/footer/footer.component.scss b/src/app/components/footer/footer.component.scss new file mode 100644 index 0000000..1138784 --- /dev/null +++ b/src/app/components/footer/footer.component.scss @@ -0,0 +1,50 @@ +/* Footer Styles */ +.footer { + background-color: #0088cc; + color: white; + padding: 1rem 2rem; +} + +.footer-content { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 1200px; + margin: 0 auto; +} + +.footer-left, .footer-right { + display: flex; + align-items: center; + font-size: 0.8rem; +} + +.footer-left { + gap: 0.5rem; +} + +.footer-text { + display: flex; + flex-direction: column; +} + +.footer-logo { + height: 40px; +} + +.footer-right { + flex-direction: column; + align-items: flex-end; +} + +/* Responsive styles */ +@media screen and (max-width: 768px) { + .footer-content { + flex-direction: column; + gap: 1rem; + } + + .footer-right { + align-items: center; + } +} diff --git a/src/app/components/footer/footer.component.spec.ts b/src/app/components/footer/footer.component.spec.ts new file mode 100644 index 0000000..3f93915 --- /dev/null +++ b/src/app/components/footer/footer.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FooterComponent } from './footer.component'; + +describe('FooterComponent', () => { + let component: FooterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FooterComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FooterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/footer/footer.component.ts b/src/app/components/footer/footer.component.ts new file mode 100644 index 0000000..2e208c3 --- /dev/null +++ b/src/app/components/footer/footer.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-footer', + imports: [], + templateUrl: './footer.component.html', + styleUrl: './footer.component.scss' +}) +export class FooterComponent { + +} diff --git a/src/app/components/layout/layout.component.html b/src/app/components/layout/layout.component.html new file mode 100644 index 0000000..282f954 --- /dev/null +++ b/src/app/components/layout/layout.component.html @@ -0,0 +1,26 @@ + +
+ + + + + + + + + +
+ + + + +
+ +
+ + + +
+
\ No newline at end of file diff --git a/src/app/components/layout/layout.component.scss b/src/app/components/layout/layout.component.scss new file mode 100644 index 0000000..21f63cf --- /dev/null +++ b/src/app/components/layout/layout.component.scss @@ -0,0 +1,115 @@ +/* src/app/components/layout/layout.component.scss */ +.layout-wrapper { + display: flex; + min-height: 100vh; +} + +.sidebar-wrapper { + flex: 0 0 250px; + width: 250px; + position: fixed; + height: 100vh; + z-index: 100; + box-shadow: 0 0 10px rgba(0,0,0,0.1); + background-color: #0088cc; + transition: transform 0.3s ease; + + @media (max-width: 768px) { + /* En pantallas pequeñas, asegurarse que esté fuera de la vista cuando no es visible */ + &:not(.sidebar-visible) { + transform: translateX(-100%); + width: 0; + } + } +} + +/* Para todas las pantallas cuando el sidebar está oculto */ +.sidebar-wrapper:not(.sidebar-visible) { + transform: translateX(-250px); +} + +.main-content-wrapper { + flex: 1; + display: flex; + flex-direction: column; + min-height: 100vh; + background-color: #f8f9fa; + transition: margin-left 0.3s ease; + margin-left: 0; + width: 100%; +} + +/* Solo en pantallas grandes (>992px) ajustar el margen cuando el sidebar está visible */ +@media (min-width: 993px) { + .main-content-wrapper.with-sidebar { + margin-left: 250px; + width: calc(100% - 250px); + } +} + +/* Cuando el sidebar está oculto, ajustar el margen */ +.sidebar-wrapper:not(.sidebar-visible) ~ .main-content-wrapper { + margin-left: 0; + width: 100%; +} + +.page-content { + flex: 1; + padding: 0; + background-color: #f8f9fa; +} + +.footer { + display: flex; + justify-content: space-between; + background-color: #0088cc; + color: white; + padding: 0.75rem 1rem; + font-size: 0.8rem; +} + +.footer-left, .footer-right { + display: flex; + flex-direction: column; +} + +/* Ajustes para pantallas pequeñas y tablets */ +@media (max-width: 992px) { + .sidebar-wrapper { + transform: translateX(-100%); + width: 250px; + z-index: 1030; /* Z-index mayor para asegurar que esté sobre el contenido */ + position: fixed; + } + + .main-content-wrapper { + margin-left: 0; + width: 100%; + } + + .sidebar-wrapper.sidebar-visible { + transform: translateX(0); + } + + /* Overlay para cuando el sidebar está visible en pantallas pequeñas */ + body.sidebar-visible::before { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1020; + } + + .sidebar-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1025; + background-color: rgba(0, 0, 0, 0.5); + } +} \ No newline at end of file diff --git a/src/app/components/layout/layout.component.spec.ts b/src/app/components/layout/layout.component.spec.ts new file mode 100644 index 0000000..09aa9fc --- /dev/null +++ b/src/app/components/layout/layout.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LayoutComponent } from './layout.component'; + +describe('LayoutComponent', () => { + let component: LayoutComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LayoutComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LayoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/layout/layout.component.ts b/src/app/components/layout/layout.component.ts new file mode 100644 index 0000000..b38e6a7 --- /dev/null +++ b/src/app/components/layout/layout.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit, HostListener } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { NavbarComponent } from '../navbar/navbar.component'; +import { SidebarComponent } from '../sidebar/sidebar.component'; +import { FooterComponent } from "../footer/footer.component"; +import { RouteAnimationsComponent } from '../route-animations/route-animations.component'; +import { ToastModule } from 'primeng/toast'; +import { MessageService } from 'primeng/api'; + +@Component({ + selector: 'app-layout', + imports: [ + CommonModule, + RouterModule, + NavbarComponent, + SidebarComponent, + FooterComponent, + RouteAnimationsComponent, + ToastModule + ], + templateUrl: './layout.component.html', + styleUrl: './layout.component.scss', + standalone: true +}) +export class LayoutComponent implements OnInit { + isSidebarVisible: boolean = true; + window = window; // Exposición de window para su uso en el template + private readonly MOBILE_BREAKPOINT = 991; // Breakpoint para ocultar sidebar automáticamente + + ngOnInit() { + // Comprobar el ancho inicial y ajustar el sidebar + this.checkScreenSize(); + } + + @HostListener('window:resize', ['$event']) + onResize() { + this.checkScreenSize(); + } + + private checkScreenSize() { + // Si la pantalla es menor a 420px, ocultar el sidebar automáticamente + if (window.innerWidth < this.MOBILE_BREAKPOINT) { + this.isSidebarVisible = false; + } else if (window.innerWidth >= 992) { + // En pantallas grandes, mostrar el sidebar por defecto + this.isSidebarVisible = true; + } + // Entre 420px y 992px mantener el estado actual (toggle manual) + } + + toggleSidebar() { + this.isSidebarVisible = !this.isSidebarVisible; + + // Aplicar clase al body para todas las resoluciones + document.body.classList.toggle('sidebar-visible', this.isSidebarVisible); + + // Si cerramos el sidebar en resolución pequeña, forzar que el contenido no tenga margen + if (!this.isSidebarVisible && window.innerWidth <= 992) { + setTimeout(() => { + document.querySelector('.main-content-wrapper')?.classList.remove('with-sidebar'); + }, 10); + } + } +} diff --git a/src/app/components/navbar/navbar.component.html b/src/app/components/navbar/navbar.component.html new file mode 100644 index 0000000..f01f31b --- /dev/null +++ b/src/app/components/navbar/navbar.component.html @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/src/app/components/navbar/navbar.component.scss b/src/app/components/navbar/navbar.component.scss new file mode 100644 index 0000000..3d8fcdd --- /dev/null +++ b/src/app/components/navbar/navbar.component.scss @@ -0,0 +1,131 @@ +.navbar-container { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #bcdaef; + height: 48px; + padding: 0 1rem; + width: 100%; + } + + .navbar-left { + display: flex; + align-items: center; + } + + .sidebar-toggle { + color: #343a40; + margin-right: 0.5rem; + } + + .app-title { + font-weight: bold; + color: #0a2847; + font-size: 1rem; + } + + .navbar-right { + display: flex; + align-items: center; + } + + .user-name { + margin-right: 0.5rem; + font-size: 0.9rem; + color: #0a2847; + } + + .logout-button { + color: #0a2847; + } + + /* Page header styles */ + .page-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + border-bottom: 1px solid #dee2e6; + background-color: #fff; + } + + .page-title { + font-size: 1.25rem; + font-weight: 500; + color: #495057; + } + + .breadcrumb-container { + font-size: 0.875rem; + color: #6c757d; + } + + .breadcrumb-link { + color: #0088cc; + text-decoration: none; + } + + .breadcrumb-link:hover { + text-decoration: underline; + } + + .breadcrumb-separator { + margin: 0 0.25rem; + } + + .breadcrumb-current { + color: #6c757d; + } + + /* Sobrescribir estilos de PrimeNG para los botones en el navbar */ + :host ::ng-deep .p-button.p-button-text { + padding: 0.5rem; + color: #0a2847; + + &:focus { + box-shadow: none; + } + + .p-button-icon { + font-size: 1rem; + } + } + + /* Responsive para contraer navbar en md y sm */ +@media (max-width: 991.98px) { + /* .navbar-left, */ + .navbar-right { + width: 100%; + justify-content: space-between; + margin-bottom: 0.5rem; + } + + .navbar-right { + justify-content: flex-end; + } + + .app-title { + width: 208px; + max-width: 100%; + white-space: normal; + } +} + +/* Responsive para ocultar título para resoluciones sm */ + @media (max-width: 767.98px) { + .app-title { + display: none; + } + } + + .logo-img { + max-height: 50px; + width: auto; + opacity: 0.8; + } + + @media (max-width: 576px) { + .logo-img { + max-height: 35px; + } + } diff --git a/src/app/components/navbar/navbar.component.spec.ts b/src/app/components/navbar/navbar.component.spec.ts new file mode 100644 index 0000000..cfedf9e --- /dev/null +++ b/src/app/components/navbar/navbar.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavbarComponent } from './navbar.component'; + +describe('NavbarComponent', () => { + let component: NavbarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NavbarComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NavbarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/navbar/navbar.component.ts b/src/app/components/navbar/navbar.component.ts new file mode 100644 index 0000000..713e70b --- /dev/null +++ b/src/app/components/navbar/navbar.component.ts @@ -0,0 +1,95 @@ +import { Component, EventEmitter, Output, OnInit } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { ButtonModule } from 'primeng/button'; +import { ConfirmationService, MessageService } from 'primeng/api'; +import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; +import { filter, map, mergeMap } from 'rxjs/operators'; +import { CommonModule } from '@angular/common'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; +import { ToastModule } from 'primeng/toast'; +import { AuthService } from '../../services/auth.service'; + +@Component({ + selector: 'app-navbar', + imports: [ + CommonModule, + RouterLink, + ButtonModule, + ConfirmDialogModule, + ToastModule + ], + providers: [ConfirmationService, MessageService], + templateUrl: './navbar.component.html', + styleUrl: './navbar.component.scss', + standalone: true +}) +export class NavbarComponent implements OnInit { + @Output() sidebarToggle = new EventEmitter(); + pageTitle: string = 'Starter Pages'; + userName: string = ''; + + constructor( + private confirmationService: ConfirmationService, + private messageService: MessageService, + private router: Router, + private activatedRoute: ActivatedRoute, + private authService: AuthService + ) { + this.router.events.pipe( + filter(event => event instanceof NavigationEnd), + map(() => { + let route = this.activatedRoute; + while (route.firstChild) { + route = route.firstChild; + } + return route; + }), + mergeMap(route => route.data) + ).subscribe(data => { + this.pageTitle = data['title'] || 'Sin título'; + }); + } + + ngOnInit() { + // Obtener nombre de usuario del usuario logueado + this.authService.user$.subscribe(user => { + if (user) { + this.userName = user.name || user.username || 'Usuario'; + } else { + this.userName = 'Usuario'; + } + }); + } + + toggleSidebar() { + this.sidebarToggle.emit(); + } + + confirmarAccion() { + this.confirmationService.confirm({ + message: '¿Estás seguro de que deseas cerrar sesión?', + header: 'Cerrar Sesión', + icon: 'pi pi-sign-out', + acceptLabel: 'Sí, cerrar', + rejectLabel: 'Cancelar', + acceptButtonStyleClass: 'p-button-danger', + rejectButtonStyleClass: 'p-button-secondary', + accept: () => { + this.logout(); + }, + reject: () => { + console.log('Canceló cierre de sesión'); + } + }); + } + + logout() { + this.authService.logout(); + this.messageService.add({ + severity: 'success', + summary: 'Sesión cerrada', + detail: 'Has cerrado sesión exitosamente' + }); + this.router.navigate(['/login']); + } +} diff --git a/src/app/components/route-animations/route-animations.component.ts b/src/app/components/route-animations/route-animations.component.ts new file mode 100644 index 0000000..b4c5402 --- /dev/null +++ b/src/app/components/route-animations/route-animations.component.ts @@ -0,0 +1,59 @@ +import { Component, OnDestroy } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router, RouterOutlet, NavigationEnd, Event } from '@angular/router'; +import { Subscription, filter } from 'rxjs'; + +@Component({ + selector: 'app-route-animations', + standalone: true, + imports: [CommonModule, RouterOutlet], + template: ` +
+ +
+ `, + styles: [` + .content-container { + width: 100%; + height: 100%; + } + + .animate__animated { + animation-duration: 1.2s; + } + `] +}) +export class RouteAnimationsComponent implements OnDestroy { + // Clase de animación actual + animationClass: string = 'animate__animated animate__fadeIn '; + + // Animaciones disponibles + private animations: string[] = [ + 'animate__fadeIn', + ]; + + private routerSub: Subscription; + + constructor(private router: Router) { + // Suscribirse a los eventos de navegación + this.routerSub = this.router.events + .pipe(filter((event: Event) => event instanceof NavigationEnd)) + .subscribe(() => { + // Resetear la animación primero + this.animationClass = ''; + + // Aplicar una nueva animación después de un breve retraso + setTimeout(() => { + const randomIndex = Math.floor(Math.random() * this.animations.length); + this.animationClass = `animate__animated ${this.animations[randomIndex]} `; + }, 50); + }); + } + + ngOnDestroy(): void { + // Cancelar suscripción para evitar memory leaks + if (this.routerSub) { + this.routerSub.unsubscribe(); + } + } +} \ No newline at end of file diff --git a/src/app/components/sidebar/sidebar.component.html b/src/app/components/sidebar/sidebar.component.html new file mode 100644 index 0000000..8811b3e --- /dev/null +++ b/src/app/components/sidebar/sidebar.component.html @@ -0,0 +1,68 @@ + \ No newline at end of file diff --git a/src/app/components/sidebar/sidebar.component.scss b/src/app/components/sidebar/sidebar.component.scss new file mode 100644 index 0000000..cd8a21a --- /dev/null +++ b/src/app/components/sidebar/sidebar.component.scss @@ -0,0 +1,218 @@ +.sidebar { + width: 250px; + height: 100%; + background-color: #bcdaef; + display: flex; + flex-direction: column; +} + +.logo-container { + text-align: center; +} + +.logo-image-container { + position: relative; + display: inline-block; + margin-bottom: 0.5rem; +} + +.logo-image { + width: 100%; + height: auto; + display: block; + margin: 0 auto; +} + +.version-badge { + position: absolute; + top: 0; + right: 20px; + background-color: #0088cc; + color: white; + border-radius: 50%; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.7rem; + font-weight: bold; +} + +.logo-text { + font-size: 1.5rem; + font-weight: bold; + color: #0a2847; + margin-bottom: 0.25rem; +} + +.logo-subtitle { + font-size: 0.7rem; + color: #0a2847; + max-width: 180px; + margin: 0 auto; + line-height: 1.2; +} + +.separator { + height: 1px; + background-color: #a3c5e6; + margin: 0 0.5rem; +} + +.menu-container { + padding: 0.5rem 0; + flex: 1; + overflow-y: auto; + height: 100%; +} + +.sidebar-menu { + list-style: none; + padding: 0; + margin: 0; +} + +.menu-item { + margin: 2px 0; + padding: 0 0.5rem; /* Añadimos padding lateral al ítem */ + transition: all 0.3s ease-in-out; /* Suaviza todas las transiciones */ +} + +.menu-link { + display: flex; + align-items: center; + padding: 0.75rem 1rem; + color: #0a2847; + text-decoration: none; + transition: all 0.3s ease-in-out; /* Transición para todos los cambios */ + border-left: 3px solid transparent; + border-radius: .25rem; + width: calc(100% - 1rem); /* Reducimos el ancho para crear margen */ + position: relative; /* Para los efectos de pseudo-elementos */ +} + +.menu-link:hover { + background-color: #a2c9ec !important; + transform: translateX(2px); /* Pequeño movimiento al hacer hover */ +} + +.menu-icon { + margin-right: 0.75rem; + width: 1.25rem; + text-align: center; + color: #0a2847; + transition: transform 0.3s ease-in-out; /* Transición para el icono */ +} + +.menu-text { + font-size: 0.9rem; + transition: font-weight 0.3s ease-in-out, color 0.3s ease-in-out; /* Transición para el texto */ +} + +/* Efectos especiales para los ítems activos */ +.menu-item.active { + padding-left: 0.75rem; /* Añade un poco más de padding a la izquierda */ +} + +.menu-item.active .menu-icon { + transform: scale(1.2); /* Hace el icono ligeramente más grande */ + color: #0066cc; /* Color más brillante para el icono */ +} + +/* Indicador de elemento activo */ +.active-indicator { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + width: 6px; + height: 6px; + border-radius: 50%; + opacity: 0; + transition: all 0.3s ease-in-out; +} + +.menu-item.active .active-indicator { + opacity: 1; + background-color: #0066cc; + box-shadow: 0 0 8px 1px rgba(0, 102, 204, 0.8); + animation: pulse 1.5s infinite ease-in-out; +} + +@keyframes pulse { + 0% { + transform: translateY(-50%) scale(0.8); + opacity: 0.7; + } + 50% { + transform: translateY(-50%) scale(1.2); + opacity: 1; + } + 100% { + transform: translateY(-50%) scale(0.8); + opacity: 0.7; + } +} + +.menu-item.active .menu-link { + background: linear-gradient(90deg, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 100%) !important; + border-left: 3px solid white !important; + color: #706f6f !important; + font-weight: 600; + box-shadow: 0 1px 3px rgba(0,0,0,.12), 0 1px 2px rgba(0,0,0,.24) !important; + margin: 0 auto; /* Centra el elemento */ + /* Añadir animación de entrada */ + animation: activeItemEffect 0.4s ease-in-out, glowEffect 2s ease-in-out infinite alternate; + position: relative; /* Para el efecto de resplandor */ +} + +/* Definir la animación para el elemento activo - movimiento lateral */ +@keyframes activeItemEffect { + 0% { + transform: translateX(-10px); + opacity: 0.5; + } + 50% { + transform: translateX(5px); + opacity: 0.8; + } + 100% { + transform: translateX(0); + opacity: 1; + } +} + +/* Efecto de resplandor suave */ +@keyframes glowEffect { + 0% { + box-shadow: 0 1px 3px rgba(0,0,0,.12), 0 1px 2px rgba(0,0,0,.24); + } + 100% { + box-shadow: 0 0 5px rgba(255,255,255,0.8), 0 0 10px rgba(10, 40, 71, 0.3); + } +} + +/* Añadir pseudo-elemento para un brillo en el borde izquierdo */ +.menu-item.active .menu-link::before { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 3px; + background-color: white; + animation: borderPulse 1.5s ease-in-out infinite; +} + +@keyframes borderPulse { + 0% { + opacity: 0.6; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.6; + } +} \ No newline at end of file diff --git a/src/app/components/sidebar/sidebar.component.spec.ts b/src/app/components/sidebar/sidebar.component.spec.ts new file mode 100644 index 0000000..5445f3c --- /dev/null +++ b/src/app/components/sidebar/sidebar.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SidebarComponent } from './sidebar.component'; + +describe('SidebarComponent', () => { + let component: SidebarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SidebarComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SidebarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/sidebar/sidebar.component.ts b/src/app/components/sidebar/sidebar.component.ts new file mode 100644 index 0000000..de38495 --- /dev/null +++ b/src/app/components/sidebar/sidebar.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { RouterLink, RouterLinkActive } from '@angular/router'; +import { PrimeIcons } from 'primeng/api'; + +@Component({ + selector: 'app-sidebar', + imports: [RouterLink, RouterLinkActive], + templateUrl: './sidebar.component.html', + styleUrl: './sidebar.component.scss', + standalone: true +}) +export class SidebarComponent { + +} diff --git a/src/app/components/visor-pdf/visor-pdf.component.html b/src/app/components/visor-pdf/visor-pdf.component.html new file mode 100644 index 0000000..c7508be --- /dev/null +++ b/src/app/components/visor-pdf/visor-pdf.component.html @@ -0,0 +1,21 @@ +
+
+ +
+
+ + +
+
\ No newline at end of file diff --git a/src/app/components/visor-pdf/visor-pdf.component.scss b/src/app/components/visor-pdf/visor-pdf.component.scss new file mode 100644 index 0000000..5e1956f --- /dev/null +++ b/src/app/components/visor-pdf/visor-pdf.component.scss @@ -0,0 +1,22 @@ +.pdf-container { + display: flex; + flex-direction: column; + height: 100%; + } + + .toolbar { + display: flex; + justify-content: flex-end; + margin-top: 10px; + gap: 10px; + } + + .pdf-viewer { + flex: 1; + border: 1px solid #ccc; + background-color: #f5f5f5; + } + + iframe { + border: none; + } \ No newline at end of file diff --git a/src/app/components/visor-pdf/visor-pdf.component.spec.ts b/src/app/components/visor-pdf/visor-pdf.component.spec.ts new file mode 100644 index 0000000..254e7bc --- /dev/null +++ b/src/app/components/visor-pdf/visor-pdf.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VisorPdfComponent } from './visor-pdf.component'; + +describe('VisorPdfComponent', () => { + let component: VisorPdfComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [VisorPdfComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(VisorPdfComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/visor-pdf/visor-pdf.component.ts b/src/app/components/visor-pdf/visor-pdf.component.ts new file mode 100644 index 0000000..538f82b --- /dev/null +++ b/src/app/components/visor-pdf/visor-pdf.component.ts @@ -0,0 +1,73 @@ +import { Component, OnInit, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { DynamicDialogRef, DynamicDialogConfig } from 'primeng/dynamicdialog'; +import { SafePipe } from '../../../pipes/safe.pipe'; +import { ButtonModule } from 'primeng/button'; +import { ToastModule } from 'primeng/toast'; +import { ProgressSpinnerModule } from 'primeng/progressspinner'; +import { MessageService } from 'primeng/api'; + +import { PdfService } from '../../services/pdf.service'; +import { AlertService } from '../../services/alert.service'; + +@Component({ + selector: 'app-visor-pdf', + standalone: true, + imports: [ + CommonModule, + SafePipe, + ButtonModule, + ToastModule, + ProgressSpinnerModule + ], + templateUrl: './visor-pdf.component.html', + styleUrls: ['./visor-pdf.component.scss'] +}) +export class VisorPdfComponent implements OnInit { + product: any; + pdfSrc: string = ''; + enviando: boolean = false; + + // Inyectar servicios usando la nueva sintaxis de Angular + private dialogRef = inject(DynamicDialogRef); + private config = inject(DynamicDialogConfig); + private messageService = inject(MessageService); + private pdfService = inject(PdfService); + private alertService = inject(AlertService); + + ngOnInit() { + // Obtener el producto pasado a través del servicio de diálogo + this.product = this.config.data?.product; + + // Generar el PDF + this.generarPDF(); + } + + generarPDF() { + this.pdfService.generateCronogramaPdf(this.product).then(dataUrl => { + this.pdfSrc = dataUrl; + }); + } + + descargarPDF() { + this.pdfService.downloadCronogramaPdf('cronograma', this.product); + } + + enviarPDF() { + this.enviando = true; + console.log('Enviando PDF...'); + + setTimeout(() => { + this.enviando = false; + console.log('Mostrando mensaje de envio...') + this.alertService.success( + 'El cronograma ha sido enviado correctamente a la plataforma.', + '¡Enviado con éxito!' + ); + }, 2000); + } + + cerrar() { + this.dialogRef.close(); + } +} \ No newline at end of file diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts new file mode 100644 index 0000000..c21a7e0 --- /dev/null +++ b/src/app/guards/auth.guard.ts @@ -0,0 +1,16 @@ +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { AuthService } from '../services/auth.service'; + +export const authGuard: CanActivateFn = () => { + const authService = inject(AuthService); + const router = inject(Router); + + if (authService.isLoggedIn()) { + return true; + } + + // Redirect to login if not authenticated + router.navigate(['/login']); + return false; +}; \ No newline at end of file diff --git a/src/app/interceptors/auth.interceptor.ts b/src/app/interceptors/auth.interceptor.ts new file mode 100644 index 0000000..858d5d8 --- /dev/null +++ b/src/app/interceptors/auth.interceptor.ts @@ -0,0 +1,46 @@ +import { inject } from '@angular/core'; +import { + HttpRequest, + HttpHandlerFn, + HttpInterceptorFn, + HttpErrorResponse +} from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { AuthService } from '../services/auth.service'; +import { Router } from '@angular/router'; + +export const authInterceptor: HttpInterceptorFn = ( + request: HttpRequest, + next: HttpHandlerFn +): Observable => { + const authService = inject(AuthService); + const router = inject(Router); + + // Get the auth token + const token = authService.getToken(); + + // Clone the request and add the token if it exists + if (token) { + const authRequest = request.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + + // Handle the authenticated request + return next(authRequest).pipe( + catchError((error: HttpErrorResponse) => { + // Handle 401 Unauthorized errors by logging out and redirecting to login + if (error.status === 401) { + authService.logout(); + router.navigate(['/login']); + } + return throwError(() => error); + }) + ); + } + + // If no token, just pass the request through + return next(request); +}; \ No newline at end of file diff --git a/src/app/models/actualizacion-pd.model.ts b/src/app/models/actualizacion-pd.model.ts new file mode 100644 index 0000000..90ff6d8 --- /dev/null +++ b/src/app/models/actualizacion-pd.model.ts @@ -0,0 +1,10 @@ +import { Cronograma } from './cronograma.model'; + +export interface ActualizacionPd extends Cronograma { + dato9?: string; + dato10?: string; + dato11?: string; + dato12?: string; + dato13?: string; + dato14?: string; +} diff --git a/src/app/models/ajuste-pd.model.ts b/src/app/models/ajuste-pd.model.ts new file mode 100644 index 0000000..7431e0e --- /dev/null +++ b/src/app/models/ajuste-pd.model.ts @@ -0,0 +1,12 @@ +import { Cronograma } from './cronograma.model'; + +export interface AjustePd extends Cronograma { + dato9?: string; + dato10?: string; + dato13?: string; + dato14?: string; + dato15?: string; + dato16?: string; + dato17?: string; + dato18?: string; +} diff --git a/src/app/models/cronograma.model.ts b/src/app/models/cronograma.model.ts new file mode 100644 index 0000000..3e7c39d --- /dev/null +++ b/src/app/models/cronograma.model.ts @@ -0,0 +1,11 @@ +export interface Cronograma { + id?: number; + empresa: string; + codigoCronograma: string; + codigoCronogramaAjuste: string; + tipoCarga: string; + estadoRevision?: string; + analista?: string; + fechaIngreso?: string; + semaforo?: 'green' | 'yellow' | 'red'; +} diff --git a/src/app/models/empresa.model.ts b/src/app/models/empresa.model.ts new file mode 100644 index 0000000..6f134d2 --- /dev/null +++ b/src/app/models/empresa.model.ts @@ -0,0 +1,4 @@ +export interface Empresa { + id?: number; + name: string; +} diff --git a/src/app/models/estado-aprobacion.model.ts b/src/app/models/estado-aprobacion.model.ts new file mode 100644 index 0000000..0c79473 --- /dev/null +++ b/src/app/models/estado-aprobacion.model.ts @@ -0,0 +1,4 @@ +export interface EstadoAprobacion { + id?: number; + name: string; +} diff --git a/src/app/models/index.ts b/src/app/models/index.ts new file mode 100644 index 0000000..ee573f3 --- /dev/null +++ b/src/app/models/index.ts @@ -0,0 +1,7 @@ +export * from './cronograma.model'; +export * from './empresa.model'; +export * from './tipo-carga.model'; +export * from './estado-aprobacion.model'; +export * from './actualizacion-pd.model'; +export * from './ajuste-pd.model'; +export * from './unidad-informacion.model'; diff --git a/src/app/models/tipo-carga.model.ts b/src/app/models/tipo-carga.model.ts new file mode 100644 index 0000000..109fcf9 --- /dev/null +++ b/src/app/models/tipo-carga.model.ts @@ -0,0 +1,4 @@ +export interface TipoCarga { + id?: number; + name: string; +} diff --git a/src/app/models/unidad-informacion.model.ts b/src/app/models/unidad-informacion.model.ts new file mode 100644 index 0000000..9a95674 --- /dev/null +++ b/src/app/models/unidad-informacion.model.ts @@ -0,0 +1,6 @@ +import { Cronograma } from './cronograma.model'; + +export interface UnidadInformacion extends Cronograma { + dato5?: string; + dato6?: string; +} diff --git a/src/app/pages/actualizacion-pd/actualizacion-pd.component.html b/src/app/pages/actualizacion-pd/actualizacion-pd.component.html new file mode 100644 index 0000000..6e43703 --- /dev/null +++ b/src/app/pages/actualizacion-pd/actualizacion-pd.component.html @@ -0,0 +1,121 @@ +
+
+
+
+
Filtro Empresa
+ +
+
+
+
+
Filtro Código Cronograma
+ +
+
+
+ +
+ + + +
+ + + + Empresa + Código de cronograma + Etapa del Servicio + Nombre sistema + Tipo de inversión + Código de glosa PD + Descripción glosa + Monto Inversión Total (UF) + Año de Inicio + Año de Término + Mes de Término + Nota + Estado aprobación + Observación + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + {{ product.estadoRevision }} + {{ product.estadoRevision }} + {{ product.fechaIngreso }} + {{ product.estadoRevision }} + {{ product.dato9 }} + {{ product.dato10 }} + {{ product.dato11 }} + {{ product.dato12 }} + +
+ +
+ + + + + +
+
+
diff --git a/src/app/pages/actualizacion-pd/actualizacion-pd.component.scss b/src/app/pages/actualizacion-pd/actualizacion-pd.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/actualizacion-pd/actualizacion-pd.component.spec.ts b/src/app/pages/actualizacion-pd/actualizacion-pd.component.spec.ts new file mode 100644 index 0000000..c9daf23 --- /dev/null +++ b/src/app/pages/actualizacion-pd/actualizacion-pd.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ActualizacionPdComponent } from './actualizacion-pd.component'; + +describe('ActualizacionPdComponent', () => { + let component: ActualizacionPdComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ActualizacionPdComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ActualizacionPdComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/actualizacion-pd/actualizacion-pd.component.ts b/src/app/pages/actualizacion-pd/actualizacion-pd.component.ts new file mode 100644 index 0000000..b3006fc --- /dev/null +++ b/src/app/pages/actualizacion-pd/actualizacion-pd.component.ts @@ -0,0 +1,198 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { Table, TableModule } from 'primeng/table'; +import { InputTextModule } from 'primeng/inputtext'; +import { SelectModule } from 'primeng/select'; +import { ButtonModule } from 'primeng/button'; +import { TooltipModule } from 'primeng/tooltip'; +import * as FileSaver from 'file-saver'; +import { Workbook } from 'exceljs'; + +@Component({ + selector: 'app-actualizacion-pd', + imports: [ + FormsModule, + TableModule, + InputTextModule, + SelectModule, + ButtonModule, + TooltipModule + ], + templateUrl: './actualizacion-pd.component.html', + styleUrl: './actualizacion-pd.component.scss', + standalone: true +}) +export class ActualizacionPdComponent { + pageTitle: string = 'Cronogramas cargados:'; + select1: any = ''; + selectedCity: any = ''; + empresas: any[] = [{ name: 'Empresa A' }, { name: 'Empresa B' }, { name: 'Empresa C' }]; + estadoAprobacion = [ + { name: 'Aprobado', value: true }, + { name: 'Rechazado', value: false }, + ]; + products: any[] = [ + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Ingresado', + analista: 'No asignado', + fechaIngreso: '2025-04-29', + semaforo: 'green', + dato9: 'Ingresado', + dato10: 'No asignado', + dato13: 'Rechazado', + dato14: 'green', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'En revisión', + analista: 'Gabriel Torres', + fechaIngreso: '2025-04-29', + semaforo: 'yellow', + dato9: 'Ingresado', + dato10: 'No asignado', + dato13: 'Rechazado', + dato14: '', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Rechazado', + analista: 'Jorge Muñoz', + fechaIngreso: '2025-04-29', + semaforo: 'red', + dato9: 'Ingresado', + dato10: 'No asignado', + dato13: 'Aprobado', + dato14: '', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Aprobado', + analista: 'Jarolt Matamoros', + fechaIngreso: '2025-04-29', + semaforo: 'green', + dato9: 'Ingresado', + dato10: 'No asignado', + dato11: '2025-04-29', + dato12: 'green', + dato13: 'Aprobado', + dato14: '', + }, + ]; + /** + * Exporta la tabla a Excel usando ExcelJS + * @param table Referencia a la tabla PrimeNG + */ + + exportExcelWithStyles(table: Table): void { + // Creamos un nuevo libro de trabajo + const workbook = new Workbook(); + const worksheet = workbook.addWorksheet('Datos'); + + // Exportamos los datos y aplicamos estilos + this.addDataToWorksheet(worksheet, table); + this.applyHeaderStyles(worksheet); + this.configureWorksheet(worksheet); + this.saveExcelFile(workbook); + + + } + /** + * Añade los datos de la tabla al worksheet + */ + private addDataToWorksheet(worksheet: any, table: Table): void { + // Obtenemos los datos a exportar + const data = table.filteredValue || table.value; + + // Definimos las cabeceras + const headers = [ + 'Empresa', 'Código de cronograma', 'Etapa del Servicio', + 'Nombre sistema', 'Tipo de inversión', 'Código de glosa PD', + 'Descripción glosa', 'Año de Inicio', 'Año de Término', + 'Estado aprobación' + ]; + + // Añadimos cabeceras y datos + worksheet.addRow(headers); + data.forEach(item => { + worksheet.addRow([ + item.empresa, + item.codigoCronograma, + item.codigoCronogramaAjuste, + item.tipoCarga, + item.estadoRevision, + item.analista, + item.fechaIngreso, + item.dato9, + item.dato10, + item.dato13 + ]); + }); + } + /** + * Aplica estilos a los encabezados de la tabla + */ + private applyHeaderStyles(worksheet: any): void { + const headerRow = worksheet.getRow(1); + headerRow.height = 25; + + headerRow.eachCell((cell: any) => { + cell.font = { name: 'Arial', size: 12, bold: true, color: { argb: '000000' } }; + cell.border = { + top: { style: 'thin' }, + left: { style: 'thin' }, + bottom: { style: 'thin' }, + right: { style: 'thin' } + }; + cell.alignment = { horizontal: 'center', vertical: 'middle' }; + }); + } + + /** + * Configura aspectos generales de la hoja de trabajo + */ + private configureWorksheet(worksheet: any): void { + // Ajustamos el ancho de las columnas automáticamente + if (worksheet.columns) { + worksheet.columns.forEach((column: any) => { + if (column) { + let maxLength = 0; + column.eachCell({ includeEmpty: true }, (cell: any) => { + const columnLength = cell.value ? cell.value.toString().length : 10; + if (columnLength > maxLength) { + maxLength = columnLength; + } + }); + column.width = maxLength < 10 ? 10 : maxLength + 2; + } + }); + } + + // Congelamos la primera fila + worksheet.views = [{ state: 'frozen', xSplit: 0, ySplit: 1 }]; + } + /** + * Guarda el archivo Excel + */ + private async saveExcelFile(workbook: any): Promise { + const today = new Date(); + const fileName = `Cronograma_temporal_por_actualización_de_PD${today.getFullYear()}${(today.getMonth() + 1).toString().padStart(2, '0')}${today.getDate().toString().padStart(2, '0')}.xlsx`; + + const buffer = await workbook.xlsx.writeBuffer(); + const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); + FileSaver.saveAs(blob, fileName); + } + +} diff --git a/src/app/pages/ajuste-pd/ajuste-pd.component.html b/src/app/pages/ajuste-pd/ajuste-pd.component.html new file mode 100644 index 0000000..972c71a --- /dev/null +++ b/src/app/pages/ajuste-pd/ajuste-pd.component.html @@ -0,0 +1,140 @@ +
+
+
+
+
Filtro Empresa
+ +
+
+
+
+
Filtro Código Cronograma
+ +
+
+
+ + + + +
+ +
+ + + + + + Cronograma base vigente + Cronograma base ajustado + + + Empresa + Código de cronograma + Etapa del Servicio + Nombre sistema + Nombre localidad + Tipo de inversión + Código de glosa PD + Descripción glosa + Monto Inversión Total (UF) + Año de Inicio + Año de Término + Mes de Término + Tipo de ajuste + Año de Inicio + Año de Término + Mes de Término + Nota + Estado aprobación + Observación + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + {{ product.estadoRevision }} + {{ product.fechaIngreso }} + {{ product.estadoRevision }} + {{ product.dato9 }} + {{ product.dato10 }} + {{ product.dato13 }} + {{ product.dato14 }} + {{ product.dato15 }} + {{ product.dato16 }} + {{ product.dato17 }} + {{ product.dato18 }} + {{ product.dato11 }} + {{ product.dato12 }} + +
+ +
+ + + + + +
+
+ +
diff --git a/src/app/pages/ajuste-pd/ajuste-pd.component.scss b/src/app/pages/ajuste-pd/ajuste-pd.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/ajuste-pd/ajuste-pd.component.spec.ts b/src/app/pages/ajuste-pd/ajuste-pd.component.spec.ts new file mode 100644 index 0000000..520d088 --- /dev/null +++ b/src/app/pages/ajuste-pd/ajuste-pd.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AjustePdComponent } from './ajuste-pd.component'; + +describe('AjustePdComponent', () => { + let component: AjustePdComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AjustePdComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AjustePdComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/ajuste-pd/ajuste-pd.component.ts b/src/app/pages/ajuste-pd/ajuste-pd.component.ts new file mode 100644 index 0000000..ab0b3bf --- /dev/null +++ b/src/app/pages/ajuste-pd/ajuste-pd.component.ts @@ -0,0 +1,381 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TableModule, Table } from 'primeng/table'; +import { InputTextModule } from 'primeng/inputtext'; +import { SelectModule } from 'primeng/select'; +import { TooltipModule } from 'primeng/tooltip'; +import { ButtonModule } from 'primeng/button'; +import * as FileSaver from 'file-saver'; +import { Workbook } from 'exceljs'; + +@Component({ + selector: 'app-ajuste-pd', + imports: [ + FormsModule, + TableModule, + InputTextModule, + SelectModule, + TooltipModule, + ButtonModule + ], + templateUrl: './ajuste-pd.component.html', + styleUrl: './ajuste-pd.component.scss' +}) +export class AjustePdComponent { + selectedCity: any = ''; + empresas: any[] = [{ name: 'Empresa A' }, { name: 'Empresa B' }, { name: 'Empresa C' }]; + select1: any = ''; + estadoAprobacion = [ + { name: 'Aprobado', value: true }, + { name: 'Rechazado', value: false }, + ]; + products: any[] = [ + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Ingresado', + analista: 'No asignado', + fechaIngreso: '2025-04-29', + semaforo: 'green', + dato9: 'Ingresado', + dato10: 'No asignado', + dato15: 'green', + dato16: 'green', + dato17: 'green', + dato18: 'green', + dato13: 'Rechazado', + dato14: 'green', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'En revisión', + analista: 'Gabriel Torres', + fechaIngreso: '2025-04-29', + semaforo: 'yellow', + dato9: 'Ingresado', + dato10: 'No asignado', + dato15: 'green', + dato16: 'green', + dato17: 'green', + dato18: 'green', + dato13: 'Rechazado', + dato14: 'green', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Rechazado', + analista: 'Jorge Muñoz', + fechaIngreso: '2025-04-29', + semaforo: 'red', + dato9: 'Ingresado', + dato10: 'No asignado', + dato15: 'green', + dato16: 'green', + dato17: 'green', + dato18: 'green', + dato13: 'Rechazado', + dato14: 'green', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Aprobado', + analista: 'Jarolt Matamoros', + fechaIngreso: '2025-04-29', + semaforo: 'green', + dato9: 'Ingresado', + dato10: 'No asignado', + dato15: 'green', + dato16: 'green', + dato17: 'green', + dato18: 'green', + dato13: 'Rechazado', + dato14: 'green', + }, + ]; + + /** + * Exporta los datos de la tabla a Excel con estilos en los encabezados + * @param table Tabla PrimeNG a exportar + */ + exportExcelWithStyles(table: Table): void { + // Creamos un nuevo libro de trabajo + const workbook = new Workbook(); + const worksheet = workbook.addWorksheet('Datos'); + + // Obtenemos los datos + const data = table.filteredValue || table.value; + + // Añadimos la fila de encabezados agrupados + this.addGroupedHeaders(worksheet); + + // Añadimos la fila de encabezados detallados + const headers = this.getHeaders(); + worksheet.addRow(headers); + + // Añadimos los datos a la hoja + this.populateWorksheet(worksheet, data); + + // Aplicamos estilos a los encabezados + this.styleHeaders(worksheet); + + // Configuramos propiedades generales de la hoja + this.configureWorksheet(worksheet, data.length); + + // Exportamos el archivo + this.saveExcelFile(workbook); + } + + /** + * Añade la fila de encabezados agrupados + */ + private addGroupedHeaders(worksheet: any): void { + // Añadir fila de encabezados agrupados + const groupHeaderRow = worksheet.addRow([ + '', '', '', '', '', '', '', '', '', + 'Cronograma base vigente', '', '', + 'Cronograma base ajustado', '', '', '', + '', '', '' + ]); + + // Combinar celdas para los grupos de encabezados + worksheet.mergeCells(1, 10, 1, 12); // Cronograma base vigente (columnas 10-12) + worksheet.mergeCells(1, 13, 1, 16); // Cronograma base ajustado (columnas 13-16) + + // Dar formato a las celdas combinadas + groupHeaderRow.getCell(10).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: '0066CC' } // Color azul oscuro para "Cronograma base vigente" + }; + groupHeaderRow.getCell(13).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: '0000CC' } // Color azul más fuerte para "Cronograma base ajustado" + }; + + // Estilo de texto para encabezados agrupados + [10, 13].forEach(cellIndex => { + const cell = groupHeaderRow.getCell(cellIndex); + cell.font = { + name: 'Arial', + size: 12, + bold: true, + color: { argb: 'FFFFFF' } // Texto blanco + }; + cell.alignment = { + horizontal: 'center', + vertical: 'middle' + }; + }); + } + + /** + * Devuelve los cabeceros para el archivo Excel + */ + private getHeaders(): string[] { + return [ + 'Empresa', + 'Código de cronograma', + 'Etapa del Servicio', + 'Nombre sistema', + 'Nombre localidad', + 'Tipo de inversión', + 'Código de glosa PD', + 'Descripción glosa', + 'Monto Inversión Total (UF)', + 'Año de Inicio', + 'Año de Término', + 'Mes de Término', + 'Tipo de ajuste', + 'Año de Inicio', + 'Año de Término', + 'Mes de Término', + 'Nota', + 'Estado aprobación', + 'Observación' + ]; + } + + /** + * Rellena la hoja con los datos de la tabla + */ + private populateWorksheet(worksheet: any, data: any[]): void { + // Añadimos los datos + data.forEach(item => { + // Determinar estado de aprobación basado en el valor + let estadoAprobacion; + if (typeof item.dato13 === 'boolean') { + estadoAprobacion = item.dato13 ? 'Aprobado' : 'Rechazado'; + } else { + estadoAprobacion = item.dato13; + } + + worksheet.addRow([ + item.empresa, // Empresa + item.codigoCronograma, // Código de cronograma + item.codigoCronogramaAjuste, // Etapa del Servicio + item.tipoCarga, // Nombre sistema + item.estadoRevision, // Nombre localidad + item.fechaIngreso, // Tipo de inversión + item.estadoRevision, // Código de glosa PD + item.dato9, // Descripción glosa + item.dato10, // Monto Inversión Total (UF) + item.dato13, // Año de Inicio (Cronograma base vigente) + item.dato14, // Año de Término (Cronograma base vigente) + item.dato15, // Mes de Término (Cronograma base vigente) + item.dato16, // Tipo de ajuste (Cronograma base ajustado) + item.dato17, // Año de Inicio (Cronograma base ajustado) + item.dato18, // Año de Término (Cronograma base ajustado) + item.dato11, // Mes de Término (Cronograma base ajustado) + item.dato12, // Nota + estadoAprobacion, // Estado aprobación + item.analista // Observación + ]); + }); + } + + /** + * Aplica estilos a los encabezados + */ + private styleHeaders(worksheet: any): void { + // Estilo para la segunda fila (encabezados detallados) + const detailedHeaderRow = worksheet.getRow(2); + detailedHeaderRow.height = 25; + + detailedHeaderRow.eachCell(cell => { + // Estilo de texto Encabezado + cell.font = { + name: 'Arial', + size: 12, + bold: true, + color: { argb: 'FFFFFF' } // Texto blanco + }; + + // Bordes + cell.border = { + top: { style: 'thin' }, + left: { style: 'thin' }, + bottom: { style: 'thin' }, + right: { style: 'thin' } + }; + + // Alineación + cell.alignment = { + horizontal: 'center', + vertical: 'middle' + }; + + // Color de fondo para encabezados normales + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: '0066CC' } // Color azul por defecto + }; + }); + + // Aplicar colores específicos según el grupo de encabezados + // Cronograma base ajustado (columnas 13-16) + for (let i = 13; i <= 16; i++) { + const cell = detailedHeaderRow.getCell(i); + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: '0000CC' } // Azul más fuerte + }; + } + + // Estado aprobación y Observación (columnas 18-19) + for (let i = 18; i <= 19; i++) { + const cell = detailedHeaderRow.getCell(i); + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: '28a745' } // Color verde + }; + } + } + + /** + * Configura propiedades generales de la hoja de trabajo + */ + private configureWorksheet(worksheet: any, dataLength: number): void { + // Configuramos altura de fila predeterminada + worksheet.properties.defaultRowHeight = 20; + + // Ajustamos el ancho de las columnas automáticamente + this.adjustColumnWidths(worksheet); + + // Congelamos la primera fila + worksheet.views = [{ state: 'frozen', xSplit: 0, ySplit: 2 }]; // Congelamos 2 filas + } + + /** + * Ajusta el ancho de las columnas según el contenido + */ + private adjustColumnWidths(worksheet: any): void { + if (worksheet.columns) { + worksheet.columns.forEach(column => { + if (column) { + let maxLength = 0; + column.eachCell({ includeEmpty: true }, cell => { + const columnLength = cell.value ? cell.value.toString().length : 10; + if (columnLength > maxLength) { + maxLength = columnLength; + } + }); + column.width = maxLength < 10 ? 10 : maxLength + 2; + } + }); + } else { + // Si no hay columnas definidas, establecemos valores predeterminados + const columnWidths = [ + { width: 15 }, // Empresa + { width: 20 }, // Código de cronograma + { width: 18 }, // Etapa del Servicio + { width: 18 }, // Nombre sistema + { width: 18 }, // Nombre localidad + { width: 18 }, // Tipo de inversión + { width: 18 }, // Código de glosa PD + { width: 22 }, // Descripción glosa + { width: 18 }, // Monto Inversión Total (UF) + { width: 15 }, // Año de Inicio (vigente) + { width: 15 }, // Año de Término (vigente) + { width: 15 }, // Mes de Término (vigente) + { width: 15 }, // Tipo de ajuste + { width: 15 }, // Año de Inicio (ajustado) + { width: 15 }, // Año de Término (ajustado) + { width: 15 }, // Mes de Término (ajustado) + { width: 15 }, // Nota + { width: 18 }, // Estado aprobación + { width: 20 } // Observación + ]; + worksheet.columns = columnWidths; + } + } + + /** + * Guarda el archivo Excel generado + */ + private saveExcelFile(workbook: any): void { + const today = new Date(); + const fileName = `Cronograma_temporal_por_ajuste_de_PD${today.getFullYear()}${(today.getMonth() + 1).toString().padStart(2, '0')}${today.getDate().toString().padStart(2, '0')}.xlsx`; + + workbook.xlsx.writeBuffer() + .then(buffer => { + const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); + FileSaver.saveAs(blob, fileName); + }) + .catch(error => console.error('Error al exportar a Excel:', error)); + } +} \ No newline at end of file diff --git a/src/app/pages/concesiones/concesiones.component.html b/src/app/pages/concesiones/concesiones.component.html new file mode 100644 index 0000000..c29e47c --- /dev/null +++ b/src/app/pages/concesiones/concesiones.component.html @@ -0,0 +1,143 @@ +
+ +
Cronogramas cargados:
+ + + + Empresa + Código de cronograma + Código cronograma de ajuste + Tipo de carga + Estado de revisión + Analista + Fecha ingreso + Semáforo + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + {{ product.estadoRevision }} + + + + + {{ product.fechaIngreso }} + + +
+ +
+ + +
+
+ +
+
+
+
Filtro Empresa
+ +
+
+
+
+
Filtro Código Cronograma SINAR
+ +
+
+
+
+
Filtro tipo de carga
+ +
+
+
+
+
¿Contiene obras del año?
+ +
+
+
+
+
N° Oficio que aprueba
+ + +
+
+
+ +
+
+ + +
Cronogramas solicitados y pendientes de carga:
+ + + + + Empresa + + + Código de cronograma SINAR + + + Contiene obras del año + + + N° Oficio que aprueba + + + Tipo de carga + + + Fecha de solicitud + + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + {{ product.estadoRevision }} + {{ product.fechaIngreso }} + + + + +
diff --git a/src/app/pages/concesiones/concesiones.component.scss b/src/app/pages/concesiones/concesiones.component.scss new file mode 100644 index 0000000..d9d21e7 --- /dev/null +++ b/src/app/pages/concesiones/concesiones.component.scss @@ -0,0 +1,12 @@ +.input-with-icon { + background-size: 20px 20px; + padding-right: 35px; /* espacio para que el texto no choque con el ícono */ +} + +@media screen and (min-width: 992px) { + .lg\:col-2 { + flex: 0 0 auto; + padding: 0.5rem; + width: 19%; + } +} diff --git a/src/app/pages/concesiones/concesiones.component.spec.ts b/src/app/pages/concesiones/concesiones.component.spec.ts new file mode 100644 index 0000000..c8e341d --- /dev/null +++ b/src/app/pages/concesiones/concesiones.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConcesionesComponent } from './concesiones.component'; + +describe('ConcesionesComponent', () => { + let component: ConcesionesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ConcesionesComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ConcesionesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/concesiones/concesiones.component.ts b/src/app/pages/concesiones/concesiones.component.ts new file mode 100644 index 0000000..155c8ca --- /dev/null +++ b/src/app/pages/concesiones/concesiones.component.ts @@ -0,0 +1,145 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TableModule } from 'primeng/table'; +import { InputTextModule } from 'primeng/inputtext'; +import { SelectModule } from 'primeng/select'; + +@Component({ + selector: 'app-concesiones', + imports: [FormsModule, TableModule, InputTextModule, SelectModule], + templateUrl: './concesiones.component.html', + styleUrl: './concesiones.component.scss' +}) +export class ConcesionesComponent { + select1: any = ''; + select2: any = ''; + select3: any = ''; + select4: any = ''; + pageTitle: string = 'Cronogramas cargados:'; + empresas: any[] = [{name: 'Empresa A'}, {name: 'Empresa B'}, {name: 'Empresa C'}]; + tipoCarga: any[] = [{name: 'Actualización'}, {name: 'Nueva'}, {name: 'Concesión'}, {name: 'Ajuste'}, {name: 'ATO'}]; + contieneObras: any[] = [{name:'Si'}, {name:'No'}]; + products: any[] = [ + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Ingresado', + analista: '', + fechaIngreso: '2025-04-29', + semaforo: 'green' + }, + { + empresa: 'Fmpresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'En revisión', + analista: 'Gabriel Torres', + fechaIngreso: '2024-04-29', + semaforo: 'yellow' + }, + { + empresa: 'Gmpresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Rechazado', + analista: 'Jorge Muñoz', + fechaIngreso: '2023-04-29', + semaforo: 'red' + }, + { + empresa: 'Hmpresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Aprobado', + analista: 'Jarolt Matamoros', + fechaIngreso: '2022-04-29', + semaforo: 'green' + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Ingresado', + analista: '', + fechaIngreso: '2025-04-29', + semaforo: 'green' + }, + { + empresa: 'Fmpresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'En revisión', + analista: 'Gabriel Torres', + fechaIngreso: '2024-04-29', + semaforo: 'yellow' + }, + { + empresa: 'Gmpresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Rechazado', + analista: 'Jorge Muñoz', + fechaIngreso: '2023-04-29', + semaforo: 'red' + }, + { + empresa: 'Hmpresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Aprobado', + analista: 'Jarolt Matamoros', + fechaIngreso: '2022-04-29', + semaforo: 'green' + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Ingresado', + analista: '', + fechaIngreso: '2025-04-29', + semaforo: 'green' + }, + { + empresa: 'Fmpresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'En revisión', + analista: 'Gabriel Torres', + fechaIngreso: '2024-04-29', + semaforo: 'yellow' + }, + { + empresa: 'Gmpresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Rechazado', + analista: 'Jorge Muñoz', + fechaIngreso: '2023-04-29', + semaforo: 'red' + }, + { + empresa: 'Hmpresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + estadoRevision: 'Aprobado', + analista: 'Jarolt Matamoros', + fechaIngreso: '2022-04-29', + semaforo: 'green' + }, + ]; + +} diff --git a/src/app/pages/home/home.component.html b/src/app/pages/home/home.component.html new file mode 100644 index 0000000..784169c --- /dev/null +++ b/src/app/pages/home/home.component.html @@ -0,0 +1,88 @@ + +
+ + + +
Simple Card
+
+ +

Lorem ipsum dolor sit amet...

+
+
+ + + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est + laborum. +

+
+ + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. +

+
+ + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. +

+
+
+ + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est + laborum. +

+
+ + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. +

+
+ + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. +

+
+
+ + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est + laborum. +

+
+ + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. +

+
+ + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. +

+
+
+
\ No newline at end of file diff --git a/src/app/pages/home/home.component.scss b/src/app/pages/home/home.component.scss new file mode 100644 index 0000000..0bf0724 --- /dev/null +++ b/src/app/pages/home/home.component.scss @@ -0,0 +1,62 @@ +/* src/app/pages/home/home.component.scss */ +.home-page { + padding: 1rem; + } + + .card-title { + font-size: 1.1rem; + font-weight: 500; + color: #495057; + } + + /* Personalización del estilo de la card */ + :host ::ng-deep .p-card { + border-radius: 0; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + + .p-card-title { + font-size: 1.1rem; + margin-bottom: 0.5rem; + } + + .p-card-content { + padding-top: 0; + } + } + + /* Personalización del estilo del acordeón */ + :host ::ng-deep .p-accordion { + .p-accordion-header { + .p-accordion-header-link { + background-color: #f8f9fa; + color: #495057; + border-radius: 0; + font-weight: 500; + padding: 1rem; + + &:focus { + box-shadow: none; + } + + .p-accordion-toggle-icon { + color: #0088cc; + } + } + + &.p-highlight .p-accordion-header-link { + background-color: #f8f9fa; + color: #495057; + border-color: #dee2e6; + } + } + + .p-accordion-content { + background-color: #ffffff; + border-color: #dee2e6; + padding: 1rem; + } + } + + .mb-4 { + margin-bottom: 1rem; + } \ No newline at end of file diff --git a/src/app/pages/home/home.component.spec.ts b/src/app/pages/home/home.component.spec.ts new file mode 100644 index 0000000..1191557 --- /dev/null +++ b/src/app/pages/home/home.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HomeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/home/home.component.ts b/src/app/pages/home/home.component.ts new file mode 100644 index 0000000..dfcf793 --- /dev/null +++ b/src/app/pages/home/home.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +// Importaciones de PrimeNG +import { CardModule } from 'primeng/card'; +import { AccordionModule } from 'primeng/accordion'; +@Component({ + selector: 'app-home', + imports: [ + CommonModule, + CardModule, + AccordionModule +], + standalone: true, + templateUrl: './home.component.html', + styleUrl: './home.component.scss' +}) +export class HomeComponent { + + +} diff --git a/src/app/pages/login/login.component.html b/src/app/pages/login/login.component.html index 91978ed..f28700e 100644 --- a/src/app/pages/login/login.component.html +++ b/src/app/pages/login/login.component.html @@ -1,71 +1,121 @@ -
-
- -
-
- SACG -
1.0
+
+
+
+ gota +
1.0
+
+
+
Administrador de Cronogramas
+
+
+
+ + + + + +
+ +
- -
- -
- - -
- -
\ No newline at end of file + + \ No newline at end of file diff --git a/src/app/pages/login/login.component.scss b/src/app/pages/login/login.component.scss index 2217eb7..20a61c4 100644 --- a/src/app/pages/login/login.component.scss +++ b/src/app/pages/login/login.component.scss @@ -1,200 +1,190 @@ :host { - display: flex; - flex-direction: column; - min-height: 100vh; - } - - /* Header Styles */ - .header { - background-color: #d3e9f7; - padding: 1rem 3rem; - display: flex; - align-items: center; - box-shadow: 0 2px 5px rgba(0,0,0,0.1); - } - - .header-content { - display: flex; - align-items: center; - gap: 2rem; - width: 100%; - max-width: 1200px; - margin: 0 auto; - } - - .siss-logo { - height: 60px; - } - - .sacg-container { - display: flex; - flex-direction: column; - } - - .sacg-title { - display: flex; - align-items: center; - font-size: 2.5rem; - font-weight: bold; - color: #0a2847; - position: relative; - } - - .version-badge { - background-color: #0088cc; - color: white; - border-radius: 50%; - padding: 0.1rem 0.4rem; - font-size: 0.8rem; - position: absolute; - top: 0; - right: -1.5rem; - } - - .sacg-subtitle { - font-size: 1.2rem; - color: #0a2847; - } - - /* Main Content Styles */ - .main-content { - flex: 1; - display: flex; - justify-content: center; - align-items: center; - background-color: #f0f0f0; - background-size: cover; - background-position: center; - position: relative; - } - - .main-content::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(240, 240, 240, 0.7); - } - + display: flex; + flex-direction: column; + min-height: 100vh; +} + +/* Header Styles */ +.header-container { + position: relative; + width: 100%; + height: 190px; + overflow: hidden; +} + +.header-image { + position: relative; + width: 100%; + height: 250px; + background-image: url('/img/header2.webp'); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + will-change: transform; + contain: paint; +} + +.water-drop-container { + position: absolute; + top: 26%; + left: 49%; + transform: translate(-50%, -50%); + width: 41px; + height: 60px; + display: flex; + align-items: center; + justify-content: center; +} + +.water-drop { + position: absolute; + width: 41px; + height: 60px; +} + +.text-drop { + position: relative; + color: white; + top: 15%; + font-weight: bold; + font-size: 1.2rem; + z-index: 2; + pointer-events: none; +} + +.header-text { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: #002147; + text-align: center; + padding: 0px; + box-sizing: border-box; +} + +.sub-title { + font-size: 3.0rem; + margin-bottom: 0px; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5); + transform: translate(-4%); +} + +/* Main Content Styles */ +.main-content { + flex: 1; + background-color: #f0f0f0; + position: relative; + min-height: 60vh; /* Aumentado para mayor espacio vertical */ +} + +.main-content::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(240, 240, 240, 0.7); +} + +.login-container { + position: relative; + z-index: 10; + width: 100%; + max-width: 360px; +} + +.login-box { + width: 100%; +} + +.login-card { + background-color: white; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + overflow: hidden; + width: 100%; +} + +/* Estilo para los paneles y animaciones */ +.position-relative { + min-height: 450px; + position: relative; +} + +.panel-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + transition: opacity 0.3s ease-out, visibility 0.3s ease-out; +} + +.d-none { + visibility: hidden !important; + opacity: 0 !important; + pointer-events: none !important; + position: absolute !important; + z-index: -1 !important; +} + +/* Configuración para animate.css */ +.animate__animated { + --animate-duration: 0.8s; /* Aumentado para una transición más suave */ +} + +.login-header { + padding: 1rem; + text-align: center; + border-bottom: 1px solid #e0e0e0; +} + +.login-header h2 { + margin: 0; + font-size: 1.2rem; + color: #0088cc; +} + +.login-actions { + margin-top: 1rem; +} + +.password-recovery a { + color: #0088cc; + text-decoration: none; + font-size: 0.9rem; +} + +.password-recovery a:hover { + text-decoration: underline; +} + +/* Estilos para los inputs con iconos */ +.input-with-icon { + background-image: url("data:image/svg+xml,%3Csvg fill='gray' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 4H4C2.897 4 2 4.897 2 6v12c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2V6c0-1.103-.897-2-2-2zM4 6h16l-8 5-8-5zm0 12V8l8 5 8-5v10H4z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + background-size: 20px 20px; + padding-right: 35px; +} + +.input-with-lock { + background-image: url("data:image/svg+xml,%3Csvg fill='gray' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17 8h-1V6c0-2.757-2.243-5-5-5S6 3.243 6 6v2H5c-1.103 0-2 .897-2 2v12c0 1.103.897 2 2 2h12c1.103 0 2-.897 2-2V10c0-1.103-.897-2-2-2zM8 6c0-1.654 1.346-3 3-3s3 1.346 3 3v2H8V6zm9 16H5V10h12v12z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + background-size: 20px 20px; + padding-right: 35px; +} + +/* Responsive styles */ +@media screen and (max-width: 768px) { .login-container { - position: relative; - z-index: 10; + padding: 0 1rem; } - - .login-box { - width: 360px; - max-width: 100%; - } - - .login-card { - background-color: white; - border-radius: 4px; - box-shadow: 0 2px 10px rgba(0,0,0,0.1); - overflow: hidden; - } - - .login-header { - padding: 1rem; - text-align: center; - border-bottom: 1px solid #e0e0e0; - } - - .login-header h2 { - margin: 0; - font-size: 1.2rem; - font-weight: normal; - color: #666; - } - - .login-card form { - padding: 1.5rem; - } - - .p-inputgroup-addon { - background-color: #f0f0f0; - border-color: #ced4da; - } - - .login-actions { - display: flex; - justify-content: flex-end; - margin-top: 1rem; - } - - .p-button-primary { - background-color: #0088cc; - border-color: #0088cc; - } - - .password-recovery { - padding: 0 1.5rem 1.5rem; - text-align: left; - } - - .password-recovery a { - color: #0088cc; - text-decoration: none; - font-size: 0.9rem; - } - - .password-recovery a:hover { - text-decoration: underline; - } - - /* Footer Styles */ - .footer { - background-color: #0088cc; - color: white; - padding: 1rem 2rem; - } - - .footer-content { - display: flex; - justify-content: space-between; - align-items: center; - max-width: 1200px; - margin: 0 auto; - } - - .footer-left, .footer-right { - display: flex; - align-items: center; - font-size: 0.8rem; - } - - .footer-left { - gap: 0.5rem; - } - - .footer-text { - display: flex; - flex-direction: column; - } - - .footer-logo { - height: 40px; - } - - .footer-right { - flex-direction: column; - align-items: flex-end; - } - - /* Responsive styles */ - @media screen and (max-width: 768px) { - .header-content { - flex-direction: column; - gap: 1rem; - } - - .footer-content { - flex-direction: column; - gap: 1rem; - } - - .footer-right { - align-items: center; - } - } \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/pages/login/login.component.ts b/src/app/pages/login/login.component.ts index cb5ab41..8d96a47 100644 --- a/src/app/pages/login/login.component.ts +++ b/src/app/pages/login/login.component.ts @@ -1,13 +1,18 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; - import { CardModule } from 'primeng/card'; import { InputTextModule } from 'primeng/inputtext'; import { ButtonModule } from 'primeng/button'; import { PasswordModule } from 'primeng/password'; import { DividerModule } from 'primeng/divider'; +import { MessagesModule } from 'primeng/messages'; +import { MessageModule } from 'primeng/message'; +import { ToastModule } from 'primeng/toast'; +import { MessageService } from 'primeng/api'; +import { FooterComponent } from "../../components/footer/footer.component"; +import { AuthService } from '../../services/auth.service'; @Component({ selector: 'app-login', @@ -18,20 +23,114 @@ import { DividerModule } from 'primeng/divider'; InputTextModule, ButtonModule, PasswordModule, - DividerModule + DividerModule, + MessagesModule, + MessageModule, + ToastModule, + FooterComponent ], + providers: [MessageService], templateUrl: './login.component.html', styleUrl: './login.component.scss' }) -export class LoginComponent { +export class LoginComponent implements OnInit { + // Login form email: string = ''; password: string = ''; - - constructor(private router: Router) { } - onLogin() { - // Aquí iría la lógica de autenticación - // Por ahora, solo navegamos a la página de inicio - this.router.navigate(['/inicio']); + loading: boolean = false; + errorMessage: string = ''; + + // Password recovery form + recoveryEmail: string = ''; + recoveryLoading: boolean = false; + recoveryMessage: string = ''; + recoveryStatus: string = 'info'; + + // Control de formularios + showRecovery: boolean = false; + isInitialLoad: boolean = false; + + constructor( + private router: Router, + private authService: AuthService, + private messageService: MessageService + ) { } + + ngOnInit() { + } - -} + + /** + * Cambia entre el formulario de login y recuperación + */ + toggleRecovery(event: Event) { + event.preventDefault(); + this.showRecovery = !this.showRecovery; + this.errorMessage = ''; + this.recoveryMessage = ''; + + // Si estamos cambiando al formulario de recuperación, copiar el email actual + if (this.showRecovery && this.email) { + this.recoveryEmail = this.email; + } + + // Forzar actualización del DOM con un pequeño retraso + setTimeout(() => { + // Este timeout ayuda a que Angular aplique los cambios de clase completamente + }, 50); // Aumentado para asegurar la transición suave + } + + /** + * Proceso de login + */ + onLogin() { + this.loading = true; + this.errorMessage = ''; + + // Llamar al servicio auth + this.authService.login({ email: this.email, password: this.password }) + .subscribe({ + next: () => { + console.log('Login exitoso'); + this.loading = false; + this.router.navigate(['/inicio']); + }, + error: (error) => { + console.error('Error en login:', error); + this.loading = false; + this.errorMessage = 'Credenciales incorrectas'; + this.messageService.add({ + severity: 'error', + summary: 'Error', + detail: 'Credenciales incorrectas' + }); + } + }); + } + + /** + * Proceso de recuperación de contraseña + */ + onRecoverPassword() { + if (!this.recoveryEmail) { + this.recoveryMessage = 'Debes ingresar un email'; + this.recoveryStatus = 'error'; + return; + } + + this.recoveryLoading = true; + this.recoveryMessage = ''; + + // Simulamos la recuperación (en producción, esto llamaría a un servicio real) + setTimeout(() => { + this.recoveryLoading = false; + this.recoveryMessage = 'Hemos enviado un enlace de recuperación a tu email'; + this.recoveryStatus = 'success'; + this.messageService.add({ + severity: 'success', + summary: 'Email enviado', + detail: 'Se ha enviado un enlace de recuperación a tu correo' + }); + }, 1500); + } +} \ No newline at end of file diff --git a/src/app/pages/not-found/not-found.component.html b/src/app/pages/not-found/not-found.component.html new file mode 100644 index 0000000..6a44707 --- /dev/null +++ b/src/app/pages/not-found/not-found.component.html @@ -0,0 +1,8 @@ + +
+
+

404

+

Página no encontrada

+ +
+
\ No newline at end of file diff --git a/src/app/pages/not-found/not-found.component.scss b/src/app/pages/not-found/not-found.component.scss new file mode 100644 index 0000000..f8b113d --- /dev/null +++ b/src/app/pages/not-found/not-found.component.scss @@ -0,0 +1,43 @@ +/* error-404.component.css */ +.error-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + background-color: #f3f4f6; + } + + .error-content { + text-align: center; + } + + .error-title { + font-size: 9rem; + font-weight: 700; + color: #1f2937; + } + + .error-message { + font-size: 1.5rem; + font-weight: 500; + color: #4b5563; + margin-top: 1rem; + margin-bottom: 2rem; + } + + .error-button { + background-color: #2563eb; + color: white; + font-weight: 700; + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + transition: background-color 0.3s; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border: none; + cursor: pointer; + } + + .error-button:hover { + background-color: #1d4ed8; + } \ No newline at end of file diff --git a/src/app/pages/not-found/not-found.component.spec.ts b/src/app/pages/not-found/not-found.component.spec.ts new file mode 100644 index 0000000..5b65d9e --- /dev/null +++ b/src/app/pages/not-found/not-found.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NotFoundComponent } from './not-found.component'; + +describe('NotFoundComponent', () => { + let component: NotFoundComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NotFoundComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NotFoundComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/not-found/not-found.component.ts b/src/app/pages/not-found/not-found.component.ts new file mode 100644 index 0000000..fe9d6ae --- /dev/null +++ b/src/app/pages/not-found/not-found.component.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-not-found', + imports: [], + templateUrl: './not-found.component.html', + styleUrl: './not-found.component.scss' +}) +export class NotFoundComponent { + + constructor(private readonly router: Router) {} + + + volverAlInicio() { + this.router.navigate(['/']); + } + +} diff --git a/src/app/pages/resumen/resumen.component.html b/src/app/pages/resumen/resumen.component.html new file mode 100644 index 0000000..cb5681c --- /dev/null +++ b/src/app/pages/resumen/resumen.component.html @@ -0,0 +1,87 @@ +
+ +
Cronogramas aprobados:
+ + + + Empresa + + Código de cronograma + + Nombre sistema + Tipo + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + + + + + + + +
Cronogramas rechazados:
+ + + + Empresa + + Código de cronograma + + Nombre sistema + Tipo + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + + + + + + +
diff --git a/src/app/pages/resumen/resumen.component.scss b/src/app/pages/resumen/resumen.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/resumen/resumen.component.spec.ts b/src/app/pages/resumen/resumen.component.spec.ts new file mode 100644 index 0000000..bc65d83 --- /dev/null +++ b/src/app/pages/resumen/resumen.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ResumenComponent } from './resumen.component'; + +describe('ResumenComponent', () => { + let component: ResumenComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ResumenComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ResumenComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/resumen/resumen.component.ts b/src/app/pages/resumen/resumen.component.ts new file mode 100644 index 0000000..cc628e8 --- /dev/null +++ b/src/app/pages/resumen/resumen.component.ts @@ -0,0 +1,68 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TableModule } from 'primeng/table'; +import { InputTextModule } from 'primeng/inputtext'; +import { TooltipModule } from 'primeng/tooltip'; +import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { VisorPdfComponent } from '../../components/visor-pdf/visor-pdf.component'; + +@Component({ + selector: 'app-resumen', + imports: [FormsModule, TableModule, InputTextModule, TooltipModule], + templateUrl: './resumen.component.html', + styleUrl: './resumen.component.scss', + providers: [DialogService] +}) +export class ResumenComponent { + + constructor(public dialogService: DialogService) { } + + ref: DynamicDialogRef + products: any[] = [ + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + }, + ]; + + firma(product: any): void { + console.log('Firma digital del producto:', product); + this.ref = this.dialogService.open(VisorPdfComponent, { + header: 'Visor PDF', + width: '80vw', + height: '80vh', + modal: true, + closable: true, + maximizable: true, + data:{ + product: product + }, + breakpoints: { + '960px': '75vw', + '640px': '90vw' + }, + }); + + } + +} diff --git a/src/app/pages/unidad-informacion/unidad-informacion.component.html b/src/app/pages/unidad-informacion/unidad-informacion.component.html new file mode 100644 index 0000000..2f7c847 --- /dev/null +++ b/src/app/pages/unidad-informacion/unidad-informacion.component.html @@ -0,0 +1,91 @@ +
+ +
Cronogramas solicitados y pendientes de carga:
+ + + + Empresa + Código de cronograma SINAR + Contiene obras del año + N° Oficio que aprueba + Tipo de carga + Fecha de solicitud + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + {{ product.dato5 }} + {{ product.dato6 }} + + + + +
Cronogramas aprobados:
+ + + + Empresa + Código cronograma + Nombre sistema + Tipo + Fecha aprobación + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + {{ product.dato5 }} + + + + +
Cronogramas rechazados:
+ + + + Empresa + Código de cronograma + Nombre sistema + Tipo + Fecha rechazo + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + {{ product.dato5 }} + + + +
diff --git a/src/app/pages/unidad-informacion/unidad-informacion.component.scss b/src/app/pages/unidad-informacion/unidad-informacion.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/unidad-informacion/unidad-informacion.component.spec.ts b/src/app/pages/unidad-informacion/unidad-informacion.component.spec.ts new file mode 100644 index 0000000..63febf1 --- /dev/null +++ b/src/app/pages/unidad-informacion/unidad-informacion.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UnidadInformacionComponent } from './unidad-informacion.component'; + +describe('UnidadInformacionComponent', () => { + let component: UnidadInformacionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UnidadInformacionComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(UnidadInformacionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/unidad-informacion/unidad-informacion.component.ts b/src/app/pages/unidad-informacion/unidad-informacion.component.ts new file mode 100644 index 0000000..c47d950 --- /dev/null +++ b/src/app/pages/unidad-informacion/unidad-informacion.component.ts @@ -0,0 +1,48 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TableModule } from 'primeng/table'; +import { InputTextModule } from 'primeng/inputtext'; + +@Component({ + selector: 'app-unidad-informacion', + imports: [FormsModule, TableModule, InputTextModule], + templateUrl: './unidad-informacion.component.html', + styleUrl: './unidad-informacion.component.scss' +}) +export class UnidadInformacionComponent { + products: any[] = [ + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + dato5: 'Inicial', + dato6: 'Inicial', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + dato5: 'Inicial', + dato6: 'Inicial', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + dato5: 'Inicial', + dato6: 'Inicial', + }, + { + empresa: 'Empresa A', + codigoCronograma: '123', + codigoCronogramaAjuste: '456', + tipoCarga: 'Inicial', + dato5: 'Inicial', + dato6: 'Inicial', + }, + ]; + +} diff --git a/src/app/services/actualizacion-pd.service.ts b/src/app/services/actualizacion-pd.service.ts new file mode 100644 index 0000000..192b4dd --- /dev/null +++ b/src/app/services/actualizacion-pd.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { ActualizacionPd } from '../models/actualizacion-pd.model'; + +@Injectable({ + providedIn: 'root' +}) +export class ActualizacionPdService { + private apiUrl = 'api/actualizaciones'; + + constructor(private http: HttpClient) {} + + getActualizaciones(): Observable { + return this.http.get(this.apiUrl); + } + + getActualizacionById(id: number): Observable { + return this.http.get(`${this.apiUrl}/${id}`); + } + + createActualizacion(actualizacion: ActualizacionPd): Observable { + return this.http.post(this.apiUrl, actualizacion); + } + + updateActualizacion(actualizacion: ActualizacionPd): Observable { + return this.http.put(`${this.apiUrl}/${actualizacion.id}`, actualizacion); + } + + deleteActualizacion(id: number): Observable { + return this.http.delete(`${this.apiUrl}/${id}`); + } + + aprobarActualizacion(id: number): Observable { + return this.http.patch(`${this.apiUrl}/${id}/aprobar`, {}); + } + + rechazarActualizacion(id: number, motivo: string): Observable { + return this.http.patch(`${this.apiUrl}/${id}/rechazar`, { motivo }); + } +} diff --git a/src/app/services/ajuste-pd.service.ts b/src/app/services/ajuste-pd.service.ts new file mode 100644 index 0000000..4c57f18 --- /dev/null +++ b/src/app/services/ajuste-pd.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { AjustePd } from '../models/ajuste-pd.model'; + +@Injectable({ + providedIn: 'root' +}) +export class AjustePdService { + private apiUrl = 'api/ajustes'; + + constructor(private http: HttpClient) {} + + getAjustes(): Observable { + return this.http.get(this.apiUrl); + } + + getAjusteById(id: number): Observable { + return this.http.get(`${this.apiUrl}/${id}`); + } + + createAjuste(ajuste: AjustePd): Observable { + return this.http.post(this.apiUrl, ajuste); + } + + updateAjuste(ajuste: AjustePd): Observable { + return this.http.put(`${this.apiUrl}/${ajuste.id}`, ajuste); + } + + deleteAjuste(id: number): Observable { + return this.http.delete(`${this.apiUrl}/${id}`); + } + + aprobarAjuste(id: number): Observable { + return this.http.patch(`${this.apiUrl}/${id}/aprobar`, {}); + } + + rechazarAjuste(id: number, motivo: string): Observable { + return this.http.patch(`${this.apiUrl}/${id}/rechazar`, { motivo }); + } +} diff --git a/src/app/services/alert.service.ts b/src/app/services/alert.service.ts new file mode 100644 index 0000000..da895db --- /dev/null +++ b/src/app/services/alert.service.ts @@ -0,0 +1,128 @@ +import { Injectable, ComponentRef, createComponent, EnvironmentInjector, ApplicationRef } from '@angular/core'; +import { AlertDialogComponent, AlertDialogOptions } from '../components/alert-dialog/alert-dialog.component'; + +@Injectable({ + providedIn: 'root' +}) +export class AlertService { + private alertRef: ComponentRef | null = null; + + constructor( + private injector: EnvironmentInjector, + private appRef: ApplicationRef + ) {} + + /** + * Muestra un mensaje de alerta tipo Sweet Alert + * @param options Opciones de configuración del alert + * @returns Una promesa que se resuelve con true (confirmar) o false (cancelar) + */ + show(options: Partial = {}): Promise { + return new Promise((resolve) => { + // Si ya existe un alert, lo eliminamos + this.closeCurrentAlert(); + + // Creamos el componente dinámicamente + this.alertRef = createComponent(AlertDialogComponent, { + environmentInjector: this.injector, + }); + + // Agregamos a la aplicación y al DOM + document.body.appendChild(this.alertRef.location.nativeElement); + this.appRef.attachView(this.alertRef.hostView); + + // Configuramos las opciones y mostramos + this.alertRef.instance.show(options); + + // Manejamos la respuesta + const subscription = this.alertRef.instance.onConfirm.subscribe((result) => { + subscription.unsubscribe(); + this.closeCurrentAlert(); + resolve(result); + }); + }); + } + + /** + * Muestra un mensaje de éxito + * @param message Mensaje a mostrar + * @param title Título del alert (opcional) + */ + success(message: string, title: string = '¡Operación exitosa!'): Promise { + return this.show({ + title, + message, + icon: 'success', + showCancelButton: false + }); + } + + /** + * Muestra un mensaje de error + * @param message Mensaje a mostrar + * @param title Título del alert (opcional) + */ + error(message: string, title: string = 'Error'): Promise { + return this.show({ + title, + message, + icon: 'error', + showCancelButton: false + }); + } + + /** + * Muestra un mensaje de advertencia + * @param message Mensaje a mostrar + * @param title Título del alert (opcional) + */ + warning(message: string, title: string = 'Advertencia'): Promise { + return this.show({ + title, + message, + icon: 'warning', + showCancelButton: false + }); + } + + /** + * Muestra un mensaje informativo + * @param message Mensaje a mostrar + * @param title Título del alert (opcional) + */ + info(message: string, title: string = 'Información'): Promise { + return this.show({ + title, + message, + icon: 'info', + showCancelButton: false + }); + } + + /** + * Muestra un diálogo de confirmación + * @param message Mensaje a mostrar + * @param title Título del alert (opcional) + */ + confirm(message: string, title: string = '¿Está seguro?'): Promise { + return this.show({ + title, + message, + icon: 'question', + showCancelButton: true, + confirmButtonText: 'Sí, confirmar', + cancelButtonText: 'Cancelar' + }); + } + + /** + * Cierra el alert actual si existe + */ + private closeCurrentAlert(): void { + if (this.alertRef) { + this.appRef.detachView(this.alertRef.hostView); + this.alertRef.location.nativeElement.remove(); + this.alertRef = null; + } + } +} \ No newline at end of file diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000..a69b102 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,111 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, BehaviorSubject, of, throwError } from 'rxjs'; +import { tap, delay } from 'rxjs/operators'; + +interface LoginCredentials { + username?: string; + password?: string; + email?: string; +} + +interface AuthResponse { + token: string; + user: { + id: number; + username: string; + name: string; + role: string; + email?: string; + }; +} + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + private apiUrl = 'api/auth'; + private userSubject = new BehaviorSubject(null); + public user$ = this.userSubject.asObservable(); + + // Usuarios de prueba para simular login + private mockUsers = [ + { + email: 'admin@example.com', + password: 'admin123', + user: { + id: 1, + username: 'admin', + name: 'Administrador', + role: 'admin', + email: 'admin@example.com' + } + }, + { + email: 'user@example.com', + password: 'user123', + user: { + id: 2, + username: 'user', + name: 'Usuario', + role: 'user', + email: 'user@example.com' + } + } + ]; + + constructor(private http: HttpClient) { + // Check if user is already logged in on service initialization + const user = localStorage.getItem('user'); + if (user) { + this.userSubject.next(JSON.parse(user)); + } + } + + login(credentials: LoginCredentials): Observable { + // Simular login con usuarios de prueba + const user = this.mockUsers.find(u => + u.email === credentials.email && u.password === credentials.password); + + if (user) { + // Generar token falso + const mockResponse: AuthResponse = { + token: 'mock-jwt-token-' + Math.random().toString(36).substring(2, 15), + user: user.user + }; + + return of(mockResponse).pipe( + // Simular retraso de red + delay(800), + tap(response => { + // Store token and user info + localStorage.setItem('token', response.token); + localStorage.setItem('user', JSON.stringify(response.user)); + this.userSubject.next(response.user); + }) + ); + } else { + // Simular error de credenciales inválidas + return throwError(() => new Error('Credenciales incorrectas')); + } + } + + logout(): void { + // Clear storage and update subject + localStorage.removeItem('token'); + localStorage.removeItem('user'); + this.userSubject.next(null); + } + + isLoggedIn(): boolean { + return !!localStorage.getItem('token'); + } + + getToken(): string | null { + return localStorage.getItem('token'); + } + + getCurrentUser(): any { + return this.userSubject.value; + } +} \ No newline at end of file diff --git a/src/app/services/cronograma.service.ts b/src/app/services/cronograma.service.ts new file mode 100644 index 0000000..78c6c98 --- /dev/null +++ b/src/app/services/cronograma.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { Cronograma } from '../models/cronograma.model'; + +@Injectable({ + providedIn: 'root' +}) +export class CronogramaService { + private apiUrl = 'api/cronogramas'; + + constructor(private http: HttpClient) {} + + getCronogramas(): Observable { + return this.http.get(this.apiUrl); + } + + getCronogramaById(id: number): Observable { + return this.http.get(`${this.apiUrl}/${id}`); + } + + createCronograma(cronograma: Cronograma): Observable { + return this.http.post(this.apiUrl, cronograma); + } + + updateCronograma(cronograma: Cronograma): Observable { + return this.http.put(`${this.apiUrl}/${cronograma.id}`, cronograma); + } + + deleteCronograma(id: number): Observable { + return this.http.delete(`${this.apiUrl}/${id}`); + } +} diff --git a/src/app/services/empresa.service.ts b/src/app/services/empresa.service.ts new file mode 100644 index 0000000..0bbadb3 --- /dev/null +++ b/src/app/services/empresa.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { Empresa } from '../models/empresa.model'; + +@Injectable({ + providedIn: 'root' +}) +export class EmpresaService { + private apiUrl = 'api/empresas'; + + constructor(private http: HttpClient) {} + + getEmpresas(): Observable { + return this.http.get(this.apiUrl); + } + + getEmpresaById(id: number): Observable { + return this.http.get(`${this.apiUrl}/${id}`); + } + + createEmpresa(empresa: Empresa): Observable { + return this.http.post(this.apiUrl, empresa); + } + + updateEmpresa(empresa: Empresa): Observable { + return this.http.put(`${this.apiUrl}/${empresa.id}`, empresa); + } + + deleteEmpresa(id: number): Observable { + return this.http.delete(`${this.apiUrl}/${id}`); + } +} diff --git a/src/app/services/estado-aprobacion.service.ts b/src/app/services/estado-aprobacion.service.ts new file mode 100644 index 0000000..d718b0a --- /dev/null +++ b/src/app/services/estado-aprobacion.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { EstadoAprobacion } from '../models/estado-aprobacion.model'; + +@Injectable({ + providedIn: 'root' +}) +export class EstadoAprobacionService { + private apiUrl = 'api/estadosAprobacion'; + + constructor(private http: HttpClient) {} + + getEstadosAprobacion(): Observable { + return this.http.get(this.apiUrl); + } +} diff --git a/src/app/services/index.ts b/src/app/services/index.ts new file mode 100644 index 0000000..dd35e28 --- /dev/null +++ b/src/app/services/index.ts @@ -0,0 +1,9 @@ +export * from './cronograma.service'; +export * from './empresa.service'; +export * from './actualizacion-pd.service'; +export * from './ajuste-pd.service'; +export * from './unidad-informacion.service'; +export * from './tipo-carga.service'; +export * from './estado-aprobacion.service'; +export * from './pdf.service'; +export * from './alert.service'; diff --git a/src/app/services/pdf.service.ts b/src/app/services/pdf.service.ts new file mode 100644 index 0000000..f0f8116 --- /dev/null +++ b/src/app/services/pdf.service.ts @@ -0,0 +1,214 @@ +import { Injectable } from '@angular/core'; +import pdfMake from 'pdfmake/build/pdfmake'; +import pdfFonts from 'pdfmake/build/vfs_fonts'; +import { TDocumentDefinitions } from 'pdfmake/interfaces'; + +pdfMake.vfs = pdfFonts.vfs; + +@Injectable({ + providedIn: 'root' +}) +export class PdfService { + + constructor() { } + + /** + * Genera un PDF con la definición por defecto para cronogramas + * @param data Datos opcionales para personalizar el documento + * @returns Promise con la URL de datos del PDF generado + */ + generateCronogramaPdf(data?: any): Promise { + const docDefinition = this.getCronogramaDocDefinition(data); + return this.generatePdfDataUrl(docDefinition); + } + + /** + * Descarga un PDF con la definición de cronograma + * @param filename Nombre del archivo a descargar + * @param data Datos opcionales para personalizar el documento + */ + downloadCronogramaPdf(filename: string = 'cronograma', data?: any): void { + const docDefinition = this.getCronogramaDocDefinition(data); + pdfMake.createPdf(docDefinition).download(filename); + } + + /** + * Genera una URL de datos a partir de la definición del documento + * @param docDefinition Definición del documento PDF + * @returns Promise con la URL de datos del PDF + */ + private generatePdfDataUrl(docDefinition: TDocumentDefinitions): Promise { + return new Promise((resolve) => { + const pdfDocGenerator = pdfMake.createPdf(docDefinition); + pdfDocGenerator.getDataUrl((dataUrl) => { + resolve(dataUrl); + }); + }); + } + + /** + * Obtiene la definición del documento para un cronograma + * @param data Datos opcionales para personalizar el documento + * @returns Definición del documento PDF + */ + private getCronogramaDocDefinition(data?: any): TDocumentDefinitions { + return { + content: [ + // Encabezado con información de empresa y cronograma + { + columns: [ + { + width: '50%', + text: 'Empresa: Constructora Los Andes S.A.', + margin: [0, 0, 0, 5] + }, + { + width: '50%', + text: [ + { text: 'Cronograma: SC-23-45\n' }, + { text: 'Nombre sistema: Terminal Portuario Norte' } + ], + alignment: 'right', + margin: [0, 0, 0, 5] + } + ] + }, + + // Título y subtítulo + { + text: 'CRONOGRAMA BASE DE OBRAS E INVERSIONES', + alignment: 'center', + bold: true, + fontSize: 12, + margin: [0, 10, 0, 0] + }, + { + text: 'ACTUALIZACIÓN PLAN DE DESARROLLO O NUEVA CONCESIÓN', + alignment: 'center', + bold: true, + fontSize: 11, + margin: [0, 0, 0, 10] + }, + + // Tabla principal + { + table: { + headerRows: 1, + widths: ['10%', '12%', '23%', '12%', '10%', '10%', '10%', '13%'], + body: [ + [ + { text: 'ETAPA', style: 'tableHeader', alignment: 'center' }, + { text: 'CÓDIGO GLOSA', style: 'tableHeader', alignment: 'center' }, + { text: 'DESCRIPCIÓN GLOSA', style: 'tableHeader', alignment: 'center' }, + { text: 'MONTO INVERSIÓN UF', style: 'tableHeader', alignment: 'center' }, + { text: 'AÑO INICIO', style: 'tableHeader', alignment: 'center' }, + { text: 'AÑO TERMINO', style: 'tableHeader', alignment: 'center' }, + { text: 'MES TERMINO', style: 'tableHeader', alignment: 'center' }, + { text: 'NOTA', style: 'tableHeader', alignment: 'center' } + ], + [ + { text: '1', alignment: 'center' }, + { text: 'INF-001', alignment: 'center' }, + { text: 'Estudios preliminares', alignment: 'left' }, + { text: '2,500', alignment: 'right' }, + { text: '2025', alignment: 'center' }, + { text: '2025', alignment: 'center' }, + { text: 'Julio', alignment: 'center' }, + { text: 'Fase inicial', alignment: 'left' } + ], + [ + { text: '2', alignment: 'center' }, + { text: 'MOV-102', alignment: 'center' }, + { text: 'Movimiento de tierras', alignment: 'left' }, + { text: '15,750', alignment: 'right' }, + { text: '2025', alignment: 'center' }, + { text: '2026', alignment: 'center' }, + { text: 'Enero', alignment: 'center' }, + { text: 'En terreno', alignment: 'left' } + ], + [ + { text: '3', alignment: 'center' }, + { text: 'CIM-203', alignment: 'center' }, + { text: 'Construcción de cimientos', alignment: 'left' }, + { text: '32,800', alignment: 'right' }, + { text: '2026', alignment: 'center' }, + { text: '2026', alignment: 'center' }, + { text: 'Mayo', alignment: 'center' }, + { text: 'Crítico', alignment: 'left' } + ], + [ + { text: '4', alignment: 'center' }, + { text: 'EST-304', alignment: 'center' }, + { text: 'Estructura principal', alignment: 'left' }, + { text: '65,420', alignment: 'right' }, + { text: '2026', alignment: 'center' }, + { text: '2027', alignment: 'center' }, + { text: 'Febrero', alignment: 'center' }, + { text: 'Alta inversión', alignment: 'left' } + ], + [ + { text: '5', alignment: 'center' }, + { text: 'TER-405', alignment: 'center' }, + { text: 'Terminaciones y acabados', alignment: 'left' }, + { text: '18,300', alignment: 'right' }, + { text: '2027', alignment: 'center' }, + { text: '2027', alignment: 'center' }, + { text: 'Octubre', alignment: 'center' }, + { text: 'Etapa final', alignment: 'left' } + ] + ] + } + }, + + // Total + { + columns: [ + { + width: '80%', + text: 'TOTAL:', + alignment: 'right', + bold: true, + margin: [0, 15, 10, 20] + }, + { + width: '20%', + text: '134,770 UF', + alignment: 'left', + bold: true, + margin: [0, 15, 0, 20] + } + ] + }, + + // Firma + { + text: 'X', + alignment: 'center', + bold: true, + fontSize: 14, + margin: [0, 15, 0, 0] + }, + { + text: 'Firma SSS', + alignment: 'center', + fontSize: 10, + margin: [0, 0, 0, 10] + } + ], + + // Estilos + styles: { + tableHeader: { + bold: true, + fontSize: 10, + fillColor: '#f2f2f2' + } + }, + + // Definiciones de página + pageSize: 'A4', + pageOrientation: 'portrait', + pageMargins: [40, 40, 40, 40] + }; + } +} \ No newline at end of file diff --git a/src/app/services/tipo-carga.service.ts b/src/app/services/tipo-carga.service.ts new file mode 100644 index 0000000..48d5d75 --- /dev/null +++ b/src/app/services/tipo-carga.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { TipoCarga } from '../models/tipo-carga.model'; + +@Injectable({ + providedIn: 'root' +}) +export class TipoCargaService { + private apiUrl = 'api/tiposCarga'; + + constructor(private http: HttpClient) {} + + getTiposCarga(): Observable { + return this.http.get(this.apiUrl); + } +} diff --git a/src/app/services/unidad-informacion.service.ts b/src/app/services/unidad-informacion.service.ts new file mode 100644 index 0000000..5056adb --- /dev/null +++ b/src/app/services/unidad-informacion.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { UnidadInformacion } from '../models/unidad-informacion.model'; + +@Injectable({ + providedIn: 'root' +}) +export class UnidadInformacionService { + private apiUrl = 'api/unidades'; + + constructor(private http: HttpClient) {} + + getUnidades(): Observable { + return this.http.get(this.apiUrl); + } + + getUnidadById(id: number): Observable { + return this.http.get(`${this.apiUrl}/${id}`); + } + + createUnidad(unidad: UnidadInformacion): Observable { + return this.http.post(this.apiUrl, unidad); + } + + updateUnidad(unidad: UnidadInformacion): Observable { + return this.http.put(`${this.apiUrl}/${unidad.id}`, unidad); + } + + deleteUnidad(id: number): Observable { + return this.http.delete(`${this.apiUrl}/${id}`); + } +} diff --git a/src/app/utils/custom-route-reuse-strategy.ts b/src/app/utils/custom-route-reuse-strategy.ts new file mode 100644 index 0000000..9f585d3 --- /dev/null +++ b/src/app/utils/custom-route-reuse-strategy.ts @@ -0,0 +1,42 @@ +import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router'; + +/** + * Estrategia personalizada que evita la reutilización de rutas + * para asegurar que los componentes se vuelvan a crear en cada navegación + */ +export class CustomRouteReuseStrategy implements RouteReuseStrategy { + /** + * No almacenar ninguna ruta al desactivarse + */ + shouldDetach(route: ActivatedRouteSnapshot): boolean { + return false; + } + + /** + * No hay rutas almacenadas, así que esto nunca se llama + */ + store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {} + + /** + * No recuperar rutas almacenadas + */ + shouldAttach(route: ActivatedRouteSnapshot): boolean { + return false; + } + + /** + * No hay rutas almacenadas, así que esto nunca se llama + */ + retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null { + return null; + } + + /** + * No reutilizar rutas para forzar la recreación de componentes + */ + shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { + // Solo reutilizar si es la misma ruta exactamente (evita problemas con rutas anidadas) + return future.routeConfig === curr.routeConfig && + JSON.stringify(future.params) === JSON.stringify(curr.params); + } +} \ No newline at end of file diff --git a/src/index.html b/src/index.html index 9037551..7f406d9 100644 --- a/src/index.html +++ b/src/index.html @@ -2,10 +2,13 @@ - CronogramasPrimeng + Cronogramas + + + diff --git a/src/pipes/safe.pipe.ts b/src/pipes/safe.pipe.ts new file mode 100644 index 0000000..2f2a405 --- /dev/null +++ b/src/pipes/safe.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; + +@Pipe({ + name: 'safe', + standalone: true +}) +export class SafePipe implements PipeTransform { + constructor(private sanitizer: DomSanitizer) {} + + transform(url: string): SafeResourceUrl { + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } +} \ No newline at end of file diff --git a/src/styles.scss b/src/styles.scss index 2811923..da62d51 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,69 +1,136 @@ + + :root { - --primary-color: #0088cc; // Color principal azul SISS - --secondary-color: #0a2847; // Color azul oscuro - --light-blue: #d3e9f7; // Color para el header - --danger-color: #dc3545; // Color rojo para los indicadores en inputs - --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --primary-color: #0088cc; /* Azul principal SISS */ + --primary-light: #bcdaef; /* Azul claro para fondos */ + --text-color: #0a2847; /* Color texto principal */ + --secondary-text: #6c757d; /* Texto secundario */ + --border-color: #dee2e6; /* Color bordes */ + --background-color: #f8f9fa; /* Fondo gris claro */ } - - // Estilos generales + + /* Estilos globales */ html, body { margin: 0; padding: 0; height: 100%; - font-family: var(--font-family); + /* font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; */ + /* font-family: "Museo Sans 300", "Museo Sans 700" !important; */ + font-family: "Museo Sans", sans-serif !important; + font-weight: 300; /* o 700 según necesites */ + font-size: 14px; + color: var(--text-color); } - + body { - background-color: #f0f0f0; + background-color: var(--background-color); } - - // Estilos específicos para inputs - .p-inputtext { - padding: 0.75rem 0.75rem; - font-size: 1rem; - } - - // Sobreescritura de estilos de PrimeNG - .p-button { - padding: 0.75rem 1.25rem; - border-radius: 4px; - - &.p-button-primary { - background-color: var(--primary-color); - border-color: var(--primary-color); - - &:hover { - background-color: darken(#0088cc, 10%); - border-color: darken(#0088cc, 10%); - } + + /* Clases de utilidad */ + .mb-1 { margin-bottom: 0.25rem !important; } + .mb-2 { margin-bottom: 0.5rem !important; } + .mb-3 { margin-bottom: 1rem !important; } + .mb-4 { margin-bottom: 1.5rem !important; } + .mt-1 { margin-top: 0.25rem !important; } + .mt-2 { margin-top: 0.5rem !important; } + .mt-3 { margin-top: 1rem !important; } + .mt-4 { margin-top: 1.5rem !important; } + + /* Personalizaciones globales de PrimeNG */ + /* .p-component { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + } */ + + .p-button.p-button-text { + &:focus { + box-shadow: none; } } - - // Clase para fondos rojos en los addons de input - .bg-red-600 { - background-color: var(--danger-color) !important; - border-color: var(--danger-color) !important; + + .tituloTabla { + color: #002147 !important; + } + + .tablaAzul{ + background-color: #008BC6 !important; color: white !important; } - - // Sobreescribe el estilo de los iconos en los input groups - .p-inputgroup-addon { - .pi { - color: #6c757d; - } + + #azul .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(odd) { + --p-datatable-row-striped-background: #EAF3FB; + background: var(--p-datatable-row-striped-background); } - - // Ajustes para espaciado consistente - .mb-3 { - margin-bottom: 1rem !important; + + #azul .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(even) { + --p-datatable-row-striped-background: #BCDAEF; + background: var(--p-datatable-row-striped-background); } - - .mb-4 { - margin-bottom: 1.5rem !important; + + .tablaVerde{ + background-color: #008BC6 !important; + color: white !important; + } + + #verde .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(odd) { + --p-datatable-row-striped-background: #F5F9F5; + background: var(--p-datatable-row-striped-background); + } + + #verde .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(even) { + --p-datatable-row-striped-background: #D0E1CD; + background: var(--p-datatable-row-striped-background); + } + + .tablaPloma { + background-color: #706f6f !important; + color: white !important; + } + + #ploma .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(odd) { + --p-datatable-row-striped-background: #E7E7E7; + background: var(--p-datatable-row-striped-background); + } + + #ploma .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(even) { + --p-datatable-row-striped-background: #CBCBCB; + background: var(--p-datatable-row-striped-background); + } + + .tablaVerde { + background-color: #706f6f !important; + color: white !important; + } + + #azul .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(odd) > td.bg-verde { + --p-datatable-row-striped-background: #F5F9F5; + background: var(--p-datatable-row-striped-background); + } + + #azul .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(even) > td.bg-verde { + --p-datatable-row-striped-background: #D0E1CD; + background: var(--p-datatable-row-striped-background); + } + + .tablaAzulFuerte{ + background-color: #156082 !important; + color: white !important; + } + + #azul .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(odd) > td.bg-azulFuerte { + --p-datatable-row-striped-background: #DCEAF7; + background: var(--p-datatable-row-striped-background); + } + + #azul .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(even) > td.bg-azulFuerte { + --p-datatable-row-striped-background: #A6CAEC; + background: var(--p-datatable-row-striped-background); + } + + /* Para redondear esquinas de tablas */ + .p-datatable-table-container{ + border-radius: var(--p-content-border-radius) !important; + } + + .selectTabla{ + width: 228px !important; } - - // Ajustes para el ancho completo - .w-full { - width: 100% !important; - } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 5525117..268414f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "compileOnSave": false, "compilerOptions": { "outDir": "./dist/out-tsc", - "strict": true, + "strict": false, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true,