refs #1608 and #1561 Fix selection in both views. Add multi-selection in cards view.

deb-pkg
Lucas Lara García 2025-02-26 09:34:29 +01:00
parent f0e3a34f00
commit 1962beaf8a
5 changed files with 109 additions and 87 deletions

View File

@ -1,20 +1,18 @@
<ng-container [ngSwitch]="buttonType">
<button *ngSwitchCase="'icon'" mat-icon-button color="primary" [matMenuTriggerFor]="commandMenu">
<button *ngSwitchCase="'icon'" mat-icon-button color="primary" [matMenuTriggerFor]="commandMenu"
[disabled]="disabled">
<mat-icon>{{ icon }}</mat-icon>
</button>
<button class="action-button" [disabled]="clientData.length === 0" *ngSwitchCase="'text'" [matMenuTriggerFor]="commandMenu">
<button class="action-button" [disabled]="clientData.length === 0 || disabled" *ngSwitchCase="'text'"
[matMenuTriggerFor]="commandMenu">
{{ buttonText }}
</button>
</ng-container>
<mat-menu #commandMenu="matMenu" >
<button
mat-menu-item
[disabled]="command.disabled || (command.slug === 'create-image' && clientData.length > 1)"
*ngFor="let command of arrayCommands"
(click)="onCommandSelect(command.slug)"
>
<mat-menu #commandMenu="matMenu">
<button mat-menu-item [disabled]="command.disabled || (command.slug === 'create-image' && clientData.length > 1)"
*ngFor="let command of arrayCommands" (click)="onCommandSelect(command.slug)">
{{ command.name }}
</button>
</mat-menu>
</mat-menu>

View File

