refs #781 command task create modal added stepper

oggui/commands
Alvaro Puente Mella 2024-09-27 14:21:54 +02:00
parent ab303ba76e
commit 7927addaab
8 changed files with 266 additions and 135 deletions

View File

@ -1,60 +1,60 @@
<div class="header-container">
<h2 class="title">Administrar Tareas</h2>
<div class="task-button-row">
<button mat-flat-button color="primary" (click)="openCreateTaskModal()">Añadir Tarea</button>
</div>
<h2 class="title">Administrar Tareas</h2>
<div class="task-button-row">
<button mat-flat-button color="primary" (click)="openCreateTaskModal()">Añadir Tarea</button>
</div>
<table mat-table [dataSource]="tasks" class="mat-elevation-z8">
<!-- Nombre de la tarea -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Creado por </th>
<td mat-cell *matCellDef="let task"> {{ task.createdBy }} </td>
</ng-container>
<!-- Fecha de ejecución -->
<ng-container matColumnDef="scheduledDate">
<th mat-header-cell *matHeaderCellDef> Fecha de Ejecución </th>
<td mat-cell *matCellDef="let task"> {{ task.dateTime | date:'short' }} </td>
</ng-container>
<!-- Estado -->
<ng-container matColumnDef="enabled">
<th mat-header-cell *matHeaderCellDef> Estado </th>
<td mat-cell *matCellDef="let task"> {{ task.enabled ? 'Habilitado' : 'Deshabilitado' }} </td>
</ng-container>
<!-- Acciones -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> Acciones </th>
<td mat-cell *matCellDef="let task">
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</div>
<table mat-table [dataSource]="tasks" class="mat-elevation-z8">
<ng-container matColumnDef="notes">
<th mat-header-cell *matHeaderCellDef> Info</th>
<td mat-cell *matCellDef="let task"> {{ task.notes }} </td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Creado por </th>
<td mat-cell *matCellDef="let task"> {{ task.createdBy }} </td>
</ng-container>
<ng-container matColumnDef="scheduledDate">
<th mat-header-cell *matHeaderCellDef> Fecha de Ejecución </th>
<td mat-cell *matCellDef="let task"> {{ task.dateTime | date:'short' }} </td>
</ng-container>
<ng-container matColumnDef="enabled">
<th mat-header-cell *matHeaderCellDef> Estado </th>
<td mat-cell *matCellDef="let task"> {{ task.enabled ? 'Habilitado' : 'Deshabilitado' }} </td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> Acciones </th>
<td mat-cell *matCellDef="let task">
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="viewTaskDetails(task)">
<mat-icon>info</mat-icon>
<span>Detalles</span>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="viewTaskDetails(task)">
<mat-icon>info</mat-icon>
<span>Detalles</span>
</button>
<button mat-menu-item (click)="editTask(task)">
<mat-icon>edit</mat-icon>
<span>Editar</span>
</button>
<button mat-menu-item (click)="deleteTask(task)">
<mat-icon>delete</mat-icon>
<span>Eliminar</span>
</button>
</mat-menu>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
<button mat-menu-item (click)="editTask(task)">
<mat-icon>edit</mat-icon>
<span>Editar</span>
</button>
<button mat-menu-item (click)="deleteTask(task)">
<mat-icon>delete</mat-icon>
<span>Eliminar</span>
</button>
</mat-menu>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>

View File

