Compare commits
No commits in common. "ad653dbdb405e19939acb1b6cddf7f9d2780d797" and "f64d76ab524e1b218f9b6944a8b6ab4bd6f2d6db" have entirely different histories.
ad653dbdb4
...
f64d76ab52
@ -1,66 +0,0 @@
|
|||||||
# 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
|
|
||||||
33
.gitignore
vendored
33
.gitignore
vendored
@ -5,18 +5,11 @@
|
|||||||
/tmp
|
/tmp
|
||||||
/out-tsc
|
/out-tsc
|
||||||
/bazel-out
|
/bazel-out
|
||||||
/build
|
|
||||||
backend/dist
|
|
||||||
backend/build
|
|
||||||
|
|
||||||
# Node
|
# Node
|
||||||
/node_modules
|
/node_modules
|
||||||
backend/node_modules
|
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
pnpm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
# IDEs and editors
|
# IDEs and editors
|
||||||
.idea/
|
.idea/
|
||||||
@ -40,36 +33,10 @@ lerna-debug.log*
|
|||||||
.sass-cache/
|
.sass-cache/
|
||||||
/connect.lock
|
/connect.lock
|
||||||
/coverage
|
/coverage
|
||||||
backend/coverage
|
|
||||||
backend/.nyc_output
|
|
||||||
/libpeerconnection.log
|
/libpeerconnection.log
|
||||||
testem.log
|
testem.log
|
||||||
/typings
|
/typings
|
||||||
.temp
|
|
||||||
.tmp
|
|
||||||
|
|
||||||
# System files
|
# System files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
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
|
|
||||||
67
.htaccess
67
.htaccess
@ -1,67 +0,0 @@
|
|||||||
# Habilitar compresión GZIP
|
|
||||||
<IfModule mod_deflate.c>
|
|
||||||
# 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
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# Habilitar caché de navegador
|
|
||||||
<IfModule mod_expires.c>
|
|
||||||
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"
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# Reglas de SPA para Angular
|
|
||||||
RewriteEngine On
|
|
||||||
RewriteBase /
|
|
||||||
RewriteRule ^index\.html$ - [L]
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
RewriteRule . /index.html [L]
|
|
||||||
197
README.md
197
README.md
@ -1,178 +1,59 @@
|
|||||||
# Cronogramas PrimeNG Application
|
# CronogramasPrimeng
|
||||||
|
|
||||||
Este proyecto es una aplicación Angular 19 utilizando PrimeNG para la gestión de cronogramas.
|
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.9.
|
||||||
|
|
||||||
## 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 <URL-del-repositorio>
|
|
||||||
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
|
## Development server
|
||||||
|
|
||||||
Para iniciar un servidor de desarrollo local, ejecute:
|
To start a local development server, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ng serve
|
ng serve
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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
|
||||||
|
```
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Para compilar el proyecto ejecute:
|
To build the project run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ng build
|
ng build
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Additional Resources
|
## Additional Resources
|
||||||
|
|
||||||
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).
|
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.
|
||||||
|
|||||||
61
angular.json
61
angular.json
@ -3,7 +3,7 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
"cronogramas": {
|
"cronogramas-primeng": {
|
||||||
"projectType": "application",
|
"projectType": "application",
|
||||||
"schematics": {
|
"schematics": {
|
||||||
"@schematics/angular:component": {
|
"@schematics/angular:component": {
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular-devkit/build-angular:application",
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": "dist/cronogramas",
|
"outputPath": "dist/cronogramas-primeng",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"browser": "src/main.ts",
|
"browser": "src/main.ts",
|
||||||
"polyfills": [
|
"polyfills": [
|
||||||
@ -29,44 +29,29 @@
|
|||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "public"
|
"input": "public"
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": ".htaccess",
|
|
||||||
"input": ".",
|
|
||||||
"output": "/"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"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": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"outputHashing": "all",
|
"budgets": [
|
||||||
"optimization": true,
|
{
|
||||||
"namedChunks": false,
|
"type": "initial",
|
||||||
"aot": true,
|
"maximumWarning": "500kB",
|
||||||
"extractLicenses": true
|
"maximumError": "1MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "4kB",
|
||||||
|
"maximumError": "8kB"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
@ -80,10 +65,10 @@
|
|||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"buildTarget": "cronogramas:build:production"
|
"buildTarget": "cronogramas-primeng:build:production"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"buildTarget": "cronogramas:build:development"
|
"buildTarget": "cronogramas-primeng:build:development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development"
|
||||||
@ -104,11 +89,6 @@
|
|||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "public"
|
"input": "public"
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": ".htaccess",
|
|
||||||
"input": ".",
|
|
||||||
"output": "/"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
@ -119,8 +99,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"cli": {
|
|
||||||
"analytics": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1167
package-lock.json
generated
1167
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -1,14 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cronogramas",
|
"name": "cronogramas-primeng",
|
||||||
"version": "0.1.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"build:prod": "ng build --configuration production",
|
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test",
|
"test": "ng test"
|
||||||
"postinstall": "node setup-project.js"
|
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -21,9 +19,6 @@
|
|||||||
"@angular/router": "^19.2.0",
|
"@angular/router": "^19.2.0",
|
||||||
"@primeng/themes": "^19.1.0",
|
"@primeng/themes": "^19.1.0",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"exceljs": "^4.4.0",
|
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"pdfmake": "^0.2.19",
|
|
||||||
"primeflex": "^4.0.0",
|
"primeflex": "^4.0.0",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primeng": "^19.1.0",
|
"primeng": "^19.1.0",
|
||||||
@ -35,10 +30,7 @@
|
|||||||
"@angular-devkit/build-angular": "^19.2.9",
|
"@angular-devkit/build-angular": "^19.2.9",
|
||||||
"@angular/cli": "^19.2.9",
|
"@angular/cli": "^19.2.9",
|
||||||
"@angular/compiler-cli": "^19.2.0",
|
"@angular/compiler-cli": "^19.2.0",
|
||||||
"@types/file-saver": "^2.0.7",
|
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"@types/pdfmake": "^0.2.11",
|
|
||||||
"@types/xlsx": "^0.0.35",
|
|
||||||
"jasmine-core": "~5.6.0",
|
"jasmine-core": "~5.6.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
@ -46,6 +38,5 @@
|
|||||||
"karma-jasmine": "~5.1.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"typescript": "~5.7.2"
|
"typescript": "~5.7.2"
|
||||||
},
|
}
|
||||||
"description": "cronogramas - Proyecto generado desde template"
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
147
setup-project.js
147
setup-project.js
@ -1,147 +0,0 @@
|
|||||||
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>/, `<title>${projectName}</title>`);
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
});
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
# # 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
|
|
||||||
@ -1,3 +1,2 @@
|
|||||||
|
|
||||||
<router-outlet />
|
<router-outlet />
|
||||||
<p-confirmDialog></p-confirmDialog>
|
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
|
||||||
import { ConfirmationService } from 'primeng/api';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet, ConfirmDialogModule],
|
imports: [RouterOutlet],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss',
|
styleUrl: './app.component.scss'
|
||||||
providers: [ConfirmationService]
|
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'SACG - Sistema Administrador de Cronogramas';
|
title = 'SACG - Sistema Administrador de Cronogramas';
|
||||||
|
|||||||
@ -1,31 +1,20 @@
|
|||||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||||
import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
|
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||||
import { providePrimeNG } from 'primeng/config';
|
import { providePrimeNG } from 'primeng/config';
|
||||||
import Aura from '@primeng/themes/aura';
|
import Aura from '@primeng/themes/aura';
|
||||||
import { authInterceptor } from './interceptors/auth.interceptor';
|
|
||||||
import { MessageService } from 'primeng/api';
|
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
provideRouter(
|
provideRouter(routes),
|
||||||
routes,
|
|
||||||
withPreloading(PreloadAllModules)
|
|
||||||
),
|
|
||||||
provideAnimations(),
|
provideAnimations(),
|
||||||
provideHttpClient(withFetch(), withInterceptors([authInterceptor])),
|
|
||||||
providePrimeNG({
|
providePrimeNG({
|
||||||
theme: {
|
theme: {
|
||||||
preset: Aura,
|
preset: Aura
|
||||||
options: {
|
|
||||||
darkModeSelector: false || 'none'
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}),
|
|
||||||
MessageService
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,54 +1,9 @@
|
|||||||
import { Routes, PreloadAllModules } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { LoginComponent } from './pages/login/login.component';
|
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 = [
|
export const routes: Routes = [
|
||||||
|
{ path: '', redirectTo: 'login', pathMatch: 'full' },
|
||||||
{ path: 'login', component: LoginComponent },
|
{ path: 'login', component: LoginComponent },
|
||||||
{
|
{ path: '**', redirectTo: 'login' }
|
||||||
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' }
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
<p-dialog
|
|
||||||
[(visible)]="visible"
|
|
||||||
[modal]="true"
|
|
||||||
[draggable]="false"
|
|
||||||
[resizable]="false"
|
|
||||||
[closeOnEscape]="true"
|
|
||||||
[style]="{width: '400px'}"
|
|
||||||
[contentStyle]="{'text-align': 'center', 'padding': '20px'}"
|
|
||||||
styleClass="alert-dialog"
|
|
||||||
[header]="title">
|
|
||||||
|
|
||||||
<div class="flex flex-column align-items-center">
|
|
||||||
<i class="pi text-8xl mb-4" [ngClass]="getIconClass()"></i>
|
|
||||||
<div class="text-xl font-semibold mb-3" *ngIf="title">{{ title }}</div>
|
|
||||||
<div class="text-center mb-5">{{ message }}</div>
|
|
||||||
|
|
||||||
<div class="flex justify-content-center gap-2">
|
|
||||||
<button *ngIf="showCancelButton" pButton (click)="cancel()"
|
|
||||||
class="p-button-outlined p-button-secondary"
|
|
||||||
[label]="cancelButtonText">
|
|
||||||
</button>
|
|
||||||
<button *ngIf="showConfirmButton" pButton (click)="confirm()"
|
|
||||||
class="p-button-primary"
|
|
||||||
[label]="confirmButtonText">
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</p-dialog>
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
:host ::ng-deep {
|
|
||||||
.alert-dialog {
|
|
||||||
.p-dialog-header {
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-dialog-content {
|
|
||||||
overflow-y: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
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<boolean>();
|
|
||||||
public onConfirm = this.confirmSubject.asObservable();
|
|
||||||
|
|
||||||
show(options: Partial<AlertDialogOptions> = {}): 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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
<!-- FOOTER -->
|
|
||||||
<footer class="footer mt-5">
|
|
||||||
<div class="footer-content">
|
|
||||||
<div class="footer-left">
|
|
||||||
<div class="footer-text">
|
|
||||||
<div>Superintendencia de Servicios Sanitarios</div>
|
|
||||||
<div>área de Información y Tecnologías</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="footer-center">
|
|
||||||
<img src="img/footer-logo.webp" alt="SISS Logo" class="footer-logo" loading="lazy" width="150" height="50">
|
|
||||||
</div>
|
|
||||||
<div class="footer-right">
|
|
||||||
<div>Dirección: Moneda 673 Piso 9 - Metro Santa Lucía</div>
|
|
||||||
<div>Mesa Central: 2 2382 4000</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { FooterComponent } from './footer.component';
|
|
||||||
|
|
||||||
describe('FooterComponent', () => {
|
|
||||||
let component: FooterComponent;
|
|
||||||
let fixture: ComponentFixture<FooterComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [FooterComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(FooterComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-footer',
|
|
||||||
imports: [],
|
|
||||||
templateUrl: './footer.component.html',
|
|
||||||
styleUrl: './footer.component.scss'
|
|
||||||
})
|
|
||||||
export class FooterComponent {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
|
|
||||||
<div class="layout-wrapper">
|
|
||||||
<p-toast baseZIndex="2000"></p-toast>
|
|
||||||
|
|
||||||
<!-- Overlay para cerrar el sidebar al hacer clic fuera (solo en móviles) -->
|
|
||||||
<div *ngIf="isSidebarVisible && window.innerWidth <= 992" class="sidebar-overlay" (click)="toggleSidebar()"></div>
|
|
||||||
|
|
||||||
<!-- Fixed sidebar -->
|
|
||||||
<div class="sidebar-wrapper" [ngClass]="{'sidebar-visible': isSidebarVisible}">
|
|
||||||
<app-sidebar></app-sidebar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main content area -->
|
|
||||||
<div class="main-content-wrapper" [ngClass]="{'with-sidebar': isSidebarVisible && window.innerWidth > 992}">
|
|
||||||
<!-- Top navbar -->
|
|
||||||
<app-navbar (sidebarToggle)="toggleSidebar()"></app-navbar>
|
|
||||||
|
|
||||||
<!-- Page content with animations -->
|
|
||||||
<div class="page-content">
|
|
||||||
<app-route-animations></app-route-animations>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<app-footer></app-footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
/* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { LayoutComponent } from './layout.component';
|
|
||||||
|
|
||||||
describe('LayoutComponent', () => {
|
|
||||||
let component: LayoutComponent;
|
|
||||||
let fixture: ComponentFixture<LayoutComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [LayoutComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(LayoutComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
<div class="navbar-container">
|
|
||||||
<div class="navbar-left">
|
|
||||||
<button pButton icon="pi pi-bars" class="p-button-text p-button-rounded sidebar-toggle"
|
|
||||||
(click)="toggleSidebar()"></button>
|
|
||||||
<span class="app-title">Administrador de Cronogramas</span>
|
|
||||||
</div>
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
|
|
||||||
<div style="flex-grow: 1; text-align: center;">
|
|
||||||
<img src="/img/teclado1.png" alt="Logo" style="width: 90% !important;height: 50px !important; opacity: 0.8; ">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<div class="navbar-right">
|
|
||||||
<span class="user-name">{{ userName }}</span>
|
|
||||||
<button pButton icon="pi pi-sign-out" class="p-button-text p-button-rounded logout-button"
|
|
||||||
(click)="confirmarAccion()"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Page title and breadcrumb -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="page-title tituloNavbar">{{ pageTitle }}</div>
|
|
||||||
<div class="breadcrumb-container">
|
|
||||||
<a routerLink="/inicio" class="breadcrumb-link">Inicio</a>
|
|
||||||
<span class="breadcrumb-separator">/</span>
|
|
||||||
<span class="breadcrumb-current">{{ pageTitle }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Componentes para mensajes y diálogos -->
|
|
||||||
<p-toast></p-toast>
|
|
||||||
<p-confirmDialog [style]="{width: '450px'}"></p-confirmDialog>
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { NavbarComponent } from './navbar.component';
|
|
||||||
|
|
||||||
describe('NavbarComponent', () => {
|
|
||||||
let component: NavbarComponent;
|
|
||||||
let fixture: ComponentFixture<NavbarComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [NavbarComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(NavbarComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
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<void>();
|
|
||||||
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']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
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: `
|
|
||||||
<div class="content-container" [class]="animationClass">
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
<div class="sidebar">
|
|
||||||
<!-- Logo container -->
|
|
||||||
<div class="logo-container">
|
|
||||||
<!-- Imagen del logo con posicionamiento para el badge -->
|
|
||||||
<div class="logo-image-container">
|
|
||||||
<img src="img/SAC-2.jpeg" alt="SISS Logo" class="logo-image">
|
|
||||||
<div class="version-badge">1.0</div>
|
|
||||||
</div>
|
|
||||||
<!-- Subtítulo del logo -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Separador -->
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<!-- Navigation Menu -->
|
|
||||||
<div class="menu-container">
|
|
||||||
<ul class="sidebar-menu">
|
|
||||||
<li class="menu-item" routerLinkActive="active">
|
|
||||||
<a routerLink="/inicio" class="menu-link">
|
|
||||||
<i class="menu-icon pi pi-home"></i>
|
|
||||||
<span class="menu-text">Inicio</span>
|
|
||||||
<span class="active-indicator"></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="menu-item" routerLinkActive="active">
|
|
||||||
<a routerLink="/unidad-concesiones" class="menu-link">
|
|
||||||
<i class="menu-icon pi pi-building"></i>
|
|
||||||
<span class="menu-text">Unidad de Concesiones</span>
|
|
||||||
<span class="active-indicator"></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="menu-item" routerLinkActive="active">
|
|
||||||
<a routerLink="/ct-actualizacion" class="menu-link">
|
|
||||||
<i class="menu-icon pi pi-flag"></i>
|
|
||||||
<span class="menu-text">CT Actualización de PD</span>
|
|
||||||
<span class="active-indicator"></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="menu-item" routerLinkActive="active">
|
|
||||||
<a routerLink="/ct-ajuste" class="menu-link">
|
|
||||||
<i class="menu-icon pi pi-sliders-h"></i>
|
|
||||||
<span class="menu-text">CT Ajuste de PD</span>
|
|
||||||
<span class="active-indicator"></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="menu-item" routerLinkActive="active">
|
|
||||||
<a routerLink="/resumen" class="menu-link">
|
|
||||||
<i class="menu-icon pi pi-list"></i>
|
|
||||||
<span class="menu-text">Resumen</span>
|
|
||||||
<span class="active-indicator"></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="menu-item" routerLinkActive="active">
|
|
||||||
<a routerLink="/unidad-informacion" class="menu-link">
|
|
||||||
<i class="menu-icon pi pi-box"></i>
|
|
||||||
<span class="menu-text">Unidad de Información</span>
|
|
||||||
<span class="active-indicator"></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,218 +0,0 @@
|
|||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { SidebarComponent } from './sidebar.component';
|
|
||||||
|
|
||||||
describe('SidebarComponent', () => {
|
|
||||||
let component: SidebarComponent;
|
|
||||||
let fixture: ComponentFixture<SidebarComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [SidebarComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(SidebarComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
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 {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
<div class="pdf-container">
|
|
||||||
<div class="pdf-viewer">
|
|
||||||
<iframe [src]="pdfSrc | safe" width="100%" height="100%"></iframe>
|
|
||||||
</div>
|
|
||||||
<div class="toolbar">
|
|
||||||
<button pButton type="button" class="p-button p-button-danger" (click)="descargarPDF()">
|
|
||||||
<i class="pi pi-file-pdf" style="margin-right: 8px;"></i>
|
|
||||||
Descargar PDF
|
|
||||||
</button>
|
|
||||||
<button pButton type="button" class="p-button p-button-success ml-2" (click)="enviarPDF()" [disabled]="enviando">
|
|
||||||
<ng-container *ngIf="!enviando; else cargando">
|
|
||||||
<i class="pi pi-send" style="margin-right: 8px;"></i>
|
|
||||||
Enviar
|
|
||||||
</ng-container>
|
|
||||||
<ng-template #cargando>
|
|
||||||
<p-progressSpinner [style]="{width: '20px', height: '20px'}" styleClass="custom-spinner" strokeWidth="4" fill="var(--surface-ground)" animationDuration=".5s"></p-progressSpinner>
|
|
||||||
<span style="margin-left: 8px;">Enviando...</span>
|
|
||||||
</ng-template>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
.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;
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { VisorPdfComponent } from './visor-pdf.component';
|
|
||||||
|
|
||||||
describe('VisorPdfComponent', () => {
|
|
||||||
let component: VisorPdfComponent;
|
|
||||||
let fixture: ComponentFixture<VisorPdfComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [VisorPdfComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(VisorPdfComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
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;
|
|
||||||
};
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
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<unknown>,
|
|
||||||
next: HttpHandlerFn
|
|
||||||
): Observable<any> => {
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import { Cronograma } from './cronograma.model';
|
|
||||||
|
|
||||||
export interface ActualizacionPd extends Cronograma {
|
|
||||||
dato9?: string;
|
|
||||||
dato10?: string;
|
|
||||||
dato11?: string;
|
|
||||||
dato12?: string;
|
|
||||||
dato13?: string;
|
|
||||||
dato14?: string;
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
export interface Cronograma {
|
|
||||||
id?: number;
|
|
||||||
empresa: string;
|
|
||||||
codigoCronograma: string;
|
|
||||||
codigoCronogramaAjuste: string;
|
|
||||||
tipoCarga: string;
|
|
||||||
estadoRevision?: string;
|
|
||||||
analista?: string;
|
|
||||||
fechaIngreso?: string;
|
|
||||||
semaforo?: 'green' | 'yellow' | 'red';
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export interface Empresa {
|
|
||||||
id?: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export interface EstadoAprobacion {
|
|
||||||
id?: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
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';
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export interface TipoCarga {
|
|
||||||
id?: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { Cronograma } from './cronograma.model';
|
|
||||||
|
|
||||||
export interface UnidadInformacion extends Cronograma {
|
|
||||||
dato5?: string;
|
|
||||||
dato6?: string;
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
<div class="p-3 text-white">
|
|
||||||
<div
|
|
||||||
class="flex align-content-start flex-wrap gap-2 border-round border-blue-400 my-2 lg:px-0 py-3 my-1"
|
|
||||||
>
|
|
||||||
<div class="col-12 md:col-3 lg:col-2 tablaAzul border-round">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<div class="h-4rem">Filtro Empresa</div>
|
|
||||||
<p-select appendTo="body"
|
|
||||||
[options]="empresas"
|
|
||||||
[(ngModel)]="selectedCity"
|
|
||||||
optionLabel="name"
|
|
||||||
placeholder="Seleccione..."
|
|
||||||
class="w-full md:w-56"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 md:col-3 lg:col-2 tablaAzul border-round">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<div class="h-4rem">Filtro Código Cronograma</div>
|
|
||||||
<p-select appendTo="body"
|
|
||||||
[options]="empresas"
|
|
||||||
[(ngModel)]="selectedCity"
|
|
||||||
optionLabel="name"
|
|
||||||
placeholder="Seleccione..."
|
|
||||||
class="w-full md:w-56"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Tabla1 -->
|
|
||||||
<div class="flex justify-content-end align-items-center my-2 py-2">
|
|
||||||
<!-- <div class="font-bold tituloTabla">Título de la tabla:</div> -->
|
|
||||||
<p-button
|
|
||||||
icon="pi pi-file-excel"
|
|
||||||
(onClick)="exportExcelWithStyles(dt)"
|
|
||||||
styleClass="p-button-success"
|
|
||||||
pTooltip="Descargar planilla Excel"
|
|
||||||
tooltipPosition="top"
|
|
||||||
label="Exportar"
|
|
||||||
[tooltipOptions]="{showDelay: 100, appendTo: 'body'}"
|
|
||||||
>
|
|
||||||
</p-button>
|
|
||||||
</div>
|
|
||||||
<p-table
|
|
||||||
id="azul"
|
|
||||||
#dt
|
|
||||||
[value]="products"
|
|
||||||
stripedRows
|
|
||||||
showGridlines
|
|
||||||
[paginator]="true"
|
|
||||||
[rows]="5"
|
|
||||||
[showCurrentPageReport]="true"
|
|
||||||
currentPageReportTemplate="Mostrando del {first} al {last} de un total de {totalRecords} registros"
|
|
||||||
[rowsPerPageOptions]="[5, 10, 20]"
|
|
||||||
styleClass="p-datatable-sm"
|
|
||||||
>
|
|
||||||
<ng-template pTemplate="header">
|
|
||||||
<tr>
|
|
||||||
<th class="tablaAzul font-bold text-white">Empresa</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Código de cronograma</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Etapa del Servicio</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Nombre sistema</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Tipo de inversión</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Código de glosa PD</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Descripción glosa</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Monto Inversión Total (UF)</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Año de Inicio</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Año de Término</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Mes de Término</th>
|
|
||||||
<th class="tablaAzul font-bold text-white">Nota</th>
|
|
||||||
<th class="bg-green-400 font-bold text-white">Estado aprobación</th>
|
|
||||||
<th class="bg-green-400 font-bold text-white">Observación</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template pTemplate="body" let-product>
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.empresa }}</td>
|
|
||||||
<td>{{ product.codigoCronograma }}</td>
|
|
||||||
<td>{{ product.codigoCronogramaAjuste }}</td>
|
|
||||||
<td>{{ product.tipoCarga }}</td>
|
|
||||||
<td>{{ product.estadoRevision }}</td>
|
|
||||||
<td>{{ product.estadoRevision }}</td>
|
|
||||||
<td>{{ product.fechaIngreso }}</td>
|
|
||||||
<td>{{ product.estadoRevision }}</td>
|
|
||||||
<td>{{ product.dato9 }}</td>
|
|
||||||
<td>{{ product.dato10 }}</td>
|
|
||||||
<td>{{ product.dato11 }}</td>
|
|
||||||
<td>{{ product.dato12 }}</td>
|
|
||||||
<td class="bg-verde">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<p-select appendTo="body"
|
|
||||||
[options]="estadoAprobacion"
|
|
||||||
optionValue="value"
|
|
||||||
[(ngModel)]="product.dato13"
|
|
||||||
appendTo="body"
|
|
||||||
optionLabel="name"
|
|
||||||
placeholder="Seleccione..."
|
|
||||||
class="selectTabla"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="bg-verde">
|
|
||||||
<input
|
|
||||||
type="analista"
|
|
||||||
pInputText
|
|
||||||
[hidden]="product.dato13"
|
|
||||||
[(ngModel)]="product.analista"
|
|
||||||
placeholder="Analista"
|
|
||||||
name="analista"
|
|
||||||
class="input-with-icon w-full"
|
|
||||||
style="
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
width: 228px !important;
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
</div>
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ActualizacionPdComponent } from './actualizacion-pd.component';
|
|
||||||
|
|
||||||
describe('ActualizacionPdComponent', () => {
|
|
||||||
let component: ActualizacionPdComponent;
|
|
||||||
let fixture: ComponentFixture<ActualizacionPdComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [ActualizacionPdComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ActualizacionPdComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,198 +0,0 @@
|
|||||||
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<void> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
<div class="p-3 text-white">
|
|
||||||
<div
|
|
||||||
class="flex align-content-start flex-wrap gap-2 border-round border-blue-400 my-2 lg:px-0 py-3 my-1"
|
|
||||||
>
|
|
||||||
<div class="col-6 md:col-3 lg:col-2 tablaAzul border-round">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<div class="h-4rem">Filtro Empresa</div>
|
|
||||||
<p-select appendTo="body"
|
|
||||||
[options]="empresas"
|
|
||||||
[(ngModel)]="selectedCity"
|
|
||||||
optionLabel="name"
|
|
||||||
placeholder="Seleccione..."
|
|
||||||
class="w-full md:w-56"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-6 md:col-3 lg:col-2 tablaAzul border-round">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<div class="h-4rem">Filtro Código Cronograma</div>
|
|
||||||
<p-select appendTo="body"
|
|
||||||
[options]="empresas"
|
|
||||||
[(ngModel)]="selectedCity"
|
|
||||||
optionLabel="name"
|
|
||||||
placeholder="Seleccione..."
|
|
||||||
class="w-full md:w-56"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Tabla1 -->
|
|
||||||
<!-- <div class="font-bold text-black-alpha-90 my-2">Título de la tabla:</div> -->
|
|
||||||
|
|
||||||
<!-- Botón de exportar a Excel (movido arriba) -->
|
|
||||||
<div class="flex justify-content-end align-items-center my-2 py-2">
|
|
||||||
<button
|
|
||||||
pButton
|
|
||||||
type="button"
|
|
||||||
(click)="exportExcelWithStyles(dt)"
|
|
||||||
icon="pi pi-file-excel"
|
|
||||||
class="p-button-success"
|
|
||||||
label="Exportar"
|
|
||||||
pTooltip="Descargar planilla Excel"
|
|
||||||
tooltipPosition="top"
|
|
||||||
[tooltipOptions]="{showDelay: 100, appendTo: 'body'}"
|
|
||||||
></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p-table
|
|
||||||
#dt
|
|
||||||
id="azul"
|
|
||||||
[value]="products"
|
|
||||||
stripedRows
|
|
||||||
showGridlines
|
|
||||||
[paginator]="true"
|
|
||||||
[rows]="5"
|
|
||||||
[showCurrentPageReport]="true"
|
|
||||||
currentPageReportTemplate="Mostrando del {first} al {last} de un total de {totalRecords} registros"
|
|
||||||
[rowsPerPageOptions]="[5, 10, 20]"
|
|
||||||
>
|
|
||||||
<ng-template #header>
|
|
||||||
<tr>
|
|
||||||
<th colspan="9" style="background-color: #f8f9fa; border: none"></th>
|
|
||||||
<th colspan="3" class="tablaAzul text-white font-bold text-center">Cronograma base vigente</th>
|
|
||||||
<th colspan="4" class="bg-blue-700 text-white font-bold text-center">Cronograma base ajustado</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th class="tablaAzul text-white font-bold">Empresa</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Código de cronograma</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Etapa del Servicio</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Nombre sistema</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Nombre localidad</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Tipo de inversión</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Código de glosa PD</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Descripción glosa</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Monto Inversión Total (UF)</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Año de Inicio</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Año de Término</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Mes de Término</th>
|
|
||||||
<th class="bg-blue-700 text-white font-bold">Tipo de ajuste</th>
|
|
||||||
<th class="bg-blue-700 text-white font-bold">Año de Inicio</th>
|
|
||||||
<th class="bg-blue-700 text-white font-bold">Año de Término</th>
|
|
||||||
<th class="bg-blue-700 text-white font-bold">Mes de Término</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Nota</th>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Estado aprobación</th>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Observación</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #body let-product>
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.empresa }}</td>
|
|
||||||
<td>{{ product.codigoCronograma }}</td>
|
|
||||||
<td>{{ product.codigoCronogramaAjuste }}</td>
|
|
||||||
<td>{{ product.tipoCarga }}</td>
|
|
||||||
<td>{{ product.estadoRevision }}</td>
|
|
||||||
<td>{{ product.fechaIngreso }}</td>
|
|
||||||
<td>{{ product.estadoRevision }}</td>
|
|
||||||
<td>{{ product.dato9 }}</td>
|
|
||||||
<td>{{ product.dato10 }}</td>
|
|
||||||
<td>{{ product.dato13 }}</td>
|
|
||||||
<td>{{ product.dato14 }}</td>
|
|
||||||
<td>{{ product.dato15 }}</td>
|
|
||||||
<td class="bg-azulFuerte">{{ product.dato16 }}</td>
|
|
||||||
<td class="bg-azulFuerte">{{ product.dato17 }}</td>
|
|
||||||
<td class="bg-azulFuerte">{{ product.dato18 }}</td>
|
|
||||||
<td class="bg-azulFuerte">{{ product.dato11 }}</td>
|
|
||||||
<td>{{ product.dato12 }}</td>
|
|
||||||
<td class="bg-verde">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<p-select appendTo="body"
|
|
||||||
[options]="estadoAprobacion"
|
|
||||||
optionValue="value"
|
|
||||||
[(ngModel)]="product.dato13"
|
|
||||||
appendTo="body"
|
|
||||||
optionLabel="name"
|
|
||||||
placeholder="Seleccione..."
|
|
||||||
class="selectTabla"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="bg-verde">
|
|
||||||
<input
|
|
||||||
type="analista"
|
|
||||||
pInputText
|
|
||||||
[hidden]="product.dato13"
|
|
||||||
[(ngModel)]="product.analista"
|
|
||||||
placeholder="Analista"
|
|
||||||
name="analista"
|
|
||||||
class="input-with-icon w-full"
|
|
||||||
style="
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
width: 228px !important;
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
<!-- Se eliminó el botón que estaba aquí abajo -->
|
|
||||||
</div>
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { AjustePdComponent } from './ajuste-pd.component';
|
|
||||||
|
|
||||||
describe('AjustePdComponent', () => {
|
|
||||||
let component: AjustePdComponent;
|
|
||||||
let fixture: ComponentFixture<AjustePdComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [AjustePdComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(AjustePdComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,381 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
<div class="p-3 text-white">
|
|
||||||
<!-- Tabla1 -->
|
|
||||||
<div class="font-bold my-2 tituloTabla">Cronogramas cargados:</div>
|
|
||||||
<p-table id="azul"
|
|
||||||
[value]="products" stripedRows
|
|
||||||
showGridlines
|
|
||||||
[paginator]="true"
|
|
||||||
[rows]="5"
|
|
||||||
[showCurrentPageReport]="true"
|
|
||||||
currentPageReportTemplate="Mostrando del {first} al {last} de un total de {totalRecords} registros"
|
|
||||||
[rowsPerPageOptions]="[5, 10, 20]">
|
|
||||||
<ng-template #header>
|
|
||||||
<tr>
|
|
||||||
<th class="tablaAzul font-bold">Empresa</th>
|
|
||||||
<th class="tablaAzul font-bold">Código de cronograma</th>
|
|
||||||
<th class="tablaAzul font-bold">Código cronograma de ajuste</th>
|
|
||||||
<th class="tablaAzul font-bold">Tipo de carga</th>
|
|
||||||
<th class="bg-green-400 font-bold text-white font-bold">Estado de revisión</th>
|
|
||||||
<th class="bg-green-400 font-bold text-white font-bold">Analista</th>
|
|
||||||
<th class="bg-green-400 font-bold text-white font-bold">Fecha ingreso</th>
|
|
||||||
<th class="bg-green-400 font-bold text-white font-bold">Semáforo</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #body let-product>
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.empresa }}</td>
|
|
||||||
<td>{{ product.codigoCronograma }}</td>
|
|
||||||
<td>{{ product.codigoCronogramaAjuste }}</td>
|
|
||||||
<td>{{ product.tipoCarga }}</td>
|
|
||||||
<td class="bg-verde">{{ product.estadoRevision }}</td>
|
|
||||||
<td class="bg-verde">
|
|
||||||
<!-- analista -->
|
|
||||||
<input
|
|
||||||
type="analista"
|
|
||||||
pInputText
|
|
||||||
[(ngModel)]="product.analista"
|
|
||||||
placeholder="Analista"
|
|
||||||
name="analista"
|
|
||||||
class="input-with-icon w-full"
|
|
||||||
style="background-color: white; color: black;width: 228px !important;"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td class="bg-verde">{{ product.fechaIngreso }}</td>
|
|
||||||
<td class="bg-verde">
|
|
||||||
<!-- {{ product.semaforo }} -->
|
|
||||||
<div class="text-center">
|
|
||||||
<i class="pi pi-circle-fill" style="font-size: 24px;"
|
|
||||||
[style.color]="product.semaforo === 'green' ? '#00bb00' : product.semaforo === 'yellow' ? 'yellow' : product.semaforo === 'red' ? 'red' : ''"></i>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
|
|
||||||
<div class="flex justify-content-between flex-wrap gap-3 border-round border-blue-400 my-6 lg:px-0 py-3 my-1 ">
|
|
||||||
<div class="col-12 md:col-5 lg:col-2 tablaAzul border-round ">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<div class="h-4rem">Filtro Empresa</div>
|
|
||||||
<p-select appendTo="body" [options]="empresas" [(ngModel)]="select1" optionLabel="name" placeholder="Seleccione..." class="w-full md:w-56" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 md:col-5 lg:col-2 tablaAzul border-round">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<div class="h-4rem">Filtro Código Cronograma SINAR</div>
|
|
||||||
<p-select appendTo="body" [options]="empresas" [(ngModel)]="select2" optionLabel="name" placeholder="Seleccione..." class="w-full md:w-56" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 md:col-5 lg:col-2 tablaAzul border-round">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<div class="h-4rem">Filtro tipo de carga</div>
|
|
||||||
<p-select appendTo="body" [options]="tipoCarga" [(ngModel)]="select3" optionLabel="name" placeholder="Seleccione..." class="w-full md:w-56" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 md:col-5 lg:col-2 tablaAzul border-round">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<div class="h-4rem">¿Contiene obras del año?</div>
|
|
||||||
<p-select appendTo="body" [options]="contieneObras" [(ngModel)]="select4" optionLabel="name" placeholder="Seleccione..." class="w-full md:w-56" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 md:col-5 lg:col-2 tablaAzul border-round">
|
|
||||||
<div class="border-round font-bold">
|
|
||||||
<div class="h-4rem">N° Oficio que aprueba</div>
|
|
||||||
<!-- Numero Oficio -->
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
pInputText
|
|
||||||
placeholder="xxxxx"
|
|
||||||
name="numeroOficio"
|
|
||||||
class="input-with-icon w-full mt-1 text-center sm:text-start"
|
|
||||||
style="background-color: white; color: black;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 text-center">
|
|
||||||
<button type="button" class="bg-primary border-primary-500 mt-3 px-5 py-3 text-base border-1 border-solid border-round cursor-pointer transition-all transition-duration-200 hover:bg-primary-600 hover:border-primary-600 active:bg-primary-700 active:border-primary-700">Crear solicitud</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tabla2 -->
|
|
||||||
<div class="font-bold my-2 tituloTabla">Cronogramas solicitados y pendientes de carga:</div>
|
|
||||||
<p-table id="azul" sortField="price" [sortOrder]="-1"
|
|
||||||
[value]="products" stripedRows
|
|
||||||
showGridlines
|
|
||||||
[paginator]="true"
|
|
||||||
[rows]="5"
|
|
||||||
[showCurrentPageReport]="true"
|
|
||||||
currentPageReportTemplate="Mostrando del {first} al {last} de un total de {totalRecords} registros"
|
|
||||||
[rowsPerPageOptions]="[5, 10, 20]">
|
|
||||||
<ng-template #header>
|
|
||||||
<tr>
|
|
||||||
<th pSortableColumn="empresa" class="tablaAzul font-bold">
|
|
||||||
Empresa <p-sortIcon field="empresa" />
|
|
||||||
</th>
|
|
||||||
<th pSortableColumn="codigoCronograma" class="tablaAzul font-bold">
|
|
||||||
Código de cronograma SINAR <p-sortIcon field="codigoCronograma" />
|
|
||||||
</th>
|
|
||||||
<th pSortableColumn="codigoCronogramaAjuste" class="tablaAzul font-bold">
|
|
||||||
Contiene obras del año <p-sortIcon field="codigoCronogramaAjuste" />
|
|
||||||
</th>
|
|
||||||
<th pSortableColumn="tipoCarga" class="tablaAzul font-bold">
|
|
||||||
N° Oficio que aprueba <p-sortIcon field="tipoCarga" />
|
|
||||||
</th>
|
|
||||||
<th pSortableColumn="estadoRevision" class="tablaAzul font-bold">
|
|
||||||
Tipo de carga <p-sortIcon field="estadoRevision" />
|
|
||||||
</th>
|
|
||||||
<th pSortableColumn="fechaIngreso" class="tablaAzul font-bold">
|
|
||||||
Fecha de solicitud <p-sortIcon field="fechaIngreso" />
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #body let-product>
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.empresa }}</td>
|
|
||||||
<td>{{ product.codigoCronograma }}</td>
|
|
||||||
<td>{{ product.codigoCronogramaAjuste }}</td>
|
|
||||||
<td>{{ product.tipoCarga }}</td>
|
|
||||||
<td>{{ product.estadoRevision }}</td>
|
|
||||||
<td>{{ product.fechaIngreso }}</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
.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%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ConcesionesComponent } from './concesiones.component';
|
|
||||||
|
|
||||||
describe('ConcesionesComponent', () => {
|
|
||||||
let component: ConcesionesComponent;
|
|
||||||
let fixture: ComponentFixture<ConcesionesComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [ConcesionesComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ConcesionesComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
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'
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
|
|
||||||
<div class="home-page">
|
|
||||||
<!-- Simple Card -->
|
|
||||||
<p-card styleClass="mb-4">
|
|
||||||
<ng-template pTemplate="title">
|
|
||||||
<div class="card-title">Simple Card</div>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template pTemplate="content">
|
|
||||||
<p>Lorem ipsum dolor sit amet...</p>
|
|
||||||
</ng-template>
|
|
||||||
</p-card>
|
|
||||||
|
|
||||||
<!-- Accordion sections -->
|
|
||||||
<p-accordion [multiple]="true">
|
|
||||||
<p-accordionTab header="Header I" [selected]="true">
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
</p-accordionTab>
|
|
||||||
|
|
||||||
<p-accordionTab header="Header II">
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
|
||||||
magna aliqua.
|
|
||||||
</p>
|
|
||||||
</p-accordionTab>
|
|
||||||
|
|
||||||
<p-accordionTab header="Header III">
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|
||||||
</p>
|
|
||||||
</p-accordionTab>
|
|
||||||
</p-accordion>
|
|
||||||
|
|
||||||
<p-accordion [multiple]="true">
|
|
||||||
<p-accordionTab header="Header I" [selected]="true">
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
</p-accordionTab>
|
|
||||||
|
|
||||||
<p-accordionTab header="Header II">
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
|
||||||
magna aliqua.
|
|
||||||
</p>
|
|
||||||
</p-accordionTab>
|
|
||||||
|
|
||||||
<p-accordionTab header="Header III">
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|
||||||
</p>
|
|
||||||
</p-accordionTab>
|
|
||||||
</p-accordion>
|
|
||||||
|
|
||||||
<p-accordion [multiple]="true">
|
|
||||||
<p-accordionTab header="Header I" [selected]="true">
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
</p-accordionTab>
|
|
||||||
|
|
||||||
<p-accordionTab header="Header II">
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
|
||||||
magna aliqua.
|
|
||||||
</p>
|
|
||||||
</p-accordionTab>
|
|
||||||
|
|
||||||
<p-accordionTab header="Header III">
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|
||||||
</p>
|
|
||||||
</p-accordionTab>
|
|
||||||
</p-accordion>
|
|
||||||
</div>
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { HomeComponent } from './home.component';
|
|
||||||
|
|
||||||
describe('HomeComponent', () => {
|
|
||||||
let component: HomeComponent;
|
|
||||||
let fixture: ComponentFixture<HomeComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [HomeComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(HomeComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
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 {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,116 +1,49 @@
|
|||||||
<!-- Header container -->
|
<!-- Header container -->
|
||||||
<div class="header-container">
|
<div class="header">
|
||||||
<div class="header-image">
|
<div class="header-content">
|
||||||
<div class="water-drop-container">
|
<img src="assets/img/siss-logo.png" alt="SISS Logo" class="siss-logo">
|
||||||
<img src="img/gota.png" alt="gota" class="water-drop" width="41" height="60" fetchpriority="high">
|
<div class="sacg-container">
|
||||||
<div class="text-drop">1.0</div>
|
<div class="sacg-title">
|
||||||
|
<span>SACG</span>
|
||||||
|
<div class="version-badge">1.0</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-text">
|
<div class="sacg-subtitle">Sistema Administrador de Cronogramas</div>
|
||||||
<div class="sub-title">Administrador de Cronogramas</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Toast para mensajes -->
|
|
||||||
<p-toast></p-toast>
|
|
||||||
|
|
||||||
<!-- MAIN CONTENT -->
|
<!-- MAIN CONTENT -->
|
||||||
<main class="main-content flex align-items-center justify-content-center">
|
<main class="main-content">
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<div class="login-box">
|
<div class="login-box">
|
||||||
<!-- Contenedor principal con posición relativa -->
|
<div class="login-card">
|
||||||
<div class="position-relative overflow-hidden">
|
|
||||||
|
|
||||||
<!-- PANEL DE LOGIN -->
|
|
||||||
<div class="panel-container w-full"
|
|
||||||
[ngClass]="{'animate__animated animate__fadeOut d-none': showRecovery,
|
|
||||||
'animate__animated animate__fadeIn': !showRecovery && !isInitialLoad}">
|
|
||||||
<div class="login-card shadow-2 border-round">
|
|
||||||
<div class="login-header">
|
<div class="login-header">
|
||||||
<h2>Iniciar Sesión</h2>
|
<h2>Iniciar Sesión</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form (ngSubmit)="onLogin()" class="p-3">
|
<form (ngSubmit)="onLogin()">
|
||||||
<!-- Email -->
|
<div class="p-inputgroup mb-3">
|
||||||
<div class="field mb-3">
|
<input type="email" pInputText [(ngModel)]="email" name="email" placeholder="Email" class="w-full">
|
||||||
<input type="email" pInputText [(ngModel)]="email" name="email" placeholder="Email"
|
<span class="p-inputgroup-addon bg-red-600 text-white">@</span>
|
||||||
class="input-with-icon w-full" required />
|
<span class="p-inputgroup-addon">
|
||||||
|
<i class="pi pi-envelope"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-inputgroup mb-4">
|
||||||
|
<input type="password" pInputText [(ngModel)]="password" name="password" placeholder="Password" class="w-full">
|
||||||
|
<span class="p-inputgroup-addon bg-red-600 text-white">***</span>
|
||||||
|
<span class="p-inputgroup-addon">
|
||||||
|
<i class="pi pi-lock"></i>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Password -->
|
|
||||||
<div class="field mb-3">
|
|
||||||
<input type="password" pInputText [(ngModel)]="password" name="password" placeholder="Password"
|
|
||||||
class="input-with-lock w-full" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mensaje de error -->
|
|
||||||
<div *ngIf="errorMessage" class="error-message my-2">
|
|
||||||
<p-message severity="error" [text]="errorMessage"></p-message>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Botón -->
|
|
||||||
<div class="login-actions">
|
<div class="login-actions">
|
||||||
<button pButton type="submit" [label]="loading ? 'Autenticando...' : 'Autenticar'"
|
<button pButton type="submit" label="Autenticar" class="p-button-primary"></button>
|
||||||
class="p-button-primary w-full" [disabled]="loading || !email || !password">
|
|
||||||
<i *ngIf="loading" class="pi pi-spin pi-spinner mr-2"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Recuperar contraseña -->
|
<div class="password-recovery">
|
||||||
<div class="password-recovery px-3 pb-3">
|
<a href="#">Recuperar Contraseña</a>
|
||||||
<a href="#" (click)="toggleRecovery($event)">Recuperar Contraseña</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Credenciales de prueba -->
|
|
||||||
<div class="test-credentials mx-3 mb-3 p-2 border-round bg-gray-100">
|
|
||||||
<p class="mb-1 font-bold">Credenciales de prueba:</p>
|
|
||||||
<p class="mb-1">Email: admin@example.com</p>
|
|
||||||
<p>Password: admin123</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- PANEL DE RECUPERACIÓN -->
|
|
||||||
<div class="panel-container w-full"
|
|
||||||
[ngClass]="{'animate__animated animate__fadeIn': showRecovery,
|
|
||||||
'animate__animated animate__fadeOut d-none': !showRecovery && !isInitialLoad}">
|
|
||||||
<div class="login-card shadow-2 border-round">
|
|
||||||
<div class="login-header">
|
|
||||||
<h2>Recuperar Contraseña</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form (ngSubmit)="onRecoverPassword()" class="p-3">
|
|
||||||
<!-- Email para recuperación -->
|
|
||||||
<div class="field mb-3">
|
|
||||||
<input type="email" pInputText [(ngModel)]="recoveryEmail" name="recoveryEmail"
|
|
||||||
placeholder="Ingresa tu email" class="input-with-icon w-full" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mensaje informativo -->
|
|
||||||
<div class="info-message mb-3">
|
|
||||||
<p class="text-sm">Te enviaremos un enlace para restablecer tu contraseña.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mensaje de estado de recuperación -->
|
|
||||||
<div *ngIf="recoveryMessage" class="recovery-message my-2">
|
|
||||||
<p-message [severity]="recoveryStatus" [text]="recoveryMessage"></p-message>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Botón de enviar -->
|
|
||||||
<div class="login-actions">
|
|
||||||
<button pButton type="submit" [label]="recoveryLoading ? 'Enviando...' : 'Enviar Enlace'"
|
|
||||||
class="p-button-primary w-full" [disabled]="recoveryLoading || !recoveryEmail">
|
|
||||||
<i *ngIf="recoveryLoading" class="pi pi-spin pi-spinner mr-2"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Volver al login -->
|
|
||||||
<div class="password-recovery px-3 pb-3">
|
|
||||||
<a href="#" (click)="toggleRecovery($event)">Volver al Login</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -118,4 +51,21 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- FOOTER -->
|
<!-- FOOTER -->
|
||||||
<app-footer></app-footer>
|
<footer class="footer">
|
||||||
|
<div class="footer-content">
|
||||||
|
<div class="footer-left">
|
||||||
|
<i class="pi pi-building"></i>
|
||||||
|
<div class="footer-text">
|
||||||
|
<div>Superintendencia de Servicios Sanitarios</div>
|
||||||
|
<div>Área de Información y Tecnologías</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-center">
|
||||||
|
<img src="assets/img/siss-logo-white.png" alt="SISS Logo" class="footer-logo">
|
||||||
|
</div>
|
||||||
|
<div class="footer-right">
|
||||||
|
<div>Dirección: Moneda 673 Piso 9 - Metro Santa Lucía</div>
|
||||||
|
<div>Mesa Central: 2 2382 4000</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
@ -5,82 +5,67 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Header Styles */
|
/* Header Styles */
|
||||||
.header-container {
|
.header {
|
||||||
position: relative;
|
background-color: #d3e9f7;
|
||||||
width: 100%;
|
padding: 1rem 3rem;
|
||||||
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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.water-drop {
|
.header-content {
|
||||||
position: absolute;
|
display: flex;
|
||||||
width: 41px;
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.siss-logo {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-drop {
|
.sacg-container {
|
||||||
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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
color: #002147;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-title {
|
.sacg-title {
|
||||||
font-size: 3.0rem;
|
display: flex;
|
||||||
margin-bottom: 0px;
|
align-items: center;
|
||||||
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
|
font-size: 2.5rem;
|
||||||
transform: translate(-4%);
|
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 Styles */
|
||||||
.main-content {
|
.main-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 60vh; /* Aumentado para mayor espacio vertical */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content::before {
|
.main-content::before {
|
||||||
@ -96,12 +81,11 @@
|
|||||||
.login-container {
|
.login-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
width: 100%;
|
|
||||||
max-width: 360px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-box {
|
.login-box {
|
||||||
width: 100%;
|
width: 360px;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card {
|
.login-card {
|
||||||
@ -109,34 +93,6 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
overflow: hidden;
|
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 {
|
.login-header {
|
||||||
@ -148,13 +104,35 @@
|
|||||||
.login-header h2 {
|
.login-header h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: #0088cc;
|
font-weight: normal;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card form {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-inputgroup-addon {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-color: #ced4da;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-actions {
|
.login-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
margin-top: 1rem;
|
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 {
|
.password-recovery a {
|
||||||
color: #0088cc;
|
color: #0088cc;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -165,26 +143,58 @@
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilos para los inputs con iconos */
|
/* Footer Styles */
|
||||||
.input-with-icon {
|
.footer {
|
||||||
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-color: #0088cc;
|
||||||
background-repeat: no-repeat;
|
color: white;
|
||||||
background-position: right 10px center;
|
padding: 1rem 2rem;
|
||||||
background-size: 20px 20px;
|
|
||||||
padding-right: 35px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-with-lock {
|
.footer-content {
|
||||||
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");
|
display: flex;
|
||||||
background-repeat: no-repeat;
|
justify-content: space-between;
|
||||||
background-position: right 10px center;
|
align-items: center;
|
||||||
background-size: 20px 20px;
|
max-width: 1200px;
|
||||||
padding-right: 35px;
|
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 */
|
/* Responsive styles */
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.login-container {
|
.header-content {
|
||||||
padding: 0 1rem;
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-content {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-right {
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,18 +1,13 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { CardModule } from 'primeng/card';
|
import { CardModule } from 'primeng/card';
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
import { ButtonModule } from 'primeng/button';
|
import { ButtonModule } from 'primeng/button';
|
||||||
import { PasswordModule } from 'primeng/password';
|
import { PasswordModule } from 'primeng/password';
|
||||||
import { DividerModule } from 'primeng/divider';
|
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({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
@ -23,114 +18,20 @@ import { AuthService } from '../../services/auth.service';
|
|||||||
InputTextModule,
|
InputTextModule,
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
PasswordModule,
|
PasswordModule,
|
||||||
DividerModule,
|
DividerModule
|
||||||
MessagesModule,
|
|
||||||
MessageModule,
|
|
||||||
ToastModule,
|
|
||||||
FooterComponent
|
|
||||||
],
|
],
|
||||||
providers: [MessageService],
|
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrl: './login.component.scss'
|
styleUrl: './login.component.scss'
|
||||||
})
|
})
|
||||||
export class LoginComponent implements OnInit {
|
export class LoginComponent {
|
||||||
// Login form
|
|
||||||
email: string = '';
|
email: string = '';
|
||||||
password: string = '';
|
password: string = '';
|
||||||
loading: boolean = false;
|
|
||||||
errorMessage: string = '';
|
|
||||||
|
|
||||||
// Password recovery form
|
constructor(private router: Router) { }
|
||||||
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() {
|
onLogin() {
|
||||||
this.loading = true;
|
// Aquí iría la lógica de autenticación
|
||||||
this.errorMessage = '';
|
// Por ahora, solo navegamos a la página de inicio
|
||||||
|
|
||||||
// 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']);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
<!-- error-404.component.html -->
|
|
||||||
<div class="error-container">
|
|
||||||
<div class="error-content">
|
|
||||||
<h1 class="error-title">404</h1>
|
|
||||||
<p class="error-message">Página no encontrada</p>
|
|
||||||
<button class="error-button" (click)="volverAlInicio()">Volver al inicio</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { NotFoundComponent } from './not-found.component';
|
|
||||||
|
|
||||||
describe('NotFoundComponent', () => {
|
|
||||||
let component: NotFoundComponent;
|
|
||||||
let fixture: ComponentFixture<NotFoundComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [NotFoundComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(NotFoundComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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(['/']);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
<div class="p-3 text-white">
|
|
||||||
<!-- Tabla1 -->
|
|
||||||
<div class="font-bold tituloTabla my-2">Cronogramas aprobados:</div>
|
|
||||||
<p-table
|
|
||||||
id="verde"
|
|
||||||
[value]="products"
|
|
||||||
stripedRows
|
|
||||||
showGridlines
|
|
||||||
[paginator]="true"
|
|
||||||
[rows]="5"
|
|
||||||
[showCurrentPageReport]="true"
|
|
||||||
currentPageReportTemplate="Mostrando del {first} al {last} de un total de {totalRecords} registros"
|
|
||||||
[rowsPerPageOptions]="[5, 10, 20]"
|
|
||||||
>
|
|
||||||
<ng-template #header>
|
|
||||||
<tr>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Empresa</th>
|
|
||||||
<th class="bg-green-400 text-white font-bold">
|
|
||||||
Código de cronograma
|
|
||||||
</th>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Nombre sistema</th>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Tipo</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #body let-product>
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.empresa }}</td>
|
|
||||||
<td>{{ product.codigoCronograma }}</td>
|
|
||||||
<td>{{ product.codigoCronogramaAjuste }}</td>
|
|
||||||
<td>{{ product.tipoCarga }}</td>
|
|
||||||
<td style="background-color: #f8f9fa; border: none; width: 50px">
|
|
||||||
<button
|
|
||||||
pTooltip="Firma digital"
|
|
||||||
tooltipPosition="left"
|
|
||||||
(click)="firma(product)"
|
|
||||||
showDelay="300"
|
|
||||||
style="background: transparent; border: none; cursor: pointer"
|
|
||||||
>
|
|
||||||
<i class="pi pi-pen-to-square" style="font-size: 24px"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
|
|
||||||
<div class="font-bold tituloTabla my-2">Cronogramas rechazados:</div>
|
|
||||||
<p-table
|
|
||||||
id="ploma"
|
|
||||||
[value]="products"
|
|
||||||
stripedRows
|
|
||||||
showGridlines
|
|
||||||
[paginator]="true"
|
|
||||||
[rows]="5"
|
|
||||||
[showCurrentPageReport]="true"
|
|
||||||
currentPageReportTemplate="Mostrando del {first} al {last} de un total de {totalRecords} registros"
|
|
||||||
[rowsPerPageOptions]="[5, 10, 20]"
|
|
||||||
>
|
|
||||||
<ng-template #header>
|
|
||||||
<tr>
|
|
||||||
<th class="tablaPloma text-white font-bold">Empresa</th>
|
|
||||||
<th class="tablaPloma text-white font-bold">
|
|
||||||
Código de cronograma
|
|
||||||
</th>
|
|
||||||
<th class="tablaPloma text-white font-bold">Nombre sistema</th>
|
|
||||||
<th class="tablaPloma text-white font-bold">Tipo</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #body let-product>
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.empresa }}</td>
|
|
||||||
<td>{{ product.codigoCronograma }}</td>
|
|
||||||
<td>{{ product.codigoCronogramaAjuste }}</td>
|
|
||||||
<td>{{ product.tipoCarga }}</td>
|
|
||||||
<td style="background-color: #f8f9fa; border: none; width: 50px">
|
|
||||||
<button
|
|
||||||
pTooltip="Notificar a unidad de información o empresa"
|
|
||||||
tooltipPosition="left"
|
|
||||||
showDelay="300"
|
|
||||||
style="background: transparent; border: none; cursor: pointer"
|
|
||||||
>
|
|
||||||
<i class="pi pi-arrow-circle-right" style="font-size: 24px"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
</div>
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ResumenComponent } from './resumen.component';
|
|
||||||
|
|
||||||
describe('ResumenComponent', () => {
|
|
||||||
let component: ResumenComponent;
|
|
||||||
let fixture: ComponentFixture<ResumenComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [ResumenComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ResumenComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
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'
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
<div class="p-3 text-white">
|
|
||||||
<!-- Tabla1 -->
|
|
||||||
<div class="font-bold tituloTabla my-2">Cronogramas solicitados y pendientes de carga:</div>
|
|
||||||
<p-table id="azul"
|
|
||||||
[value]="products" stripedRows
|
|
||||||
showGridlines
|
|
||||||
[paginator]="true"
|
|
||||||
[rows]="5"
|
|
||||||
[showCurrentPageReport]="true"
|
|
||||||
currentPageReportTemplate="Mostrando del {first} al {last} de un total de {totalRecords} registros"
|
|
||||||
[rowsPerPageOptions]="[5, 10, 20]">
|
|
||||||
<ng-template #header>
|
|
||||||
<tr>
|
|
||||||
<th class="tablaAzul text-white font-bold">Empresa</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Código de cronograma SINAR</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Contiene obras del año</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">N° Oficio que aprueba</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Tipo de carga</th>
|
|
||||||
<th class="tablaAzul text-white font-bold">Fecha de solicitud</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #body let-product>
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.empresa }}</td>
|
|
||||||
<td>{{ product.codigoCronograma }}</td>
|
|
||||||
<td>{{ product.codigoCronogramaAjuste }}</td>
|
|
||||||
<td>{{ product.tipoCarga }}</td>
|
|
||||||
<td>{{ product.dato5 }}</td>
|
|
||||||
<td>{{ product.dato6 }}</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
<!-- Tabla2 -->
|
|
||||||
<div class="font-bold tituloTabla my-2">Cronogramas aprobados:</div>
|
|
||||||
<p-table id="verde"
|
|
||||||
[value]="products" stripedRows
|
|
||||||
showGridlines
|
|
||||||
[paginator]="true"
|
|
||||||
[rows]="5"
|
|
||||||
[showCurrentPageReport]="true"
|
|
||||||
currentPageReportTemplate="Mostrando del {first} al {last} de un total de {totalRecords} registros"
|
|
||||||
[rowsPerPageOptions]="[5, 10, 20]">
|
|
||||||
<ng-template #header>
|
|
||||||
<tr>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Empresa</th>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Código cronograma</th>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Nombre sistema</th>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Tipo</th>
|
|
||||||
<th class="bg-green-400 text-white font-bold">Fecha aprobación</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #body let-product>
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.empresa }}</td>
|
|
||||||
<td>{{ product.codigoCronograma }}</td>
|
|
||||||
<td>{{ product.codigoCronogramaAjuste }}</td>
|
|
||||||
<td>{{ product.tipoCarga }}</td>
|
|
||||||
<td>{{ product.dato5 }}</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
<!-- Tabla3 -->
|
|
||||||
<div class="font-bold tituloTabla my-2">Cronogramas rechazados:</div>
|
|
||||||
<p-table id="ploma"
|
|
||||||
[value]="products" stripedRows
|
|
||||||
showGridlines
|
|
||||||
[paginator]="true"
|
|
||||||
[rows]="5"
|
|
||||||
[showCurrentPageReport]="true"
|
|
||||||
currentPageReportTemplate="Mostrando del {first} al {last} de un total de {totalRecords} registros"
|
|
||||||
[rowsPerPageOptions]="[5, 10, 20]">
|
|
||||||
<ng-template #header>
|
|
||||||
<tr>
|
|
||||||
<th class="tablaPloma text-white font-bold">Empresa</th>
|
|
||||||
<th class="tablaPloma text-white font-bold">Código de cronograma</th>
|
|
||||||
<th class="tablaPloma text-white font-bold">Nombre sistema</th>
|
|
||||||
<th class="tablaPloma text-white font-bold">Tipo</th>
|
|
||||||
<th class="tablaPloma text-white font-bold">Fecha rechazo</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #body let-product>
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.empresa }}</td>
|
|
||||||
<td>{{ product.codigoCronograma }}</td>
|
|
||||||
<td>{{ product.codigoCronogramaAjuste }}</td>
|
|
||||||
<td>{{ product.tipoCarga }}</td>
|
|
||||||
<td>{{ product.dato5 }}</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
</div>
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { UnidadInformacionComponent } from './unidad-informacion.component';
|
|
||||||
|
|
||||||
describe('UnidadInformacionComponent', () => {
|
|
||||||
let component: UnidadInformacionComponent;
|
|
||||||
let fixture: ComponentFixture<UnidadInformacionComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [UnidadInformacionComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(UnidadInformacionComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
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',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
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<ActualizacionPd[]> {
|
|
||||||
return this.http.get<ActualizacionPd[]>(this.apiUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
getActualizacionById(id: number): Observable<ActualizacionPd> {
|
|
||||||
return this.http.get<ActualizacionPd>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
createActualizacion(actualizacion: ActualizacionPd): Observable<ActualizacionPd> {
|
|
||||||
return this.http.post<ActualizacionPd>(this.apiUrl, actualizacion);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateActualizacion(actualizacion: ActualizacionPd): Observable<ActualizacionPd> {
|
|
||||||
return this.http.put<ActualizacionPd>(`${this.apiUrl}/${actualizacion.id}`, actualizacion);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteActualizacion(id: number): Observable<void> {
|
|
||||||
return this.http.delete<void>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
aprobarActualizacion(id: number): Observable<ActualizacionPd> {
|
|
||||||
return this.http.patch<ActualizacionPd>(`${this.apiUrl}/${id}/aprobar`, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
rechazarActualizacion(id: number, motivo: string): Observable<ActualizacionPd> {
|
|
||||||
return this.http.patch<ActualizacionPd>(`${this.apiUrl}/${id}/rechazar`, { motivo });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
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<AjustePd[]> {
|
|
||||||
return this.http.get<AjustePd[]>(this.apiUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAjusteById(id: number): Observable<AjustePd> {
|
|
||||||
return this.http.get<AjustePd>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
createAjuste(ajuste: AjustePd): Observable<AjustePd> {
|
|
||||||
return this.http.post<AjustePd>(this.apiUrl, ajuste);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAjuste(ajuste: AjustePd): Observable<AjustePd> {
|
|
||||||
return this.http.put<AjustePd>(`${this.apiUrl}/${ajuste.id}`, ajuste);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteAjuste(id: number): Observable<void> {
|
|
||||||
return this.http.delete<void>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
aprobarAjuste(id: number): Observable<AjustePd> {
|
|
||||||
return this.http.patch<AjustePd>(`${this.apiUrl}/${id}/aprobar`, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
rechazarAjuste(id: number, motivo: string): Observable<AjustePd> {
|
|
||||||
return this.http.patch<AjustePd>(`${this.apiUrl}/${id}/rechazar`, { motivo });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
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<AlertDialogComponent> | 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<AlertDialogOptions> = {}): Promise<boolean> {
|
|
||||||
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<boolean> {
|
|
||||||
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<boolean> {
|
|
||||||
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<boolean> {
|
|
||||||
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<boolean> {
|
|
||||||
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<boolean> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
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<any>(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<AuthResponse> {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
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<Cronograma[]> {
|
|
||||||
return this.http.get<Cronograma[]>(this.apiUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCronogramaById(id: number): Observable<Cronograma> {
|
|
||||||
return this.http.get<Cronograma>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
createCronograma(cronograma: Cronograma): Observable<Cronograma> {
|
|
||||||
return this.http.post<Cronograma>(this.apiUrl, cronograma);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCronograma(cronograma: Cronograma): Observable<Cronograma> {
|
|
||||||
return this.http.put<Cronograma>(`${this.apiUrl}/${cronograma.id}`, cronograma);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteCronograma(id: number): Observable<void> {
|
|
||||||
return this.http.delete<void>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
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<Empresa[]> {
|
|
||||||
return this.http.get<Empresa[]>(this.apiUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
getEmpresaById(id: number): Observable<Empresa> {
|
|
||||||
return this.http.get<Empresa>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
createEmpresa(empresa: Empresa): Observable<Empresa> {
|
|
||||||
return this.http.post<Empresa>(this.apiUrl, empresa);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateEmpresa(empresa: Empresa): Observable<Empresa> {
|
|
||||||
return this.http.put<Empresa>(`${this.apiUrl}/${empresa.id}`, empresa);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteEmpresa(id: number): Observable<void> {
|
|
||||||
return this.http.delete<void>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
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<EstadoAprobacion[]> {
|
|
||||||
return this.http.get<EstadoAprobacion[]>(this.apiUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
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';
|
|
||||||
@ -1,214 +0,0 @@
|
|||||||
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<string> {
|
|
||||||
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<string> {
|
|
||||||
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]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
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<TipoCarga[]> {
|
|
||||||
return this.http.get<TipoCarga[]>(this.apiUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
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<UnidadInformacion[]> {
|
|
||||||
return this.http.get<UnidadInformacion[]>(this.apiUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
getUnidadById(id: number): Observable<UnidadInformacion> {
|
|
||||||
return this.http.get<UnidadInformacion>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
createUnidad(unidad: UnidadInformacion): Observable<UnidadInformacion> {
|
|
||||||
return this.http.post<UnidadInformacion>(this.apiUrl, unidad);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUnidad(unidad: UnidadInformacion): Observable<UnidadInformacion> {
|
|
||||||
return this.http.put<UnidadInformacion>(`${this.apiUrl}/${unidad.id}`, unidad);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteUnidad(id: number): Observable<void> {
|
|
||||||
return this.http.delete<void>(`${this.apiUrl}/${id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,13 +2,10 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Cronogramas</title>
|
<title>CronogramasPrimeng</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<link rel="preload" as="image" href="img/header2.webp" type="image/webp">
|
|
||||||
<link rel="preload" as="image" href="img/footer-logo.webp" type="image/webp">
|
|
||||||
<link rel="preload" as="image" href="img/gota.png" type="image/png">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
151
src/styles.scss
151
src/styles.scss
@ -1,136 +1,69 @@
|
|||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #0088cc; /* Azul principal SISS */
|
--primary-color: #0088cc; // Color principal azul SISS
|
||||||
--primary-light: #bcdaef; /* Azul claro para fondos */
|
--secondary-color: #0a2847; // Color azul oscuro
|
||||||
--text-color: #0a2847; /* Color texto principal */
|
--light-blue: #d3e9f7; // Color para el header
|
||||||
--secondary-text: #6c757d; /* Texto secundario */
|
--danger-color: #dc3545; // Color rojo para los indicadores en inputs
|
||||||
--border-color: #dee2e6; /* Color bordes */
|
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
--background-color: #f8f9fa; /* Fondo gris claro */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilos globales */
|
// Estilos generales
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
/* font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; */
|
font-family: var(--font-family);
|
||||||
/* 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 {
|
body {
|
||||||
background-color: var(--background-color);
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clases de utilidad */
|
// Estilos específicos para inputs
|
||||||
.mb-1 { margin-bottom: 0.25rem !important; }
|
.p-inputtext {
|
||||||
.mb-2 { margin-bottom: 0.5rem !important; }
|
padding: 0.75rem 0.75rem;
|
||||||
.mb-3 { margin-bottom: 1rem !important; }
|
font-size: 1rem;
|
||||||
.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 */
|
// Sobreescritura de estilos de PrimeNG
|
||||||
/* .p-component {
|
.p-button {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
padding: 0.75rem 1.25rem;
|
||||||
} */
|
border-radius: 4px;
|
||||||
|
|
||||||
.p-button.p-button-text {
|
&.p-button-primary {
|
||||||
&:focus {
|
background-color: var(--primary-color);
|
||||||
box-shadow: none;
|
border-color: var(--primary-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: darken(#0088cc, 10%);
|
||||||
|
border-color: darken(#0088cc, 10%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tituloTabla {
|
// Clase para fondos rojos en los addons de input
|
||||||
color: #002147 !important;
|
.bg-red-600 {
|
||||||
}
|
background-color: var(--danger-color) !important;
|
||||||
|
border-color: var(--danger-color) !important;
|
||||||
.tablaAzul{
|
|
||||||
background-color: #008BC6 !important;
|
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#azul .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(odd) {
|
// Sobreescribe el estilo de los iconos en los input groups
|
||||||
--p-datatable-row-striped-background: #EAF3FB;
|
.p-inputgroup-addon {
|
||||||
background: var(--p-datatable-row-striped-background);
|
.pi {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#azul .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(even) {
|
// Ajustes para espaciado consistente
|
||||||
--p-datatable-row-striped-background: #BCDAEF;
|
.mb-3 {
|
||||||
background: var(--p-datatable-row-striped-background);
|
margin-bottom: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tablaVerde{
|
.mb-4 {
|
||||||
background-color: #008BC6 !important;
|
margin-bottom: 1.5rem !important;
|
||||||
color: white !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#verde .p-datatable.p-datatable-striped .p-datatable-tbody > tr:nth-child(odd) {
|
// Ajustes para el ancho completo
|
||||||
--p-datatable-row-striped-background: #F5F9F5;
|
.w-full {
|
||||||
background: var(--p-datatable-row-striped-background);
|
width: 100% !important;
|
||||||
}
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist/out-tsc",
|
"outDir": "./dist/out-tsc",
|
||||||
"strict": false,
|
"strict": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
"noPropertyAccessFromIndexSignature": true,
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user