@ -15,6 +15,7 @@ export class ExecuteCommandComponent implements OnInit {
@Input() buttonType: 'icon' | 'text' = 'icon';
@Input() buttonText: string = 'Ejecutar Comandos';
@Input() icon: string = 'terminal';
@Input() disabled: boolean = false;
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
loading: boolean = true;

View File

@ -328,12 +328,6 @@ mat-tree mat-tree-node.disabled:hover {
overflow-y: auto;
}
.clients-container {
width: 75%;
box-sizing: border-box;
overflow-y: auto;
}
.client-item {
display: flex;
justify-content: center;
@ -378,13 +372,6 @@ mat-tree mat-tree-node.disabled:hover {
color: #666;
}
.clients-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 4px;
/* Espaciado reducido entre cards */
}
.clients-list {
display: flex;
flex-direction: column;
@ -405,15 +392,6 @@ mat-tree mat-tree-node.disabled:hover {
flex-direction: column;
}
.clients-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
/* 6 columnas por fila */
gap: 16px;
/* Espaciado entre tarjetas */
padding: 20px;
}
.clients-list .list-item-content {
display: flex;
justify-content: space-between;
@ -568,4 +546,24 @@ mat-button-toggle-group {
.mat-button-toggle-group .mat-button-toggle.mat-button-toggle-disabled {
background-color: #c7c7c7;
}
.clients-container {
width: 75%;
box-sizing: border-box;
overflow-y: auto;
}
.cards-view {
display: flex;
width: 100%;
}
.clients-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 16px;
padding: 8px 20px 20px 20px;
width: 100%;
box-sizing: border-box;
}

View File

@ -171,8 +171,8 @@
</span>
</div>
<div class="view-type-container">
<app-execute-command [clientData]="arrayClients" [buttonType]="'text'"
[buttonText]="'Ejecutar comandos'"></app-execute-command>
<app-execute-command [clientData]="selection.selected" [buttonType]="'text'"
[buttonText]="'Ejecutar comandos'" [disabled]="selection.selected.length === 0"></app-execute-command>
<mat-button-toggle-group name="viewType" aria-label="View Type" [hideSingleSelectionIndicator]="true"
(change)="toggleView($event.value)">
<mat-button-toggle value="list" [disabled]="currentView === 'list'">
@ -190,55 +190,63 @@
<div *ngIf="!isLoadingClients">
<div *ngIf="hasClients; else noClientsTemplate">
<!-- Cards view -->
<div class="clients-grid" *ngIf="currentView === 'card'">
<div *ngFor="let client of arrayClients" class="client-item">
<div class="client-card">
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
[checked]="selection.isSelected(client)" [disabled]="client.status === 'busy'">
</mat-checkbox>
<img [src]="'assets/images/ordenador_' + client.status + '.png'" alt="Client Icon"
class="client-image" />
<div *ngIf="currentView === 'card'" class="cards-view">
<mat-checkbox (change)="toggleAllCards()" [checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()" style="margin-left: 1em; margin-top: 0.5rem;">
</mat-checkbox>
<div class="clients-grid">
<div *ngFor="let client of arrayClients" class="client-item">
<div class="client-card">
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
[checked]="selection.isSelected(client)" [disabled]="client.status === 'busy'">
</mat-checkbox>
<img [src]="'assets/images/ordenador_' + client.status + '.png'" alt="Client Icon"
class="client-image" />
<div class="client-details">
<span class="client-name">{{ client.name }}</span>
<span class="client-ip">{{ client.ip }}</span>
<span class="client-ip">{{ client.mac }}</span>
<div class="action-icons">
<button *ngIf="(!syncStatus || syncingClientId !== client.uuid)" mat-icon-button color="primary"
(click)="getStatus(client, selectedNode)">
<mat-icon>sync</mat-icon>
</button>
<button *ngIf="syncStatus && syncingClientId === client.uuid" mat-icon-button color="primary">
<mat-spinner diameter="24"></mat-spinner>
</button>
<app-execute-command [clientData]="[client]" [buttonType]="'icon'"
[icon]="'terminal'"></app-execute-command>
<button mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #clientMenu="matMenu">
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
<mat-icon>edit</mat-icon>
<span>{{ 'edit' | translate }}</span>
<div class="client-details">
<span class="client-name">{{ client.name }}</span>
<span class="client-ip">{{ client.ip }}</span>
<span class="client-ip">{{ client.mac }}</span>
<div class="action-icons">
<button *ngIf="(!syncStatus || syncingClientId !== client.uuid)" mat-icon-button color="primary"
(click)="getStatus(client, selectedNode)">
<mat-icon>sync</mat-icon>
</button>
<button mat-menu-item (click)="onShowClientDetail($event, client)">
<mat-icon>visibility</mat-icon>
<span>{{ 'viewDetails' | translate }}</span>
<button *ngIf="syncStatus && syncingClientId === client.uuid" mat-icon-button color="primary">
<mat-spinner diameter="24"></mat-spinner>
</button>
<button mat-menu-item (click)="onDeleteClick($event, client)">
<mat-icon>delete</mat-icon>
<span>{{ 'delete' | translate }}</span>
<app-execute-command [clientData]="[client]" [buttonType]="'icon'" [icon]="'terminal'"
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"></app-execute-command>
<button
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"
mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
<mat-icon>more_vert</mat-icon>
</button>
</mat-menu>
<mat-menu #clientMenu="matMenu">
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
<mat-icon>edit</mat-icon>
<span>{{ 'edit' | translate }}</span>
</button>
<button mat-menu-item (click)="onShowClientDetail($event, client)">
<mat-icon>visibility</mat-icon>
<span>{{ 'viewDetails' | translate }}</span>
</button>
<button mat-menu-item (click)="onDeleteClick($event, client)">
<mat-icon>delete</mat-icon>
<span>{{ 'delete' | translate }}</span>
</button>
</mat-menu>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- List view -->
<div class="clients-table" *ngIf="currentView === 'list'">
<table mat-table matSort [dataSource]="selectedClients" class="mat-elevation-z8">
@ -314,11 +322,13 @@
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'actions' | translate }} </th>
<td mat-cell *matCellDef="let client">
<button mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
<button
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"
mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
<mat-icon>more_vert</mat-icon>
</button>
<app-execute-command [clientData]="[client]" [buttonType]="'icon'"
[icon]="'terminal'"></app-execute-command>
<app-execute-command [clientData]="[client]" [buttonType]="'icon'" [icon]="'terminal'"
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"></app-execute-command>
<mat-menu #clientMenu="matMenu">
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
<mat-icon>edit</mat-icon>

View File

@ -244,7 +244,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
}
private refreshData(selectedNodeIdOrUuid?: string): void {
private refreshData(selectedNodeIdOrUuid?: string, selectedClientsBeforeEdit: string[] = []): void {
this.dataService.getOrganizationalUnits().subscribe({
next: (data) => {
this.originalTreeData = data.map((unidad) => this.convertToTreeData(unidad));
@ -255,13 +255,13 @@ export class GroupsComponent implements OnInit, OnDestroy {
if (this.selectedNode) {
this.treeControl.collapseAll();
this.expandPathToNode(this.selectedNode);
this.fetchClientsForNode(this.selectedNode);
this.fetchClientsForNode(this.selectedNode, selectedClientsBeforeEdit);
}
} else {
this.treeControl.collapseAll();
if (this.treeDataSource.data.length > 0) {
this.selectedNode = this.treeDataSource.data[0];
this.fetchClientsForNode(this.selectedNode);
this.fetchClientsForNode(this.selectedNode, selectedClientsBeforeEdit);
} else {
this.selectedNode = null;
this.selectedClients.data = [];
@ -333,7 +333,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
}
public fetchClientsForNode(node: TreeNode): void {
public fetchClientsForNode(node: TreeNode, selectedClientsBeforeEdit: string[] = []): void {
this.isLoadingClients = true;
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}`).subscribe({
next: (response) => {
@ -342,6 +342,13 @@ export class GroupsComponent implements OnInit, OnDestroy {
this.hasClients = node.hasClients ?? false;
this.isLoadingClients = false;
this.initialLoading = false;
this.selection.clear();
selectedClientsBeforeEdit.forEach(uuid => {
const client = this.selectedClients.data.find(client => client.uuid === uuid);
if (client) {
this.selection.select(client);
}
});
},
error: () => {
this.isLoadingClients = false;
@ -483,12 +490,13 @@ export class GroupsComponent implements OnInit, OnDestroy {
onEditClick(event: MouseEvent, type: string, uuid: string): void {
event.stopPropagation();
const selectedClientsBeforeEdit = this.selection.selected.map(client => client.uuid);
const dialogRef = type !== NodeType.Client
? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
dialogRef.afterClosed().subscribe(() => {
this.refreshData(this.selectedNode?.id);
this.refreshData(this.selectedNode?.id, selectedClientsBeforeEdit);
});
}
@ -668,12 +676,19 @@ export class GroupsComponent implements OnInit, OnDestroy {
toggleAllRows() {
if (this.isAllSelected()) {
this.selection.clear();
this.arrayClients = []
return;
} else {
this.selection.select(...this.selectedClients.data);
}
this.updateSelectedClients();
}
this.selection.select(...this.selectedClients.data);
this.arrayClients = [...this.selection.selected];
toggleAllCards() {
if (this.isAllSelected()) {
this.selection.clear();
} else {
this.selection.select(...this.selectedClients.data);
}
this.updateSelectedClients();
}
@ -684,7 +699,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
updateSelectedClients() {
this.arrayClients = [...this.selection.selected];
this.arrayClients = this.selectedClients.data;
}