refs #1446 Refactor GroupsComponent: Improve loading state management
testing/ogGui-multibranch/pipeline/head This commit looks good
Details
testing/ogGui-multibranch/pipeline/head This commit looks good
Details
parent
d9f3fd6203
commit
3bd923cbbb
|
@ -13,7 +13,8 @@
|
||||||
matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">
|
matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">
|
||||||
{{ 'newOrganizationalUnitButton' | translate }}
|
{{ 'newOrganizationalUnitButton' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button mat-flat-button color="primary" [matMenuTriggerFor]="menuClients">{{ 'newClientButton' | translate }}</button>
|
<button mat-flat-button color="primary" [matMenuTriggerFor]="menuClients">{{ 'newClientButton' | translate
|
||||||
|
}}</button>
|
||||||
<mat-menu #menuClients="matMenu">
|
<mat-menu #menuClients="matMenu">
|
||||||
<button mat-menu-item (click)="addClient($event)">{{ 'newSingleClientButton' | translate }}</button>
|
<button mat-menu-item (click)="addClient($event)">{{ 'newSingleClientButton' | translate }}</button>
|
||||||
<button mat-menu-item (click)="addMultipleClients($event)">{{ 'newMultipleClientButton' | translate }}</button>
|
<button mat-menu-item (click)="addMultipleClients($event)">{{ 'newMultipleClientButton' | translate }}</button>
|
||||||
|
@ -26,332 +27,309 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filters Panel -->
|
<div *ngIf="initialLoading; else contentTemplate">
|
||||||
<div class="filters-panel" joyrideStep="filtersPanelStep" text="{{ 'filtersPanelStepText' | translate }}">
|
<app-loading [isLoading]="initialLoading"></app-loading>
|
||||||
<div class="filters-container">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>{{ 'searchTree' | translate }}</mat-label>
|
|
||||||
<input matInput #treeSearchInput (input)="onTreeFilterInput($event)" placeholder="Centro, aula, grupos ..." />
|
|
||||||
<button
|
|
||||||
*ngIf="treeSearchInput.value"
|
|
||||||
mat-icon-button
|
|
||||||
matSuffix
|
|
||||||
aria-label="Clear tree search"
|
|
||||||
(click)="clearTreeSearch(treeSearchInput)"
|
|
||||||
>
|
|
||||||
<mat-icon>close</mat-icon>
|
|
||||||
</button>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>{{ 'searchClient' | translate }}</mat-label>
|
|
||||||
<input matInput #clientSearchInput (input)="onClientFilterInput($event)" placeholder="Nombre, IP, estado o MAC" />
|
|
||||||
<button
|
|
||||||
*ngIf="clientSearchInput.value"
|
|
||||||
mat-icon-button
|
|
||||||
matSuffix
|
|
||||||
aria-label="Clear client search"
|
|
||||||
(click)="clearClientSearch(clientSearchInput)"
|
|
||||||
>
|
|
||||||
<mat-icon>close</mat-icon>
|
|
||||||
</button>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<!-- Funcionalidad actualmente deshabilitada-->
|
|
||||||
<!-- <mat-form-field appearance="outline">
|
|
||||||
<mat-select (selectionChange)="loadSelectedFilter($event.value)" placeholder="Cargar filtro" disabled>
|
|
||||||
<mat-option *ngFor="let savedFilter of savedFilterNames" [value]="savedFilter">
|
|
||||||
{{ savedFilter[0] }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>{{ 'filterByType' | translate }}</mat-label>
|
|
||||||
<mat-select [(value)]="selectedTreeFilter" (selectionChange)="filterTree(searchTerm, $event.value)" disabled>
|
|
||||||
<mat-option [value]=""> {{ 'all' | translate }} </mat-option>
|
|
||||||
<mat-option value="classrooms-group">{{ 'classroomsGroup' | translate }}</mat-option>
|
|
||||||
<mat-option value="classroom">{{ 'classrooms' | translate }}</mat-option>
|
|
||||||
<mat-option value="group">{{ 'computerGroups' | translate }}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Unit details view-->
|
<ng-template #contentTemplate>
|
||||||
<div class="main-container">
|
<!-- Filters Panel -->
|
||||||
<!-- Tree view -->
|
<div class="filters-panel" joyrideStep="filtersPanelStep" text="{{ 'filtersPanelStepText' | translate }}">
|
||||||
<div class="tree-container">
|
<div class="filters-container">
|
||||||
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl">
|
<mat-form-field appearance="outline">
|
||||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}" *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding (click)="onNodeClick(node)">
|
<mat-label>{{ 'searchTree' | translate }}</mat-label>
|
||||||
<button mat-icon-button matTreeNodeToggle [disabled]="!node.expandable" [ngClass]="{'disabled-toggle': !node.expandable}">
|
<input matInput #treeSearchInput (input)="onTreeFilterInput($event)" placeholder="Centro, aula, grupos ..." />
|
||||||
<mat-icon>{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}</mat-icon>
|
<button *ngIf="treeSearchInput.value" mat-icon-button matSuffix aria-label="Clear tree search"
|
||||||
|
(click)="clearTreeSearch(treeSearchInput)">
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<mat-icon class="node-icon {{ node.type }}">
|
</mat-form-field>
|
||||||
{{
|
<mat-form-field appearance="outline">
|
||||||
node.type === 'organizational-unit' ? 'apartment'
|
<mat-label>{{ 'searchClient' | translate }}</mat-label>
|
||||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
<input matInput #clientSearchInput (input)="onClientFilterInput($event)"
|
||||||
: node.type === 'classroom' ? 'school'
|
placeholder="Nombre, IP, estado o MAC" />
|
||||||
: node.type === 'clients-group' ? 'lan'
|
<button *ngIf="clientSearchInput.value" mat-icon-button matSuffix aria-label="Clear client search"
|
||||||
: node.type === 'client' ? 'computer'
|
(click)="clearClientSearch(clientSearchInput)">
|
||||||
: 'group'
|
<mat-icon>close</mat-icon>
|
||||||
}}
|
|
||||||
</mat-icon>
|
|
||||||
<span>{{ node.name }}</span>
|
|
||||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onNodeClick(node)">
|
|
||||||
<mat-icon>more_vert</mat-icon>
|
|
||||||
</button>
|
</button>
|
||||||
</mat-tree-node>
|
</mat-form-field>
|
||||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}" *matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding (click)="onNodeClick(node)">
|
|
||||||
<button mat-icon-button matTreeNodeToggle [disabled]="true" class="disabled-toggle"></button>
|
<!-- Funcionalidad actualmente deshabilitada-->
|
||||||
<mat-icon style="color: green;">
|
<!-- <mat-form-field appearance="outline">
|
||||||
{{
|
<mat-select (selectionChange)="loadSelectedFilter($event.value)" placeholder="Cargar filtro" disabled>
|
||||||
node.type === 'organizational-unit' ? 'apartment'
|
<mat-option *ngFor="let savedFilter of savedFilterNames" [value]="savedFilter">
|
||||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
{{ savedFilter[0] }}
|
||||||
: node.type === 'classroom' ? 'school'
|
</mat-option>
|
||||||
: node.type === 'clients-group' ? 'lan'
|
</mat-select>
|
||||||
: node.type === 'client' ? 'computer'
|
</mat-form-field>
|
||||||
: 'group'
|
<mat-form-field appearance="outline">
|
||||||
}}
|
<mat-label>{{ 'filterByType' | translate }}</mat-label>
|
||||||
</mat-icon>
|
<mat-select [(value)]="selectedTreeFilter" (selectionChange)="filterTree(searchTerm, $event.value)" disabled>
|
||||||
<span>{{ node.name }}</span>
|
<mat-option [value]=""> {{ 'all' | translate }} </mat-option>
|
||||||
<ng-container *ngIf="node.type === 'client'">
|
<mat-option value="classrooms-group">{{ 'classroomsGroup' | translate }}</mat-option>
|
||||||
<span> - IP: {{ node.ip }}</span>
|
<mat-option value="classroom">{{ 'classrooms' | translate }}</mat-option>
|
||||||
</ng-container>
|
<mat-option value="group">{{ 'computerGroups' | translate }}</mat-option>
|
||||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onNodeClick(node)">
|
</mat-select>
|
||||||
<mat-icon>more_vert</mat-icon>
|
</mat-form-field> -->
|
||||||
</button>
|
</div>
|
||||||
</mat-tree-node>
|
|
||||||
</mat-tree>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-divider [vertical]="true"></mat-divider>
|
<!-- Unit details view-->
|
||||||
|
<div class="main-container">
|
||||||
<!-- Tree node actions -->
|
<!-- Tree view -->
|
||||||
<mat-menu restoreFocus=false #commandMenu="matMenu">
|
<div class="tree-container">
|
||||||
<button mat-menu-item *ngFor="let command of commands" (click)="executeCommand(command, selectedNode)">
|
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl">
|
||||||
<span>{{ command.name }}</span>
|
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}"
|
||||||
</button>
|
*matTreeNodeDef="let node; when: hasChild" matTreeNodePadding (click)="onNodeClick(node)">
|
||||||
</mat-menu>
|
<button mat-icon-button matTreeNodeToggle [disabled]="!node.expandable"
|
||||||
<mat-menu #menuNode="matMenu">
|
[ngClass]="{'disabled-toggle': !node.expandable}">
|
||||||
<button mat-menu-item (click)="onShowDetailsClick($event, selectedNode)">
|
<mat-icon>{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}</mat-icon>
|
||||||
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
</button>
|
||||||
<span>{{ 'viewUnitMenu' | translate }}</span>
|
<mat-icon class="node-icon {{ node.type }}">
|
||||||
</button>
|
{{
|
||||||
<button *ngIf="selectedNode?.type === 'classroom'" mat-menu-item (click)="onRoomMap(selectedNode)">
|
node.type === 'organizational-unit' ? 'apartment'
|
||||||
<mat-icon>map</mat-icon>
|
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||||
<span>{{ 'roomMap' | translate }}</span>
|
: node.type === 'classroom' ? 'school'
|
||||||
</button>
|
: node.type === 'clients-group' ? 'lan'
|
||||||
<button mat-menu-item (click)="addClient($event, selectedNode)">
|
: node.type === 'client' ? 'computer'
|
||||||
<mat-icon>add</mat-icon>
|
: 'group'
|
||||||
<span>{{ 'newSingleClientButton' | translate }}</span>
|
}}
|
||||||
</button>
|
</mat-icon>
|
||||||
<button mat-menu-item (click)="addMultipleClients($event, selectedNode)">
|
<span>{{ node.name }}</span>
|
||||||
<mat-icon>playlist_add</mat-icon>
|
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onNodeClick(node)">
|
||||||
<span>{{ 'newMultipleClientButton' | translate }}</span>
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item (click)="addOU($event, selectedNode)">
|
</mat-tree-node>
|
||||||
<mat-icon>account_tree</mat-icon>
|
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}"
|
||||||
<span>{{ 'addOrganizationalUnit' | translate }}</span>
|
*matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding (click)="onNodeClick(node)">
|
||||||
</button>
|
<button mat-icon-button matTreeNodeToggle [disabled]="true" class="disabled-toggle"></button>
|
||||||
<button mat-menu-item (click)="onEditNode($event, selectedNode)">
|
<mat-icon style="color: green;">
|
||||||
<mat-icon>edit</mat-icon>
|
{{
|
||||||
<span>{{ 'edit' | translate }}</span>
|
node.type === 'organizational-unit' ? 'apartment'
|
||||||
</button>
|
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||||
<button mat-menu-item (click)="onDeleteClick($event, selectedNode)">
|
: node.type === 'classroom' ? 'school'
|
||||||
<mat-icon>delete</mat-icon>
|
: node.type === 'clients-group' ? 'lan'
|
||||||
<span>{{ 'delete' | translate }}</span>
|
: node.type === 'client' ? 'computer'
|
||||||
</button>
|
: 'group'
|
||||||
</mat-menu>
|
}}
|
||||||
|
</mat-icon>
|
||||||
<!-- Clients view -->
|
<span>{{ node.name }}</span>
|
||||||
<div class="clients-container">
|
<ng-container *ngIf="node.type === 'client'">
|
||||||
<div class="clients-view-header">
|
<span> - IP: {{ node.ip }}</span>
|
||||||
<span class="clients-title-name">{{ 'clients' | translate }}
|
</ng-container>
|
||||||
<strong>{{ selectedNode?.name }}</strong>
|
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onNodeClick(node)">
|
||||||
</span>
|
<mat-icon>more_vert</mat-icon>
|
||||||
<div class="view-type-container">
|
</button>
|
||||||
<app-execute-command
|
</mat-tree-node>
|
||||||
[clientData]="arrayClients"
|
</mat-tree>
|
||||||
[buttonType]="'text'"
|
|
||||||
[buttonText]="'Ejecutar comandos'"
|
|
||||||
></app-execute-command>
|
|
||||||
<button mat-button color="primary" (click)="toggleView('card')" [disabled]="currentView === 'card'">
|
|
||||||
<mat-icon>grid_view</mat-icon> {{ 'Vista Tarjeta' | translate }}
|
|
||||||
</button>
|
|
||||||
<button mat-button color="primary" (click)="toggleView('list')" [disabled]="currentView === 'list'">
|
|
||||||
<mat-icon>list</mat-icon> {{ 'Vista Lista' | translate }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="(selectedClients.data?.length || 0) > 0; else noClientsTemplate">
|
<mat-divider [vertical]="true"></mat-divider>
|
||||||
<!-- Cards view -->
|
|
||||||
<div class="clients-grid" *ngIf="currentView === 'card'">
|
|
||||||
<div *ngFor="let client of selectedClients.data" class="client-item">
|
|
||||||
<div class="client-card">
|
|
||||||
<img
|
|
||||||
[src]="'assets/images/ordenador_' + client.status + '.png'"
|
|
||||||
alt="Client Icon"
|
|
||||||
class="client-image" />
|
|
||||||
|
|
||||||
<div class="client-details">
|
<!-- Tree node actions -->
|
||||||
<span class="client-name">{{ client.name }}</span>
|
<mat-menu restoreFocus=false #commandMenu="matMenu">
|
||||||
<span class="client-ip">{{ client.ip }}</span>
|
<button mat-menu-item *ngFor="let command of commands" (click)="executeCommand(command, selectedNode)">
|
||||||
<span class="client-ip">{{ client.mac }}</span>
|
<span>{{ command.name }}</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
<mat-menu #menuNode="matMenu">
|
||||||
|
<button mat-menu-item (click)="onShowDetailsClick($event, selectedNode)">
|
||||||
|
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
||||||
|
<span>{{ 'viewUnitMenu' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="selectedNode?.type === 'classroom'" mat-menu-item (click)="onRoomMap(selectedNode)">
|
||||||
|
<mat-icon>map</mat-icon>
|
||||||
|
<span>{{ 'roomMap' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="addClient($event, selectedNode)">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
<span>{{ 'newSingleClientButton' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="addMultipleClients($event, selectedNode)">
|
||||||
|
<mat-icon>playlist_add</mat-icon>
|
||||||
|
<span>{{ 'newMultipleClientButton' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="addOU($event, selectedNode)">
|
||||||
|
<mat-icon>account_tree</mat-icon>
|
||||||
|
<span>{{ 'addOrganizationalUnit' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onEditNode($event, selectedNode)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
<span>{{ 'edit' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onDeleteClick($event, selectedNode)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
<span>{{ 'delete' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
<div class="action-icons">
|
<!-- Clients view -->
|
||||||
<button
|
<div class="clients-container">
|
||||||
*ngIf="(!syncStatus || syncingClientId !== client.uuid)"
|
<div class="clients-view-header">
|
||||||
mat-icon-button color="primary"
|
<span [ngStyle]="{ visibility: isLoadingClients ? 'hidden' : 'visible' }" class="clients-title-name">
|
||||||
(click)="getStatus(client, selectedNode)">
|
{{ 'clients' | translate }}
|
||||||
<mat-icon>sync</mat-icon>
|
<strong>{{ selectedNode?.name }}</strong>
|
||||||
</button>
|
</span>
|
||||||
|
<div class="view-type-container">
|
||||||
|
<app-execute-command [clientData]="arrayClients" [buttonType]="'text'"
|
||||||
|
[buttonText]="'Ejecutar comandos'"></app-execute-command>
|
||||||
|
<button mat-button color="primary" (click)="toggleView('card')" [disabled]="currentView === 'card'">
|
||||||
|
<mat-icon>grid_view</mat-icon> {{ 'Vista Tarjeta' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-button color="primary" (click)="toggleView('list')" [disabled]="currentView === 'list'">
|
||||||
|
<mat-icon>list</mat-icon> {{ 'Vista Lista' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<div *ngIf="isLoadingClients">
|
||||||
*ngIf="syncStatus && syncingClientId === client.uuid"
|
<app-loading [isLoading]="isLoadingClients"></app-loading>
|
||||||
mat-icon-button color="primary">
|
</div>
|
||||||
<mat-spinner diameter="24"></mat-spinner>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-icon-button color="primary" (click)="onShowClientDetail($event, client)">
|
<div *ngIf="!isLoadingClients">
|
||||||
<mat-icon>visibility</mat-icon>
|
<div *ngIf="hasClients; else noClientsTemplate">
|
||||||
</button>
|
<!-- Cards view -->
|
||||||
<app-execute-command
|
<div class="clients-grid" *ngIf="currentView === 'card'">
|
||||||
[clientData]="[client]"
|
<div *ngFor="let client of selectedClients.data" class="client-item">
|
||||||
[buttonType]="'icon'"
|
<div class="client-card">
|
||||||
[icon]="'terminal'"
|
<img [src]="'assets/images/ordenador_' + client.status + '.png'" alt="Client Icon"
|
||||||
></app-execute-command>
|
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>
|
||||||
|
|
||||||
|
<button mat-icon-button color="primary" (click)="onShowClientDetail($event, client)">
|
||||||
|
<mat-icon>visibility</mat-icon>
|
||||||
|
</button>
|
||||||
|
<app-execute-command [clientData]="[client]" [buttonType]="'icon'"
|
||||||
|
[icon]="'terminal'"></app-execute-command>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</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">
|
||||||
|
<ng-container matColumnDef="select">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>
|
||||||
|
<mat-checkbox (change)="$event ? toggleAllRows() : null"
|
||||||
|
[checked]="selection.hasValue() && isAllSelected()"
|
||||||
|
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||||
|
</mat-checkbox>
|
||||||
|
</th>
|
||||||
|
<td mat-cell *matCellDef="let row">
|
||||||
|
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(row)"
|
||||||
|
[checked]="selection.isSelected(row)" [disabled]="row.status === 'busy'">
|
||||||
|
</mat-checkbox>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="status">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'status' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}" matTooltipPosition="left"
|
||||||
|
matTooltipShowDelay="500">
|
||||||
|
<img [src]="'assets/images/ordenador_' + client.status + '.png'" alt="Client Icon"
|
||||||
|
class="client-image" />
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="sync">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'sync' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let client">
|
||||||
|
<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>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'name' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}" matTooltipPosition="left"
|
||||||
|
matTooltipShowDelay="500">
|
||||||
|
<div class="client-info">
|
||||||
|
<div class="client-name">{{ client.name }}</div>
|
||||||
|
<div class="client-ip">{{ client.ip }}</div>
|
||||||
|
<div class="client-ip">{{ client.mac }}</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="oglive">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> OG Live </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ (client.ogLive?.filename || '').slice(0, 15) }}{{
|
||||||
|
(client.ogLive?.filename?.length > 15) ? '...' : '' }} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="maintenace">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'maintenance' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.maintenance }} </td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="subnet">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'subnet' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.subnet }} </td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="pxeTemplate">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'pxeTemplate' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.template?.name }} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="parentName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'parent' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.parentName }} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<app-execute-command [clientData]="[client]" [buttonType]="'icon'"
|
||||||
|
[icon]="'terminal'"></app-execute-command>
|
||||||
|
<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>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||||
|
</table>
|
||||||
|
<mat-paginator [pageSize]="10" [pageSizeOptions]="[5, 10, 20, 50]" showFirstLastButtons></mat-paginator>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- No clients view -->
|
||||||
<!-- List view -->
|
<ng-template #noClientsTemplate>
|
||||||
<div class="clients-table" *ngIf="currentView === 'list'">
|
<div *ngIf="!initialLoading" class="no-clients-info">
|
||||||
<table mat-table matSort [dataSource]="selectedClients" class="mat-elevation-z8">
|
<span>{{ 'noClients' | translate }}</span>
|
||||||
<ng-container matColumnDef="select">
|
<mat-icon>error_outline</mat-icon>
|
||||||
<th mat-header-cell *matHeaderCellDef>
|
</div>
|
||||||
<mat-checkbox (change)="$event ? toggleAllRows() : null"
|
</ng-template>
|
||||||
[checked]="selection.hasValue() && isAllSelected()"
|
|
||||||
[indeterminate]="selection.hasValue() && !isAllSelected()"
|
|
||||||
>
|
|
||||||
</mat-checkbox>
|
|
||||||
</th>
|
|
||||||
<td mat-cell *matCellDef="let row">
|
|
||||||
<mat-checkbox (click)="$event.stopPropagation()"
|
|
||||||
(change)="toggleRow(row)"
|
|
||||||
[checked]="selection.isSelected(row)"
|
|
||||||
[disabled]="row.status === 'busy'"
|
|
||||||
>
|
|
||||||
</mat-checkbox>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="status">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'status' | translate }} </th>
|
|
||||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}"
|
|
||||||
matTooltipPosition="left" matTooltipShowDelay="500">
|
|
||||||
<img
|
|
||||||
[src]="'assets/images/ordenador_' + client.status + '.png'"
|
|
||||||
alt="Client Icon"
|
|
||||||
class="client-image" />
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="sync">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'sync' | translate }} </th>
|
|
||||||
<td mat-cell *matCellDef="let client">
|
|
||||||
<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>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="name">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'name' | translate }} </th>
|
|
||||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}"
|
|
||||||
matTooltipPosition="left" matTooltipShowDelay="500">
|
|
||||||
<div class="client-info">
|
|
||||||
<div class="client-name">{{ client.name }}</div>
|
|
||||||
<div class="client-ip">{{ client.ip }}</div>
|
|
||||||
<div class="client-ip">{{ client.mac }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="oglive">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> OG Live </th>
|
|
||||||
<td mat-cell *matCellDef="let client"> {{ (client.ogLive?.filename || '').slice(0, 15) }}{{ (client.ogLive?.filename?.length > 15) ? '...' : '' }} </td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="maintenace">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'maintenance' | translate }} </th>
|
|
||||||
<td mat-cell *matCellDef="let client"> {{ client.maintenance }} </td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="subnet">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'subnet' | translate }} </th>
|
|
||||||
<td mat-cell *matCellDef="let client"> {{ client.subnet }} </td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="pxeTemplate">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'pxeTemplate' | translate }} </th>
|
|
||||||
<td mat-cell *matCellDef="let client"> {{ client.template?.name }} </td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="parentName">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'parent' | translate }} </th>
|
|
||||||
<td mat-cell *matCellDef="let client"> {{ client.parentName }} </td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<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">
|
|
||||||
<mat-icon>more_vert</mat-icon>
|
|
||||||
</button>
|
|
||||||
<app-execute-command
|
|
||||||
[clientData]="[client]"
|
|
||||||
[buttonType]="'icon'"
|
|
||||||
[icon]="'terminal'"
|
|
||||||
></app-execute-command>
|
|
||||||
<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>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
|
||||||
</table>
|
|
||||||
<mat-paginator [pageSize]="10" [pageSizeOptions]="[5, 10, 20, 50]" showFirstLastButtons></mat-paginator>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-template>
|
||||||
<!-- No clients view -->
|
|
||||||
<ng-template #noClientsTemplate>
|
|
||||||
<div *ngIf="isLoadingClients" class="loading-container">
|
|
||||||
<mat-spinner></mat-spinner>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!isLoadingClients" class="no-clients-info">
|
|
||||||
<span>{{ 'noClients' | translate }}</span>
|
|
||||||
<mat-icon>error_outline</mat-icon>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -25,7 +25,8 @@ import { JoyrideModule } from 'ngx-joyride';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatTreeModule } from '@angular/material/tree';
|
import { MatTreeModule } from '@angular/material/tree';
|
||||||
import { TreeNode } from './model/model';
|
import { TreeNode } from './model/model';
|
||||||
import {ExecuteCommandComponent} from "../commands/main-commands/execute-command/execute-command.component";
|
import { LoadingComponent } from '../../shared/loading/loading.component'; // Importa el componente LoadingComponent
|
||||||
|
import { ExecuteCommandComponent } from '../commands/main-commands/execute-command/execute-command.component';
|
||||||
|
|
||||||
describe('GroupsComponent', () => {
|
describe('GroupsComponent', () => {
|
||||||
let component: GroupsComponent;
|
let component: GroupsComponent;
|
||||||
|
@ -33,7 +34,7 @@ describe('GroupsComponent', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [GroupsComponent, ExecuteCommandComponent],
|
declarations: [GroupsComponent, ExecuteCommandComponent, LoadingComponent], // Declara LoadingComponent
|
||||||
imports: [
|
imports: [
|
||||||
HttpClientTestingModule,
|
HttpClientTestingModule,
|
||||||
ToastrModule.forRoot(),
|
ToastrModule.forRoot(),
|
||||||
|
@ -64,8 +65,7 @@ describe('GroupsComponent', () => {
|
||||||
{ provide: MatDialogRef, useValue: {} },
|
{ provide: MatDialogRef, useValue: {} },
|
||||||
{ provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } }
|
{ provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } }
|
||||||
]
|
]
|
||||||
})
|
}).compileComponents();
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(GroupsComponent);
|
fixture = TestBed.createComponent(GroupsComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
@ -76,18 +76,6 @@ describe('GroupsComponent', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call search on ngOnInit', () => {
|
|
||||||
spyOn(component, 'search');
|
|
||||||
component.ngOnInit();
|
|
||||||
expect(component.search).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call search method', () => {
|
|
||||||
spyOn(component, 'search');
|
|
||||||
component.search();
|
|
||||||
expect(component.search).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear selection', () => {
|
it('should clear selection', () => {
|
||||||
spyOn(component, 'clearSelection');
|
spyOn(component, 'clearSelection');
|
||||||
component.clearSelection();
|
component.clearSelection();
|
||||||
|
@ -121,4 +109,20 @@ describe('GroupsComponent', () => {
|
||||||
component.expandPathToNode(node);
|
component.expandPathToNode(node);
|
||||||
expect(component.expandPathToNode).toHaveBeenCalledWith(node);
|
expect(component.expandPathToNode).toHaveBeenCalledWith(node);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
it('should handle node click', () => {
|
||||||
|
const node: TreeNode = { id: '1', name: 'Node 1', type: 'type', children: [] };
|
||||||
|
spyOn<any>(component, 'fetchClientsForNode');
|
||||||
|
component.onNodeClick(node);
|
||||||
|
expect(component.selectedNode).toBe(node);
|
||||||
|
expect(component['fetchClientsForNode']).toHaveBeenCalledWith(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch clients for node', () => {
|
||||||
|
const node: TreeNode = { id: '1', name: 'Node 1', type: 'type', children: [] };
|
||||||
|
spyOn(component['http'], 'get').and.callThrough();
|
||||||
|
component.fetchClientsForNode(node);
|
||||||
|
expect(component.isLoadingClients).toBeTrue();
|
||||||
|
expect(component['http'].get).toHaveBeenCalledWith(`${component.baseUrl}/clients?organizationalUnit.id=${node.id}`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -21,8 +21,8 @@ import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-
|
||||||
import { MatSort } from '@angular/material/sort';
|
import { MatSort } from '@angular/material/sort';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { MatPaginator } from '@angular/material/paginator';
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
import {CreateMultipleClientComponent} from "./shared/clients/create-multiple-client/create-multiple-client.component";
|
import { CreateMultipleClientComponent } from "./shared/clients/create-multiple-client/create-multiple-client.component";
|
||||||
import {SelectionModel} from "@angular/cdk/collections";
|
import { SelectionModel } from "@angular/cdk/collections";
|
||||||
|
|
||||||
enum NodeType {
|
enum NodeType {
|
||||||
OrganizationalUnit = 'organizational-unit',
|
OrganizationalUnit = 'organizational-unit',
|
||||||
|
@ -42,13 +42,14 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
organizationalUnits: UnidadOrganizativa[] = [];
|
organizationalUnits: UnidadOrganizativa[] = [];
|
||||||
selectedUnidad: UnidadOrganizativa | null = null;
|
selectedUnidad: UnidadOrganizativa | null = null;
|
||||||
selectedDetail: UnidadOrganizativa | null = null;
|
selectedDetail: UnidadOrganizativa | null = null;
|
||||||
loading = false;
|
initialLoading: boolean = true;
|
||||||
isLoadingClients: boolean = false;
|
isLoadingClients: boolean = false;
|
||||||
searchTerm = '';
|
searchTerm = '';
|
||||||
treeControl: FlatTreeControl<FlatNode>;
|
treeControl: FlatTreeControl<FlatNode>;
|
||||||
treeFlattener: MatTreeFlattener<TreeNode, FlatNode>;
|
treeFlattener: MatTreeFlattener<TreeNode, FlatNode>;
|
||||||
treeDataSource: MatTreeFlatDataSource<TreeNode, FlatNode>;
|
treeDataSource: MatTreeFlatDataSource<TreeNode, FlatNode>;
|
||||||
selectedNode: TreeNode | null = null;
|
selectedNode: TreeNode | null = null;
|
||||||
|
hasClients: boolean = false;
|
||||||
commands: Command[] = [];
|
commands: Command[] = [];
|
||||||
commandsLoading = false;
|
commandsLoading = false;
|
||||||
selectedClients = new MatTableDataSource<Client>([]);
|
selectedClients = new MatTableDataSource<Client>([]);
|
||||||
|
@ -62,7 +63,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
private originalTreeData: TreeNode[] = [];
|
private originalTreeData: TreeNode[] = [];
|
||||||
arrayClients: any[] = [];
|
arrayClients: any[] = [];
|
||||||
|
|
||||||
displayedColumns: string[] = ['select', 'status','sync', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions'];
|
displayedColumns: string[] = ['select', 'status', 'sync', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions'];
|
||||||
|
|
||||||
private _sort!: MatSort;
|
private _sort!: MatSort;
|
||||||
private _paginator!: MatPaginator;
|
private _paginator!: MatPaginator;
|
||||||
|
@ -109,8 +110,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
|
this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.search();
|
|
||||||
this.updateGridCols();
|
this.updateGridCols();
|
||||||
this.refreshData();
|
this.refreshData();
|
||||||
window.addEventListener('resize', this.updateGridCols);
|
window.addEventListener('resize', this.updateGridCols);
|
||||||
|
@ -126,11 +127,13 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
window.removeEventListener('resize', this.updateGridCols);
|
window.removeEventListener('resize', this.updateGridCols);
|
||||||
this.subscriptions.unsubscribe();
|
this.subscriptions.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private transformer = (node: TreeNode, level: number): FlatNode => ({
|
private transformer = (node: TreeNode, level: number): FlatNode => ({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
name: node.name,
|
name: node.name,
|
||||||
|
@ -142,15 +145,18 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
'@id': node['@id'],
|
'@id': node['@id'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
toggleView(view: 'card' | 'list'): void {
|
toggleView(view: 'card' | 'list'): void {
|
||||||
this.currentView = view;
|
this.currentView = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
updateGridCols = (): void => {
|
updateGridCols = (): void => {
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth;
|
||||||
this.cols = width <= 600 ? 1 : width <= 960 ? 2 : width <= 1280 ? 3 : 4;
|
this.cols = width <= 600 ? 1 : width <= 960 ? 2 : width <= 1280 ? 3 : 4;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
clearSelection(): void {
|
clearSelection(): void {
|
||||||
this.selectedUnidad = null;
|
this.selectedUnidad = null;
|
||||||
this.selectedDetail = null;
|
this.selectedDetail = null;
|
||||||
|
@ -158,6 +164,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
this.selectedNode = null;
|
this.selectedNode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Función para obtener los filtros guardados actualmente deshabilitada
|
// Función para obtener los filtros guardados actualmente deshabilitada
|
||||||
// getFilters(): void {
|
// getFilters(): void {
|
||||||
// this.subscriptions.add(
|
// this.subscriptions.add(
|
||||||
|
@ -171,6 +178,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
// )
|
// )
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
getFilters(): void {
|
getFilters(): void {
|
||||||
this.subscriptions.add(
|
this.subscriptions.add(
|
||||||
this.dataService.getFilters().subscribe(
|
this.dataService.getFilters().subscribe(
|
||||||
|
@ -184,44 +193,32 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
search(): void {
|
|
||||||
this.loading = true;
|
|
||||||
this.subscriptions.add(
|
|
||||||
this.dataService.getOrganizationalUnits(this.searchTerm).subscribe(
|
|
||||||
(data) => {
|
|
||||||
this.organizationalUnits = data;
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.error('Error fetching organizational units', error);
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private convertToTreeData(data: UnidadOrganizativa): TreeNode {
|
private convertToTreeData(data: UnidadOrganizativa): TreeNode {
|
||||||
const processNode = (node: UnidadOrganizativa): TreeNode => ({
|
const processNode = (node: UnidadOrganizativa): TreeNode => {
|
||||||
id: node.id,
|
const children = node.children?.map(processNode) || [];
|
||||||
uuid: node.uuid,
|
const hasClients = (node.clients?.length ?? 0) > 0 || children.some(child => child.hasClients);
|
||||||
name: node.name,
|
|
||||||
type: node.type,
|
return {
|
||||||
'@id': node['@id'],
|
id: node.id,
|
||||||
children: node.children?.map(processNode) || [],
|
uuid: node.uuid,
|
||||||
hasClients: (node.clients?.length ?? 0) > 0,
|
name: node.name,
|
||||||
});
|
type: node.type,
|
||||||
|
'@id': node['@id'],
|
||||||
|
children: children,
|
||||||
|
hasClients: hasClients,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return processNode(data);
|
return processNode(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private refreshData(selectedNodeIdOrUuid?: string): void {
|
|
||||||
this.loading = true;
|
|
||||||
this.isLoadingClients = !!selectedNodeIdOrUuid;
|
|
||||||
|
|
||||||
|
private refreshData(selectedNodeIdOrUuid?: string): void {
|
||||||
this.dataService.getOrganizationalUnits().subscribe({
|
this.dataService.getOrganizationalUnits().subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
this.originalTreeData = data.map((unidad) => this.convertToTreeData(unidad));
|
this.originalTreeData = data.map((unidad) => this.convertToTreeData(unidad));
|
||||||
this.treeDataSource.data = [...this.originalTreeData];
|
this.treeDataSource.data = [...this.originalTreeData];
|
||||||
|
|
||||||
if (selectedNodeIdOrUuid) {
|
if (selectedNodeIdOrUuid) {
|
||||||
this.selectedNode = this.findNodeByIdOrUuid(this.treeDataSource.data, selectedNodeIdOrUuid);
|
this.selectedNode = this.findNodeByIdOrUuid(this.treeDataSource.data, selectedNodeIdOrUuid);
|
||||||
|
|
||||||
|
@ -240,19 +237,15 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
this.selectedClients.data = [];
|
this.selectedClients.data = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
this.isLoadingClients = false;
|
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
console.error('Error fetching organizational units', error);
|
console.error('Error fetching organizational units', error);
|
||||||
this.toastr.error('Ocurrió un error al cargar las unidades organizativas');
|
this.toastr.error('Ocurrió un error al cargar las unidades organizativas');
|
||||||
this.loading = false;
|
|
||||||
this.isLoadingClients = false;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
expandPathToNode(node: TreeNode): void {
|
expandPathToNode(node: TreeNode): void {
|
||||||
const path: TreeNode[] = [];
|
const path: TreeNode[] = [];
|
||||||
let currentNode: TreeNode | null = node;
|
let currentNode: TreeNode | null = node;
|
||||||
|
@ -270,6 +263,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private findParentNode(treeData: TreeNode[], childId: string): TreeNode | null {
|
private findParentNode(treeData: TreeNode[], childId: string): TreeNode | null {
|
||||||
for (const node of treeData) {
|
for (const node of treeData) {
|
||||||
if (node.children?.some((child) => child.id === childId)) {
|
if (node.children?.some((child) => child.id === childId)) {
|
||||||
|
@ -286,6 +280,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private findNodeByIdOrUuid(treeData: TreeNode[], identifier: string): TreeNode | null {
|
private findNodeByIdOrUuid(treeData: TreeNode[], identifier: string): TreeNode | null {
|
||||||
const search = (nodes: TreeNode[]): TreeNode | null => {
|
const search = (nodes: TreeNode[]): TreeNode | null => {
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
|
@ -300,24 +295,30 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
return search(treeData);
|
return search(treeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onNodeClick(node: TreeNode): void {
|
onNodeClick(node: TreeNode): void {
|
||||||
this.selectedNode = node;
|
this.selectedNode = node;
|
||||||
this.fetchClientsForNode(node);
|
this.fetchClientsForNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchClientsForNode(node: TreeNode): void {
|
|
||||||
|
public fetchClientsForNode(node: TreeNode): void {
|
||||||
this.isLoadingClients = true;
|
this.isLoadingClients = true;
|
||||||
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}`).subscribe({
|
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}`).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.selectedClients.data = response['hydra:member'];
|
this.selectedClients.data = response['hydra:member'];
|
||||||
|
this.hasClients = node.hasClients ?? false;
|
||||||
this.isLoadingClients = false;
|
this.isLoadingClients = false;
|
||||||
|
this.initialLoading = false;
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
this.isLoadingClients = false;
|
this.isLoadingClients = false;
|
||||||
|
this.initialLoading = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
addOU(event: MouseEvent, parent: TreeNode | null = null): void {
|
addOU(event: MouseEvent, parent: TreeNode | null = null): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, {
|
const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, {
|
||||||
|
@ -332,6 +333,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
addClient(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
|
addClient(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const targetNode = organizationalUnit || this.selectedNode;
|
const targetNode = organizationalUnit || this.selectedNode;
|
||||||
|
@ -353,6 +355,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
addMultipleClients(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
|
addMultipleClients(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const targetNode = organizationalUnit || this.selectedNode;
|
const targetNode = organizationalUnit || this.selectedNode;
|
||||||
|
@ -377,14 +380,15 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onEditNode(event: MouseEvent, node: TreeNode | null): void {
|
onEditNode(event: MouseEvent, node: TreeNode | null): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const uuid = node ? this.extractUuid(node['@id']) : null;
|
const uuid = node ? this.extractUuid(node['@id']) : null;
|
||||||
if (!uuid) return;
|
if (!uuid) return;
|
||||||
|
|
||||||
const dialogRef = node?.type !== NodeType.Client
|
const dialogRef = node?.type !== NodeType.Client
|
||||||
? this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
? this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
||||||
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(() => {
|
dialogRef.afterClosed().subscribe(() => {
|
||||||
if (node) {
|
if (node) {
|
||||||
|
@ -393,6 +397,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onDeleteClick(event: MouseEvent, node: TreeNode | null): void {
|
onDeleteClick(event: MouseEvent, node: TreeNode | null): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const uuid = node ? this.extractUuid(node['@id']) : null;
|
const uuid = node ? this.extractUuid(node['@id']) : null;
|
||||||
|
@ -411,6 +416,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private deleteEntityorClient(uuid: string, type: string): void {
|
private deleteEntityorClient(uuid: string, type: string): void {
|
||||||
if (!this.selectedNode) return;
|
if (!this.selectedNode) return;
|
||||||
|
|
||||||
|
@ -441,17 +447,19 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onEditClick(event: MouseEvent, type: string, uuid: string): void {
|
onEditClick(event: MouseEvent, type: string, uuid: string): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const dialogRef = type !== NodeType.Client
|
const dialogRef = type !== NodeType.Client
|
||||||
? this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
? this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
||||||
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(() => {
|
dialogRef.afterClosed().subscribe(() => {
|
||||||
this.refreshData(this.selectedNode?.id);
|
this.refreshData(this.selectedNode?.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onRoomMap(room: TreeNode | null): void {
|
onRoomMap(room: TreeNode | null): void {
|
||||||
if (!room || !room['@id']) return;
|
if (!room || !room['@id']) return;
|
||||||
this.subscriptions.add(
|
this.subscriptions.add(
|
||||||
|
@ -469,6 +477,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
executeCommand(command: Command, selectedNode: TreeNode | null): void {
|
executeCommand(command: Command, selectedNode: TreeNode | null): void {
|
||||||
|
|
||||||
if (!selectedNode) {
|
if (!selectedNode) {
|
||||||
|
@ -479,11 +488,13 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onShowClientDetail(event: MouseEvent, client: Client): void {
|
onShowClientDetail(event: MouseEvent, client: Client): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.router.navigate(['clients', client.uuid], { state: { clientData: client } });
|
this.router.navigate(['clients', client.uuid], { state: { clientData: client } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onShowDetailsClick(event: MouseEvent, data: TreeNode | null): void {
|
onShowDetailsClick(event: MouseEvent, data: TreeNode | null): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (data && data.type !== NodeType.Client) {
|
if (data && data.type !== NodeType.Client) {
|
||||||
|
@ -495,10 +506,12 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
openBottomSheet(): void {
|
openBottomSheet(): void {
|
||||||
this.bottomSheet.open(LegendComponent);
|
this.bottomSheet.open(LegendComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
iniciarTour(): void {
|
iniciarTour(): void {
|
||||||
this.joyrideService.startTour({
|
this.joyrideService.startTour({
|
||||||
steps: ['groupsTitleStepText', 'filtersPanelStep', 'addStep', 'keyStep', 'tabsStep'],
|
steps: ['groupsTitleStepText', 'filtersPanelStep', 'addStep', 'keyStep', 'tabsStep'],
|
||||||
|
@ -507,9 +520,11 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
hasChild = (_: number, node: FlatNode): boolean => node.expandable;
|
hasChild = (_: number, node: FlatNode): boolean => node.expandable;
|
||||||
isLeafNode = (_: number, node: FlatNode): boolean => !node.expandable;
|
isLeafNode = (_: number, node: FlatNode): boolean => !node.expandable;
|
||||||
|
|
||||||
|
|
||||||
filterTree(searchTerm: string): void {
|
filterTree(searchTerm: string): void {
|
||||||
const expandPaths: TreeNode[][] = [];
|
const expandPaths: TreeNode[][] = [];
|
||||||
|
|
||||||
|
@ -545,6 +560,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private expandPath(path: TreeNode[]): void {
|
private expandPath(path: TreeNode[]): void {
|
||||||
path.forEach((pathNode) => {
|
path.forEach((pathNode) => {
|
||||||
const flatNode = this.treeControl.dataNodes?.find((n) => n.id === pathNode.id);
|
const flatNode = this.treeControl.dataNodes?.find((n) => n.id === pathNode.id);
|
||||||
|
@ -554,27 +570,32 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onTreeFilterInput(event: Event): void {
|
onTreeFilterInput(event: Event): void {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
const searchTerm = input?.value.trim() || '';
|
const searchTerm = input?.value.trim() || '';
|
||||||
this.filterTree(searchTerm);
|
this.filterTree(searchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onClientFilterInput(event: Event): void {
|
onClientFilterInput(event: Event): void {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
const searchTerm = input?.value || '';
|
const searchTerm = input?.value || '';
|
||||||
this.filterClients(searchTerm);
|
this.filterClients(searchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
filterClients(searchTerm: string): void {
|
filterClients(searchTerm: string): void {
|
||||||
this.searchTerm = searchTerm.trim().toLowerCase();
|
this.searchTerm = searchTerm.trim().toLowerCase();
|
||||||
this.selectedClients.filter = this.searchTerm;
|
this.selectedClients.filter = this.searchTerm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public setSelectedNode(node: TreeNode): void {
|
public setSelectedNode(node: TreeNode): void {
|
||||||
this.selectedNode = node;
|
this.selectedNode = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getStatus(client: Client, node: any): void {
|
getStatus(client: Client, node: any): void {
|
||||||
if (!client.uuid || !client['@id']) return;
|
if (!client.uuid || !client['@id']) return;
|
||||||
|
|
||||||
|
@ -599,12 +620,14 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
isAllSelected() {
|
isAllSelected() {
|
||||||
const numSelected = this.selection.selected.length;
|
const numSelected = this.selection.selected.length;
|
||||||
const numRows = this.selectedClients.data.length;
|
const numRows = this.selectedClients.data.length;
|
||||||
return numSelected === numRows;
|
return numSelected === numRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
toggleAllRows() {
|
toggleAllRows() {
|
||||||
if (this.isAllSelected()) {
|
if (this.isAllSelected()) {
|
||||||
this.selection.clear();
|
this.selection.clear();
|
||||||
|
@ -616,39 +639,44 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
this.arrayClients = [...this.selection.selected];
|
this.arrayClients = [...this.selection.selected];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
toggleRow(row: any) {
|
toggleRow(row: any) {
|
||||||
this.selection.toggle(row);
|
this.selection.toggle(row);
|
||||||
this.updateSelectedClients();
|
this.updateSelectedClients();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
updateSelectedClients() {
|
updateSelectedClients() {
|
||||||
this.arrayClients = [...this.selection.selected];
|
this.arrayClients = [...this.selection.selected];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getClientPath(client: Client): string {
|
getClientPath(client: Client): string {
|
||||||
const path: string[] = [];
|
const path: string[] = [];
|
||||||
let currentNode: TreeNode | null = this.findNodeByIdOrUuid(this.treeDataSource.data, client.organizationalUnit.uuid);
|
let currentNode: TreeNode | null = this.findNodeByIdOrUuid(this.treeDataSource.data, client.organizationalUnit.uuid);
|
||||||
|
|
||||||
while (currentNode) {
|
while (currentNode) {
|
||||||
path.unshift(currentNode.name);
|
path.unshift(currentNode.name);
|
||||||
currentNode = currentNode.id ? this.findParentNode(this.treeDataSource.data, currentNode.id) : null;
|
currentNode = currentNode.id ? this.findParentNode(this.treeDataSource.data, currentNode.id) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.join(' / ');
|
return path.join(' / ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private extractUuid(idPath: string | undefined): string | null {
|
private extractUuid(idPath: string | undefined): string | null {
|
||||||
return idPath ? idPath.split('/').pop() || null : null;
|
return idPath ? idPath.split('/').pop() || null : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
clearTreeSearch(inputElement: HTMLInputElement): void {
|
clearTreeSearch(inputElement: HTMLInputElement): void {
|
||||||
inputElement.value = '';
|
inputElement.value = '';
|
||||||
this.filterTree('');
|
this.filterTree('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
clearClientSearch(inputElement: HTMLInputElement): void {
|
clearClientSearch(inputElement: HTMLInputElement): void {
|
||||||
inputElement.value = '';
|
inputElement.value = '';
|
||||||
this.filterClients('');
|
this.filterClients('');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.2);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
Loading…
Reference in New Issue