utn-cli 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/frontend.js +1 -1
- package/package.json +1 -1
- package/templates/backend/rutas/misc.js +18 -0
- package/templates/backend/servicios/Nucleo/Miscelaneas.js +12 -2
- package/templates/backend/servicios/Nucleo/ReportePDF.js +1 -1
- package/templates/backend/servicios/Servicio1.js +110 -5
- package/templates/bd/docker-scripts/1-crear estructura.sql +8 -0
- package/templates/frontend/src/app/Componentes/Nucleo/manual/manual.component.ts +4 -1
- package/templates/frontend/src/app/Paginas/Nucleo/contenedor-componentes/contenedor-componentes.component.css +72 -0
- package/templates/frontend/src/app/Paginas/Nucleo/contenedor-componentes/contenedor-componentes.component.html +17 -2
- package/templates/frontend/src/app/Paginas/Nucleo/contenedor-componentes/contenedor-componentes.component.ts +11 -0
- package/templates/frontend/src/app/Paginas/contenedor-principal/contenedor-principal.component.css +11 -0
- package/templates/frontend/src/app/Paginas/contenedor-principal/contenedor-principal.component.html +1 -1
- package/templates/frontend/src/app/Paginas/contenedor-principal/contenedor-principal.component.ts +20 -2
- package/templates/frontend/src/app/datos-globales.service.ts +4 -1
package/commands/frontend.js
CHANGED
|
@@ -42,7 +42,7 @@ export async function initFrontend() {
|
|
|
42
42
|
|
|
43
43
|
export async function updateFrontend(opciones = { cerrarAlFinalizar: true }) {
|
|
44
44
|
console.log('Actualizando el proyecto de frontend...');
|
|
45
|
-
const archivosAExcluir = ['app.routes.ts', 'contenedor-principal.component.ts', '.vscode', 'dist'];
|
|
45
|
+
const archivosAExcluir = ['app.routes.ts', 'contenedor-principal.component.ts', '.vscode', 'dist', 'Manual.md'];
|
|
46
46
|
const directoriodePlantillas = path.join(__dirname, '../templates/frontend');
|
|
47
47
|
const directorioDestino = process.cwd();
|
|
48
48
|
|
package/package.json
CHANGED
|
@@ -209,6 +209,24 @@ Router.get('/obtenerDetalleDelModulo', async (solicitud, respuesta, next) => {
|
|
|
209
209
|
}
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
+
Router.get('/VistaDelManual', async (solicitud, respuesta, next) => {
|
|
213
|
+
try {
|
|
214
|
+
if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
|
|
215
|
+
try {
|
|
216
|
+
await Miscelaneo.registrarVistaDelManual(solicitud.headers.authorization);
|
|
217
|
+
return respuesta.json({ body: undefined, error: undefined });
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const MensajeDeError = 'No fue posible registrar la vista del manual';
|
|
220
|
+
console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, `Dirección IP: ${solicitud.ip}`));
|
|
221
|
+
return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
|
|
225
|
+
} catch (error) {
|
|
226
|
+
next(error);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
212
230
|
Router.get('/obtenerDatosDelUsuario', async (solicitud, respuesta, next) => {
|
|
213
231
|
try {
|
|
214
232
|
if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
|
|
@@ -880,6 +880,14 @@ class Miscelaneo {
|
|
|
880
880
|
});
|
|
881
881
|
}
|
|
882
882
|
|
|
883
|
+
async registrarVistaDelManual(Token) {
|
|
884
|
+
const { uid } = await this.obtenerDatosDelUsuario(Token);
|
|
885
|
+
await ejecutarConsulta(
|
|
886
|
+
"INSERT INTO `DatosMiscelaneos` (`DatoMiscelaneo`, `Datos`, `LastUser`) VALUES ('VistasDelManual', JSON_OBJECT('Total', 1), ?) ON DUPLICATE KEY UPDATE `Datos` = JSON_SET(`Datos`, '$.Total', CAST(JSON_VALUE(`Datos`, '$.Total') AS UNSIGNED) + 1), `LastUser` = ?",
|
|
887
|
+
[uid, uid]
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
|
|
883
891
|
async obtenerMensajesModulares() {
|
|
884
892
|
return await ejecutarConsultaSIGU("SELECT `MensajeModularId`, `Titulo`, `Texto`, `FechaYHoraDeInicio`, `FechaYHoraDeFinalizacion` FROM `SIGU`.`SIGU_MensajesModulares` WHERE NOW(4) BETWEEN `FechaYHoraDeInicio` AND `FechaYHoraDeFinalizacion` AND `Modulo` = ?"
|
|
885
893
|
, [this.NombreCanonicoDelModulo]);
|
|
@@ -1920,10 +1928,10 @@ class Miscelaneo {
|
|
|
1920
1928
|
const informacionDelArchivo = await this.almacenarArchivoEnDisco(Solicitud, Resultado['uid']);
|
|
1921
1929
|
const Respuesta = await ejecutarConsultaSIGU("INSERT INTO `SIGU`.`SIGU_Adjuntos` (`AdjuntosId`, `Identificador`, `Modulo`, `Seccion`, `Nombre`,\
|
|
1922
1930
|
`NombreOriginal`, `Ruta`, `Tipo`, `Tamanio`, `Etiqueta`, `LastUpdate`, `LastUser`)\
|
|
1923
|
-
VALUES (NULL, ?, ?, 'No aplica', ?, ?, ?, ?, ?,
|
|
1931
|
+
VALUES (NULL, ?, ?, 'No aplica', ?, ?, ?, ?, ?, ?, NOW(4), ?)"
|
|
1924
1932
|
, [Resultado['uid'], this.NombreCanonicoDelModulo, informacionDelArchivo.nombreDeArchivo, informacionDelArchivo.nombreDeArchivo
|
|
1925
1933
|
, informacionDelArchivo.rutaDeArchivo, informacionDelArchivo.tipoDeContenido, informacionDelArchivo.tamanioTotal
|
|
1926
|
-
, Resultado['uid']]);
|
|
1934
|
+
, Etiquetas, Resultado['uid']]);
|
|
1927
1935
|
informacionDelArchivo.insertId = Respuesta.insertId;
|
|
1928
1936
|
informacionDelArchivo.Etiquetas = Etiquetas;
|
|
1929
1937
|
await ejecutarConsulta("INSERT INTO `" + this.NombreDelRepositorioDeLaBaseDeDatos.slice(0, -3) + "`.`Archivos`\
|
|
@@ -2508,6 +2516,8 @@ class Miscelaneo {
|
|
|
2508
2516
|
return {
|
|
2509
2517
|
TienePermiso: true,
|
|
2510
2518
|
...configuracion,
|
|
2519
|
+
Descripcion: detalleDelModulo[0]?.Descripcion ?? configuracion.Descripcion,
|
|
2520
|
+
Detalle: detalleDelModulo[0]?.Detalle ?? configuracion.Detalle,
|
|
2511
2521
|
EnlaceDelManual: detalleDelModulo[0]?.EnlaceDelManual ?? '-',
|
|
2512
2522
|
EnlaceDelVideo: detalleDelModulo[0]?.EnlaceDelVideo ?? '-',
|
|
2513
2523
|
Notificaciones: notificaciones,
|
|
@@ -661,7 +661,7 @@ class ReportePDF {
|
|
|
661
661
|
|
|
662
662
|
// Función auxiliar para aplicar el formato y escribir el buffer
|
|
663
663
|
const flushText = (token, isLast) => {
|
|
664
|
-
if (token
|
|
664
|
+
if (!token) return;
|
|
665
665
|
|
|
666
666
|
let fontName = this.config.fuenteBase;
|
|
667
667
|
if (isBold && isItalic) fontName = this.config.fuenteNegritaCursiva;
|
|
@@ -9,6 +9,111 @@ class Servicio1 {
|
|
|
9
9
|
|
|
10
10
|
RegistrarElServicio() {
|
|
11
11
|
const NombreDelServicio = 'Servicio1';
|
|
12
|
+
|
|
13
|
+
// El segundo parámetro de RegistrarElServicio define la tarjeta (o tarjetas) que
|
|
14
|
+
// aparecerán en el dashboard del módulo. Puede ser un objeto o un arreglo de objetos.
|
|
15
|
+
// Si se omite, el servicio se registra sin crear ninguna tarjeta.
|
|
16
|
+
//
|
|
17
|
+
// Campos comunes a todos los tipos:
|
|
18
|
+
// 'Tipo' — 'Única' | 'Múltiple' | 'Reporte' | 'Personalizada'
|
|
19
|
+
// 'Posición' — Número entero; menor = aparece antes. Use múltiplos de 10.
|
|
20
|
+
// 'Título' — Texto visible en la tarjeta del dashboard.
|
|
21
|
+
// 'Descripción' — Texto secundario de la tarjeta.
|
|
22
|
+
// 'RequierePermisoExtra' — true: solo visible para usuarios con permiso extra.
|
|
23
|
+
// 'Activa' — false: la tarjeta existe pero no se muestra.
|
|
24
|
+
//
|
|
25
|
+
// ── Tipo 'Única' ────────────────────────────────────────────────────────────────
|
|
26
|
+
// Tarjeta de acceso directo a una sola ruta.
|
|
27
|
+
//
|
|
28
|
+
// Miscelaneas.RegistrarElServicio(NombreDelServicio, {
|
|
29
|
+
// 'Tipo': 'Única',
|
|
30
|
+
// 'Posición': 10,
|
|
31
|
+
// 'Título': 'Mi sección',
|
|
32
|
+
// 'Descripción': 'Descripción breve de la sección',
|
|
33
|
+
// 'Ícono': 'settings', // Nombre de ícono de Material Icons
|
|
34
|
+
// 'RutaASeguir': 'miRuta', // Ruta relativa sin barra inicial
|
|
35
|
+
// 'ColorDeBorde': '#3498db', // Opcional; omitir para color predeterminado
|
|
36
|
+
// 'RequierePermisoExtra': false,
|
|
37
|
+
// 'Activa': true
|
|
38
|
+
// });
|
|
39
|
+
//
|
|
40
|
+
// ── Tipo 'Múltiple' ──────────────────────────────────────────────────────────────
|
|
41
|
+
// Tarjeta de menú desplegable con varias opciones de navegación.
|
|
42
|
+
//
|
|
43
|
+
// Miscelaneas.RegistrarElServicio(NombreDelServicio, {
|
|
44
|
+
// 'Tipo': 'Múltiple',
|
|
45
|
+
// 'Posición': 20,
|
|
46
|
+
// 'Título': 'Mi sección',
|
|
47
|
+
// 'Descripción': 'Descripción breve de la sección',
|
|
48
|
+
// 'Rutas': [
|
|
49
|
+
// { 'Nombre': 'Opción A', 'Ruta': 'tablaOpcionA' },
|
|
50
|
+
// { 'Nombre': 'Opción B', 'Ruta': 'tablaOpcionB' },
|
|
51
|
+
// { 'Nombre': 'Opción C', 'Ruta': 'tablaOpcionC' }
|
|
52
|
+
// ],
|
|
53
|
+
// 'RequierePermisoExtra': false,
|
|
54
|
+
// 'Activa': true
|
|
55
|
+
// });
|
|
56
|
+
//
|
|
57
|
+
// ── Tipo 'Reporte' ───────────────────────────────────────────────────────────────
|
|
58
|
+
// Tarjeta que, al hacer clic, genera y descarga un reporte.
|
|
59
|
+
// El valor de 'ReporteAGenerar' debe coincidir con el identificador manejado
|
|
60
|
+
// en el método GenerarReporte() del componente ContenedorPrincipalComponent.
|
|
61
|
+
//
|
|
62
|
+
// Miscelaneas.RegistrarElServicio(NombreDelServicio, {
|
|
63
|
+
// 'Tipo': 'Reporte',
|
|
64
|
+
// 'Posición': 30,
|
|
65
|
+
// 'Título': 'Mi reporte',
|
|
66
|
+
// 'Descripción': 'Descarga el reporte de ejemplo en formato CSV',
|
|
67
|
+
// 'Ícono': 'download',
|
|
68
|
+
// 'ReporteAGenerar': 'ElReporte1', // Identificador del reporte en el frontend
|
|
69
|
+
// 'RequierePermisoExtra': false,
|
|
70
|
+
// 'Activa': true
|
|
71
|
+
// });
|
|
72
|
+
//
|
|
73
|
+
// ── Tipo 'Personalizada' ─────────────────────────────────────────────────────────
|
|
74
|
+
// Tarjeta que ejecuta una acción programada definida en el frontend.
|
|
75
|
+
// El valor de 'Acción' debe coincidir con el identificador manejado
|
|
76
|
+
// en el método EjecutarAccionPersonalizada() del componente ContenedorPrincipalComponent.
|
|
77
|
+
//
|
|
78
|
+
// Miscelaneas.RegistrarElServicio(NombreDelServicio, {
|
|
79
|
+
// 'Tipo': 'Personalizada',
|
|
80
|
+
// 'Posición': 40,
|
|
81
|
+
// 'Título': 'Mi acción',
|
|
82
|
+
// 'Descripción': 'Ejecuta una acción personalizada',
|
|
83
|
+
// 'Ícono': 'bolt',
|
|
84
|
+
// 'Etiqueta': 'Ejecutar', // Texto del botón dentro de la tarjeta
|
|
85
|
+
// 'Acción': 'MiAccionPersonalizada', // Identificador de la acción en el frontend
|
|
86
|
+
// 'RequierePermisoExtra': false,
|
|
87
|
+
// 'Activa': true
|
|
88
|
+
// });
|
|
89
|
+
//
|
|
90
|
+
// ── Múltiples tarjetas a la vez ──────────────────────────────────────────────────
|
|
91
|
+
// El segundo parámetro también acepta un arreglo para registrar varias tarjetas
|
|
92
|
+
// en una sola llamada (útil cuando un servicio expone más de una sección).
|
|
93
|
+
//
|
|
94
|
+
// Miscelaneas.RegistrarElServicio(NombreDelServicio, [
|
|
95
|
+
// {
|
|
96
|
+
// 'Tipo': 'Única',
|
|
97
|
+
// 'Posición': 10,
|
|
98
|
+
// 'Título': 'Sección A',
|
|
99
|
+
// 'Descripción': 'Descripción de la sección A',
|
|
100
|
+
// 'Ícono': 'dashboard',
|
|
101
|
+
// 'RutaASeguir': 'tablaSeccionA',
|
|
102
|
+
// 'RequierePermisoExtra': false,
|
|
103
|
+
// 'Activa': true
|
|
104
|
+
// },
|
|
105
|
+
// {
|
|
106
|
+
// 'Tipo': 'Única',
|
|
107
|
+
// 'Posición': 20,
|
|
108
|
+
// 'Título': 'Sección B',
|
|
109
|
+
// 'Descripción': 'Descripción de la sección B',
|
|
110
|
+
// 'Ícono': 'bar_chart',
|
|
111
|
+
// 'RutaASeguir': 'tablaSeccionB',
|
|
112
|
+
// 'RequierePermisoExtra': false,
|
|
113
|
+
// 'Activa': true
|
|
114
|
+
// }
|
|
115
|
+
// ]);
|
|
116
|
+
|
|
12
117
|
Miscelaneas.RegistrarElServicio(NombreDelServicio);
|
|
13
118
|
console.log("Se ha creado el servicio: " + NombreDelServicio);
|
|
14
119
|
}
|
|
@@ -39,8 +144,8 @@ class Servicio1 {
|
|
|
39
144
|
// informacionDelArchivo.Etiquetas = Etiquetas;
|
|
40
145
|
// await ejecutarConsulta("INSERT INTO `" + this.NombreDelRepositorioDeLaBaseDeDatos.slice(0, -3) + "`.`Archivos`\
|
|
41
146
|
// VALUES (?, ?, ?, ?, ?, NOW(4), ?)"
|
|
42
|
-
// , [Respuesta.insertId, Resultado.
|
|
43
|
-
// , Etiquetas, Resultado.
|
|
147
|
+
// , [Respuesta.insertId, Resultado.Identificador, informacionDelArchivo.rutaDeArchivo, informacionDelArchivo.nombreDeArchivo
|
|
148
|
+
// , Etiquetas, Resultado.Identificador]);
|
|
44
149
|
// return informacionDelArchivo;
|
|
45
150
|
// }
|
|
46
151
|
// return;
|
|
@@ -50,7 +155,7 @@ class Servicio1 {
|
|
|
50
155
|
// return await ejecutarConsulta("SELECT `BeneficioEstudiantilId`, `Nombre`, `Tipo`, `Estado` FROM `vve-bybe`.`BYBE_BeneficiosEstudiantiles`");
|
|
51
156
|
// }
|
|
52
157
|
|
|
53
|
-
// // Ejemplo de listar con paginador
|
|
158
|
+
// // Ejemplo de listar con paginador
|
|
54
159
|
// async listar(Parametros) {
|
|
55
160
|
// let Resultados = undefined;
|
|
56
161
|
// let ORDERBY = "";
|
|
@@ -109,7 +214,7 @@ class Servicio1 {
|
|
|
109
214
|
// async agergar(Datos) {
|
|
110
215
|
// return ejecutarConsulta("INSERT INTO `vve-bybe`.`BYBE_BeneficiosEstudiantiles` VALUES (NULL,\
|
|
111
216
|
// ?, ?, 'Activo', NOW(4), ?)"
|
|
112
|
-
// , [Datos.Nombre, Datos.Tipo, Resultado.
|
|
217
|
+
// , [Datos.Nombre, Datos.Tipo, Resultado.Identificador]);
|
|
113
218
|
// }
|
|
114
219
|
|
|
115
220
|
// async borrar(Datos) {
|
|
@@ -122,7 +227,7 @@ class Servicio1 {
|
|
|
122
227
|
// SET `Nombre` = ?, `Tipo` = ?, `Estado` = ?\
|
|
123
228
|
// , `LastUpdate` = NOW(4), `LastUser` = ?\
|
|
124
229
|
// WHERE `BeneficioEstudiantilId` = ?"
|
|
125
|
-
// , [Datos.Nombre, Datos.Tipo, Datos.Estado, Resultado.
|
|
230
|
+
// , [Datos.Nombre, Datos.Tipo, Datos.Estado, Resultado.Identificador, Datos.BeneficioEstudiantilId]);
|
|
126
231
|
// }
|
|
127
232
|
|
|
128
233
|
// async detalle(BeneficioEstudiantilId) {
|
|
@@ -37,3 +37,11 @@ CREATE OR REPLACE TABLE `NOMBRE_DEL_REPOSITORIO_DE_BASE_DE_DATOS`.`Configuracion
|
|
|
37
37
|
`LastUser` VARCHAR(1000) NOT NULL DEFAULT '-' COMMENT 'Último usuario que modificó la fila',
|
|
38
38
|
PRIMARY KEY (`Identificador`, `Titulo`)
|
|
39
39
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_spanish_ci COMMENT = 'Almacena la configuración de las tarjetas del contenedor principal por usuario';
|
|
40
|
+
|
|
41
|
+
CREATE OR REPLACE TABLE `NOMBRE_DEL_REPOSITORIO_DE_BASE_DE_DATOS`.`DatosMiscelaneos` (
|
|
42
|
+
`DatoMiscelaneo` VARCHAR(200) NOT NULL COMMENT 'Nombre del datos misceláneo que se desea almacenar',
|
|
43
|
+
`Datos` JSON NOT NULL COMMENT 'Configuración completa de la tarjeta: Tipo, Posición, Título, Descripción, Ícono, RutaASeguir, Rutas, ColorDeBorde, RequierePermisoExtra, Activo',
|
|
44
|
+
`LastUpdate` DATETIME(4) NOT NULL DEFAULT CURRENT_TIMESTAMP(4) ON UPDATE CURRENT_TIMESTAMP(4) COMMENT 'Fecha de la última actualización de la fila',
|
|
45
|
+
`LastUser` VARCHAR(1000) NOT NULL DEFAULT '-' COMMENT 'Último usuario que modificó la fila',
|
|
46
|
+
PRIMARY KEY (`DatoMiscelaneo`)
|
|
47
|
+
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_spanish_ci COMMENT = 'Datos misceláneos sobre el módulo';
|
|
@@ -2,6 +2,7 @@ import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/co
|
|
|
2
2
|
import { HttpClient } from '@angular/common/http';
|
|
3
3
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
|
+
import { DatosGlobalesService } from '../../../datos-globales.service';
|
|
5
6
|
import { marked } from 'marked';
|
|
6
7
|
import { Subject } from 'rxjs';
|
|
7
8
|
import { takeUntil } from 'rxjs/operators';
|
|
@@ -29,9 +30,11 @@ export class ManualComponent implements OnInit, OnDestroy {
|
|
|
29
30
|
private _destroy$ = new Subject<void>();
|
|
30
31
|
@ViewChild('contenidoManual') contenidoManualRef!: ElementRef;
|
|
31
32
|
|
|
32
|
-
constructor(private http: HttpClient, private sanitizer: DomSanitizer) {}
|
|
33
|
+
constructor(private http: HttpClient, private sanitizer: DomSanitizer, private datosGlobalesService: DatosGlobalesService) { }
|
|
33
34
|
|
|
34
35
|
ngOnInit(): void {
|
|
36
|
+
this.http.get(`${this.datosGlobalesService.ObtenerURL()}misc/VistaDelManual`).pipe(takeUntil(this._destroy$)).subscribe({ error: () => { } });
|
|
37
|
+
|
|
35
38
|
this.http.get('/Manual.md', { responseType: 'text' }).pipe(takeUntil(this._destroy$)).subscribe({
|
|
36
39
|
next: (markdown) => {
|
|
37
40
|
const html = marked.parse(markdown) as string;
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
.cabecera2 {
|
|
3
3
|
width: 100%;
|
|
4
4
|
border-bottom: 1px solid #707070;
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
gap: 16px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.cabecera2-info {
|
|
12
|
+
flex: 1;
|
|
13
|
+
min-width: 0;
|
|
5
14
|
}
|
|
6
15
|
|
|
7
16
|
.cabecera2 p {
|
|
@@ -9,6 +18,7 @@
|
|
|
9
18
|
font-family: "Roboto";
|
|
10
19
|
letter-spacing: 0;
|
|
11
20
|
color: #000;
|
|
21
|
+
margin: 0;
|
|
12
22
|
}
|
|
13
23
|
|
|
14
24
|
.cabecera2 .titulo2 {
|
|
@@ -20,6 +30,68 @@
|
|
|
20
30
|
font-size: small;
|
|
21
31
|
}
|
|
22
32
|
|
|
33
|
+
/* Filtro de tarjetas */
|
|
34
|
+
.filtro-tarjetas {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: 4px;
|
|
38
|
+
background: #f0f4f8;
|
|
39
|
+
border: 1px solid #c4d0de;
|
|
40
|
+
border-radius: 8px;
|
|
41
|
+
padding: 4px 8px;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.filtro-tarjetas:focus-within {
|
|
47
|
+
border-color: #1976d2;
|
|
48
|
+
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.15);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.filtro-icono {
|
|
52
|
+
font-size: 18px !important;
|
|
53
|
+
width: 18px !important;
|
|
54
|
+
height: 18px !important;
|
|
55
|
+
color: #6b7c8f;
|
|
56
|
+
flex-shrink: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.filtro-input {
|
|
60
|
+
border: none;
|
|
61
|
+
background: transparent;
|
|
62
|
+
outline: none;
|
|
63
|
+
font-size: 14px;
|
|
64
|
+
font-family: 'Roboto', sans-serif;
|
|
65
|
+
color: #333;
|
|
66
|
+
width: 180px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.filtro-input::placeholder {
|
|
70
|
+
color: #a0aab4;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.filtro-limpiar {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
border: none;
|
|
77
|
+
background: none;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
padding: 0;
|
|
80
|
+
color: #6b7c8f;
|
|
81
|
+
transition: color 0.15s ease;
|
|
82
|
+
flex-shrink: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.filtro-limpiar mat-icon {
|
|
86
|
+
font-size: 16px !important;
|
|
87
|
+
width: 16px !important;
|
|
88
|
+
height: 16px !important;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.filtro-limpiar:hover {
|
|
92
|
+
color: #c0392b;
|
|
93
|
+
}
|
|
94
|
+
|
|
23
95
|
/* Botones */
|
|
24
96
|
.botonesDeNavegacion {
|
|
25
97
|
padding: 10px;
|
|
@@ -66,8 +66,23 @@
|
|
|
66
66
|
<div class="contenido">
|
|
67
67
|
@if(TienePermiso){
|
|
68
68
|
<div class="cabecera2">
|
|
69
|
-
<
|
|
70
|
-
|
|
69
|
+
<div class="cabecera2-info">
|
|
70
|
+
<p class="titulo2">{{ Descripcion }}</p>
|
|
71
|
+
<p class="descripcion2">{{ Detalle }}</p>
|
|
72
|
+
</div>
|
|
73
|
+
@if (esDashboard) {
|
|
74
|
+
<div class="filtro-tarjetas">
|
|
75
|
+
<mat-icon class="filtro-icono">search</mat-icon>
|
|
76
|
+
<input class="filtro-input" type="text" placeholder="Buscar tarjeta..." [value]="filtro"
|
|
77
|
+
(input)="onFiltroChange($event)" aria-label="Buscar tarjeta por nombre" />
|
|
78
|
+
@if (filtro) {
|
|
79
|
+
<button class="filtro-limpiar" (click)="limpiarFiltro()" title="Limpiar búsqueda"
|
|
80
|
+
aria-label="Limpiar búsqueda">
|
|
81
|
+
<mat-icon>close</mat-icon>
|
|
82
|
+
</button>
|
|
83
|
+
}
|
|
84
|
+
</div>
|
|
85
|
+
}
|
|
71
86
|
</div>
|
|
72
87
|
<ng-content><router-outlet /></ng-content>
|
|
73
88
|
}@else{
|
|
@@ -37,6 +37,17 @@ export class ContenedorComponentesComponent implements OnInit, OnDestroy {
|
|
|
37
37
|
public AnimarUsuariosActivos: boolean = true;
|
|
38
38
|
private intervaloUsuarios: any;
|
|
39
39
|
|
|
40
|
+
get esDashboard(): boolean { return this.router.url === '/'; }
|
|
41
|
+
get filtro(): string { return this.datosGlobalesService.filtroDeTarjetas$.value; }
|
|
42
|
+
|
|
43
|
+
onFiltroChange(event: Event): void {
|
|
44
|
+
this.datosGlobalesService.filtroDeTarjetas$.next((event.target as HTMLInputElement).value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
limpiarFiltro(): void {
|
|
48
|
+
this.datosGlobalesService.filtroDeTarjetas$.next('');
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
constructor(private http: HttpClient, private datosGlobalesService: DatosGlobalesService, private location: Location, private dialog: MatDialog, private router: Router, private ngZone: NgZone) {
|
|
41
52
|
if (datosGlobalesService.ObtenerToken() === '') {
|
|
42
53
|
datosGlobalesService.RedirigirALogin();
|
package/templates/frontend/src/app/Paginas/contenedor-principal/contenedor-principal.component.css
CHANGED
|
@@ -14,6 +14,17 @@
|
|
|
14
14
|
box-sizing: border-box;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
.tarjeta-opaca {
|
|
18
|
+
opacity: 0.15;
|
|
19
|
+
filter: grayscale(50%);
|
|
20
|
+
transition: opacity 0.25s ease, filter 0.25s ease;
|
|
21
|
+
pointer-events: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.contenido>div[cdkDrag] {
|
|
25
|
+
transition: opacity 0.25s ease, filter 0.25s ease;
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
.cdk-drag-preview {
|
|
18
29
|
box-sizing: border-box;
|
|
19
30
|
border-radius: 8px;
|
package/templates/frontend/src/app/Paginas/contenedor-principal/contenedor-principal.component.html
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="contenido" cdkDropList cdkDropListOrientation="mixed" (cdkDropListDropped)="drop($event)">
|
|
2
2
|
@for (tarjeta of tarjetas; track tarjeta.titulo) {
|
|
3
|
-
<div cdkDrag>
|
|
3
|
+
<div cdkDrag [class.tarjeta-opaca]="esDimmed(tarjeta)">
|
|
4
4
|
@switch (tarjeta.type) {
|
|
5
5
|
@case ('single') {
|
|
6
6
|
<app-tarjeta [rutaASeguir]="$any(tarjeta).rutaASeguir" [titulo]="tarjeta.titulo" [descripcion]="tarjeta.descripcion"
|
package/templates/frontend/src/app/Paginas/contenedor-principal/contenedor-principal.component.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Component, OnInit } from "@angular/core";
|
|
1
|
+
import { Component, OnInit, OnDestroy } from "@angular/core";
|
|
2
2
|
import { CommonModule } from "@angular/common";
|
|
3
|
+
import { Subject } from "rxjs";
|
|
4
|
+
import { takeUntil } from "rxjs/operators";
|
|
3
5
|
import { TarjetaComponent } from "../../Componentes/Nucleo/tarjeta/tarjeta.component";
|
|
4
6
|
import { TarjetaMultipleComponent } from "../../Componentes/Nucleo/tarjeta-multiple/tarjeta-multiple.component";
|
|
5
7
|
import { TarjetaReporteComponent } from "../../Componentes/Nucleo/tarjeta-reporte/tarjeta-reporte.component";
|
|
@@ -58,17 +60,33 @@ type AnyTarjetaConfig = TarjetaConfig | TarjetaMultipleConfig | TarjetaReporteCo
|
|
|
58
60
|
styleUrl: "./contenedor-principal.component.css"
|
|
59
61
|
})
|
|
60
62
|
|
|
61
|
-
export class ContenedorPrincipalComponent implements OnInit {
|
|
63
|
+
export class ContenedorPrincipalComponent implements OnInit, OnDestroy {
|
|
62
64
|
public cantidad: number = 0;
|
|
63
65
|
public cantidadMaxima: number = 0;
|
|
64
66
|
public tarjetas: AnyTarjetaConfig[] = [];
|
|
67
|
+
public filtro: string = '';
|
|
68
|
+
private destroy$ = new Subject<void>();
|
|
65
69
|
|
|
66
70
|
constructor(private http: HttpClient, private datosGlobalesService: DatosGlobalesService) {
|
|
67
71
|
this.cantidad = 0;
|
|
68
72
|
this.cantidadMaxima = 0;
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
ngOnDestroy(): void {
|
|
76
|
+
this.destroy$.next();
|
|
77
|
+
this.destroy$.complete();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
esDimmed(tarjeta: AnyTarjetaConfig): boolean {
|
|
81
|
+
if (!this.filtro.trim()) return false;
|
|
82
|
+
return !tarjeta.titulo.toLowerCase().includes(this.filtro.toLowerCase());
|
|
83
|
+
}
|
|
84
|
+
|
|
71
85
|
ngOnInit() {
|
|
86
|
+
this.datosGlobalesService.filtroDeTarjetas$
|
|
87
|
+
.pipe(takeUntil(this.destroy$))
|
|
88
|
+
.subscribe(f => this.filtro = f);
|
|
89
|
+
|
|
72
90
|
this.http.get(`${this.datosGlobalesService.ObtenerURL()}misc/obtenerTarjetasDelContenedor`).subscribe({
|
|
73
91
|
next: (datos: any) => {
|
|
74
92
|
this.tarjetas = (datos.body ?? []).map((d: any) => this.mapearTarjeta(d));
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
|
+
import { BehaviorSubject } from 'rxjs';
|
|
2
3
|
|
|
3
4
|
@Injectable({
|
|
4
5
|
providedIn: 'root'
|
|
5
6
|
})
|
|
6
7
|
export class DatosGlobalesService {
|
|
7
8
|
|
|
9
|
+
readonly filtroDeTarjetas$ = new BehaviorSubject<string>('');
|
|
10
|
+
|
|
8
11
|
constructor() { }
|
|
9
12
|
|
|
10
13
|
ObtenerToken() {
|
|
11
14
|
const baseUrl = this.ObtenerURL();
|
|
12
15
|
if (baseUrl === 'http://localhost/') {
|
|
13
|
-
return 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
16
|
+
return 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxMiIsIklkZW50aWZpY2Fkb3IiOiIxMiIsImlhdCI6MTc3Nzk4OTgwOSwiZXhwIjoxNzc4MDI1ODA5fQ.T1kUm7H4hwbapQ3eFok31GVvezkPitdTlJL96MorozY';
|
|
14
17
|
}
|
|
15
18
|
const match = document.cookie.match(/(?:^|;\s*)_siguid=([^;]+)/);
|
|
16
19
|
let token = match ? decodeURIComponent(match[1]) : '';
|