@ -19,7 +19,7 @@ export class CommandsTaskComponent implements OnInit {
itemsPerPage: number = 10;
page: number = 1;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
displayedColumns: string[] = ['name', 'scheduledDate', 'enabled', 'actions']; // Actualizado con las nuevas columnas
displayedColumns: string[] = ['notes','name', 'scheduledDate', 'enabled', 'actions'];
private apiUrl = `${this.baseUrl}/command-tasks`;
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService) {}
@ -64,7 +64,7 @@ export class CommandsTaskComponent implements OnInit {
deleteTask(task: any): void {
this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: task.createdBy }, // Usamos 'createdBy' en lugar de 'name' ya que cambiaste la columna
data: { name: task.createdBy },
}).afterClosed().subscribe((result) => {
if (result) {
this.http.delete(`${this.apiUrl}/${task.uuid}`).subscribe({
@ -81,7 +81,7 @@ export class CommandsTaskComponent implements OnInit {
}
onPageChange(event: any): void {
this.page = event.pageIndex + 1; // pageIndex es cero basado, así que sumamos 1
this.page = event.pageIndex + 1;
this.itemsPerPage = event.pageSize;
this.loadTasks();
}

View File

@ -22,4 +22,65 @@
.command-item:hover {
background-color: #e9e9e9;
}
/* Estilos generales para el dialogo */
.mat-dialog-content {
padding: 20px;
max-width: 600px; /* Ancho máximo del dialogo */
}
/* Estilos para el stepper */
.mat-horizontal-stepper {
margin: 20px 0;
}
/* Estilos para cada paso */
.mat-step {
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
background-color: #ffffff; /* Fondo blanco para los pasos */
}
/* Estilos para el encabezado de los pasos */
.mat-step-header {
background-color: #f5f5f5; /* Fondo gris claro para el encabezado */
padding: 10px;
border-radius: 4px;
font-weight: 600; /* Negrita para el texto del encabezado */
}
/* Estilos para los botones */
button.mat-button {
margin-right: 10px;
}
/* Estilos para los campos de formulario */
.mat-form-field {
margin-bottom: 20px; /* Espaciado entre campos */
}
/* Estilos para errores */
.mat-error {
font-size: 0.875em; /* Tamaño de fuente más pequeño para mensajes de error */
color: #d32f2f; /* Color rojo para errores */
}
/* Estilos para listas de comandos */
.commands-list {
margin: 20px 0;
border: 1px solid #e0e0e0; /* Borde gris claro */
border-radius: 4px;
padding: 10px;
background-color: #fafafa; /* Fondo gris muy claro */
}
/* Estilos para elementos de comando */
.command-item {
padding: 5px 0;
border-bottom: 1px solid #e0e0e0; /* Separación entre comandos */
}
.command-item:last-child {
border-bottom: none; /* Quitar borde del último elemento */
}

View File

@ -1,68 +1,104 @@
<h2 mat-dialog-title>Crear Tarea</h2>
<mat-dialog-content [formGroup]="taskForm">
<!-- Grupo de Comandos -->
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Comandos</mat-label>
<mat-select formControlName="commandGroup" (selectionChange)="onCommandGroupChange()">
<mat-option *ngFor="let group of availableCommandGroups" [value]="group.uuid">
{{ group.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('commandGroup')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Grupo de Comandoss</mat-label>
<mat-select formControlName="commandGroup" (selectionChange)="onCommandGroupChange()">
<mat-option *ngFor="let group of availableCommandGroups" [value]="group.uuid">
{{ group.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('commandGroup')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<form [formGroup]="taskForm">
<mat-dialog-content>
<mat-horizontal-stepper>
<!-- Paso 1: Información y Selecciona Comandos -->
<mat-step label="Información y Selecciona Comandos">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Información</mat-label>
<textarea matInput formControlName="notes" placeholder="notas"></textarea>
</mat-form-field>
<!-- Mostrar Comandos del Grupo Seleccionado -->
<div *ngIf="selectedGroupCommands.length > 0" class="commands-list">
<h3>Comandos del Grupo Seleccionado</h3>
<div *ngFor="let command of selectedGroupCommands" class="command-item">
{{ command.name }}
</div>
</div>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Comandos</mat-label>
<mat-select formControlName="commandGroup" (selectionChange)="onCommandGroupChange()">
<mat-option *ngFor="let group of availableCommandGroups" [value]="group.uuid">
{{ group.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('commandGroup')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<!-- Comandos Extras -->
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Comandos Individuales (Opcional)</mat-label>
<mat-select formControlName="extraCommands" multiple>
<mat-option *ngFor="let command of availableIndividualCommands" [value]="command.uuid">
{{ command.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div>
<button mat-button matStepperNext>Continuar</button>
</div>
</mat-step>
<!-- Fecha de Ejecución -->
<mat-form-field appearance="fill" class="full-width">
<mat-label>Fecha de Ejecución</mat-label>
<input matInput [matDatepicker]="picker" formControlName="date" placeholder="Selecciona una fecha">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="taskForm.get('date')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<!-- Paso 2: Selecciona Comandos Individuales -->
<mat-step label="Selecciona Comandos Individuales">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Comandos Individuales (Opcional)</mat-label>
<mat-select formControlName="extraCommands" multiple>
<mat-option *ngFor="let command of availableIndividualCommands" [value]="command.uuid">
{{ command.name }}
</mat-option>
</mat-select>
</mat-form-field>
<!-- Hora de Ejecución -->
<mat-form-field appearance="fill" class="full-width">
<mat-label>Hora de Ejecución</mat-label>
<input matInput type="time" formControlName="time" placeholder="Selecciona una hora">
<mat-error *ngIf="taskForm.get('time')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<div>
<button mat-button matStepperPrevious>Atrás</button>
<button mat-button matStepperNext>Continuar</button>
</div>
</mat-step>
<!-- Notas -->
<mat-form-field appearance="fill" class="full-width">
<mat-label>Notas</mat-label>
<textarea matInput formControlName="notes" placeholder="Opcional"></textarea>
</mat-form-field>
</mat-dialog-content>
<!-- Paso 3: Fecha de Ejecución y Hora -->
<mat-step label="Fecha de Ejecución y Hora">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Fecha de Ejecución</mat-label>
<input matInput [matDatepicker]="picker" formControlName="date" placeholder="Selecciona una fecha">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="taskForm.get('date')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<mat-dialog-actions align="end">
<button mat-button (click)="close()">Cancelar</button>
<button mat-flat-button color="primary" (click)="saveTask()">Guardar</button>
</mat-dialog-actions>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Hora de Ejecución</mat-label>
<input matInput type="time" formControlName="time" placeholder="Selecciona una hora">
<mat-error *ngIf="taskForm.get('time')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<div>
<button mat-button matStepperPrevious>Atrás</button>
<button mat-button matStepperNext>Continuar</button>
</div>
</mat-step>
<!-- Paso 4: Selecciona Unidad Organizacional, Aula y Clientes -->
<mat-step label="Selecciona Unidad Organizacional, Aula y Clientes">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Unidad Organizacional</mat-label>
<mat-select formControlName="organizationalUnit" (selectionChange)="onOrganizationalUnitChange()">
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
{{ unit.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('organizationalUnit')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona aula</mat-label>
<mat-select formControlName="selectedChild" (selectionChange)="onChildChange()">
<mat-option *ngFor="let child of selectedUnitChildren" [value]="child['@id']">
{{ child.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Clientes</mat-label>
<mat-select formControlName="selectedClients" multiple>
<mat-option *ngFor="let client of selectedClients" [value]="client.uuid">
{{ client.name }} ({{ client.ip }})
</mat-option>
</mat-select>
</mat-form-field>
<div>
<button mat-button matStepperPrevious>Atrás</button>
<button mat-flat-button color="primary" (click)="saveTask()">Guardar</button>
</div>
</mat-step>
</mat-horizontal-stepper>
</mat-dialog-content>
</form>

View File

@ -16,29 +16,34 @@ export class CreateTaskComponent implements OnInit {
selectedGroupCommands: any[] = [];
availableIndividualCommands: any[] = [];
apiUrl = `${this.baseUrl}/command-tasks`;
editing: boolean = false; // Flag to check if in edit mode
editing: boolean = false;
availableOrganizationalUnits: any[] = [];
selectedUnitChildren: any[] = [];
selectedClients: any[] = [];
constructor(
private fb: FormBuilder,
private http: HttpClient,
private toastr: ToastrService,
public dialogRef: MatDialogRef<CreateTaskComponent>,
@Inject(MAT_DIALOG_DATA) public data: any // Inject the task data if editing
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.taskForm = this.fb.group({
commandGroup: [''],
extraCommands: [[]],
date: ['', Validators.required],
time: ['', Validators.required],
notes: ['']
notes: [''],
organizationalUnit: ['', Validators.required],
selectedChild: [''],
selectedClients: [[]]
});
}
ngOnInit(): void {
this.loadCommandGroups();
this.loadIndividualCommands();
// If task data is provided, load it into the form (edit mode)
this.loadOrganizationalUnits();
if (this.data && this.data.task) {
this.editing = true;
this.loadTaskData(this.data.task);
@ -67,17 +72,27 @@ export class CreateTaskComponent implements OnInit {
);
}
loadOrganizationalUnits(): void {
this.http.get<any>('http://127.0.0.1:8001/organizational-units?page=1&itemsPerPage=30').subscribe(
(data) => {
this.availableOrganizationalUnits = data['hydra:member'].filter((unit: any) => unit['type'] === 'organizational-unit');
},
(error) => {
this.toastr.error('Error al cargar las unidades organizacionales');
}
);
}
loadTaskData(task: any): void {
// Populate form fields with task data when editing
this.taskForm.patchValue({
commandGroup: task.commandGroup ? task.commandGroup['@id'] : '',
extraCommands: task.commands ? task.commands.map((cmd: any) => cmd['@id']) : [],
date: task.dateTime ? task.dateTime.split('T')[0] : '',
time: task.dateTime ? task.dateTime.split('T')[1].slice(0, 5) : '',
notes: task.notes || ''
notes: task.notes || '',
organizationalUnit: task.organizationalUnit ? task.organizationalUnit['@id'] : ''
});
// If a command group is selected, load its commands
if (task.commandGroup) {
this.selectedGroupCommands = task.commandGroup.commands;
}
@ -95,6 +110,25 @@ export class CreateTaskComponent implements OnInit {
);
}
onOrganizationalUnitChange(): void {
const selectedUnitId = this.taskForm.get('organizationalUnit')?.value;
const selectedUnit = this.availableOrganizationalUnits.find(unit => unit['@id'] === selectedUnitId);
this.selectedUnitChildren = selectedUnit ? selectedUnit.children : [];
}
onChildChange(): void {
const selectedChildId = this.taskForm.get('selectedChild')?.value;
this.http.get<any>(`${this.baseUrl}${selectedChildId}`).subscribe(
(data) => {
this.selectedClients = data.clients;
this.taskForm.patchValue({ selectedClients: [] });
},
(error) => {
this.toastr.error('Error al cargar los detalles del aula seleccionada');
}
);
}
saveTask(): void {
if (this.taskForm.invalid) {
this.toastr.error('Por favor, rellene todos los campos obligatorios');
@ -103,7 +137,6 @@ export class CreateTaskComponent implements OnInit {
const formData = this.taskForm.value;
const dateTime = this.combineDateAndTime(formData.date, formData.time);
const selectedCommands = formData.extraCommands.length > 0
? formData.extraCommands.map((id: any) => `/commands/${id}`)
: [""];
@ -116,7 +149,6 @@ export class CreateTaskComponent implements OnInit {
};
if (this.editing) {
// Perform a PATCH request if editing an existing task
const taskId = this.data.task.uuid;
this.http.patch<any>(`${this.apiUrl}/${taskId}`, payload).subscribe({
next: () => {
@ -128,7 +160,6 @@ export class CreateTaskComponent implements OnInit {
}
});
} else {
// Perform a POST request if creating a new task
this.http.post<any>(this.apiUrl, payload).subscribe({
next: () => {
this.toastr.success('Tarea creada con éxito');
@ -144,7 +175,6 @@ export class CreateTaskComponent implements OnInit {
combineDateAndTime(date: string, time: string): string {
const dateObj = new Date(date);
const [hours, minutes] = time.split(':').map(Number);
dateObj.setHours(hours, minutes, 0);
return dateObj.toISOString();
}

View File

@ -30,3 +30,7 @@
flex: 2;
padding: 5px;
}
button{
margin-left: 10px;
}

View File

@ -2,7 +2,7 @@
<h2 class="title" i18n="@@adminImagesTitle">Administrar clientes</h2>
<div class="images-button-row">
<button mat-flat-button color="primary" (click)="resetFilters()">Reiniciar filtros</button>
<button mat-flat-button color="primary" (click)="addClient($event)">Añadir imagen</button>
<button mat-flat-button color="primary" (click)="addClient($event)">Añadir cliente</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>

View File

@ -53,7 +53,7 @@ export class CreateClientComponent implements OnInit {
ip: ['', Validators.required],
menu: [this.data.organizationalUnit && this.data.organizationalUnit.networkSettings && this.data.organizationalUnit.networkSettings.menu ? this.data.organizationalUnit.networkSettings.menu['@id'] : null],
hardwareProfile: [this.data.organizationalUnit && this.data.organizationalUnit.networkSettings && this.data.organizationalUnit.networkSettings.hardwareProfile ? this.data.organizationalUnit.networkSettings.hardwareProfile['@id'] : null],
ogLive: [null, Validators.required]
ogLive: [null]
});
}