From 3a7bff9e74f29c739c4b7a36cac23ba1886586b3 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Mon, 27 Jan 2025 14:23:28 +0100 Subject: [PATCH] refs #1373 Add search functionality for organizational units and update translations and tests --- .../components/groups/groups.component.html | 16 ++- .../groups/groups.component.spec.ts | 45 +++++-- .../app/components/groups/groups.component.ts | 121 +++++++++++------- ogWebconsole/src/locale/en.json | 2 +- ogWebconsole/src/locale/es.json | 2 +- 5 files changed, 119 insertions(+), 67 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index d609b1a..2e8ce09 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -29,21 +29,23 @@
+ + {{ 'searchTree' | translate }} + + {{ 'searchClient' | translate }} - - + + + +
diff --git a/ogWebconsole/src/app/components/groups/groups.component.spec.ts b/ogWebconsole/src/app/components/groups/groups.component.spec.ts index 60f8c01..96b59f4 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.spec.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.spec.ts @@ -24,6 +24,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { JoyrideModule } from 'ngx-joyride'; import { MatMenuModule } from '@angular/material/menu'; import { MatTreeModule } from '@angular/material/tree'; +import { TreeNode } from './model/model'; describe('GroupsComponent', () => { let component: GroupsComponent; @@ -80,21 +81,43 @@ describe('GroupsComponent', () => { expect(component.search).toHaveBeenCalled(); }); - it('should call getFilters on ngOnInit', () => { - spyOn(component, 'getFilters'); - component.ngOnInit(); - expect(component.getFilters).toHaveBeenCalled(); - }); - it('should call search method', () => { spyOn(component, 'search'); component.search(); expect(component.search).toHaveBeenCalled(); }); - it('should call getFilters method', () => { - spyOn(component, 'getFilters'); - component.getFilters(); - expect(component.getFilters).toHaveBeenCalled(); + it('should clear selection', () => { + spyOn(component, 'clearSelection'); + component.clearSelection(); + expect(component.clearSelection).toHaveBeenCalled(); }); -}); + + it('should toggle view', () => { + component.toggleView('card'); + expect(component.currentView).toBe('card'); + component.toggleView('list'); + expect(component.currentView).toBe('list'); + }); + + it('should filter tree', () => { + const searchTerm = 'test'; + spyOn(component, 'filterTree'); + component.filterTree(searchTerm); + expect(component.filterTree).toHaveBeenCalledWith(searchTerm); + }); + + it('should add multiple clients', () => { + spyOn(component, 'addMultipleClients'); + const event = new MouseEvent('click'); + component.addMultipleClients(event); + expect(component.addMultipleClients).toHaveBeenCalledWith(event); + }); + + it('should expand path to node', () => { + const node: TreeNode = { id: '1', name: 'Node 1', type: 'type', children: [] }; + spyOn(component, 'expandPathToNode'); + component.expandPathToNode(node); + expect(component.expandPathToNode).toHaveBeenCalledWith(node); + }); +}); \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 7779eba..4139e6f 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -109,7 +109,6 @@ export class GroupsComponent implements OnInit, OnDestroy { ngOnInit(): void { this.search(); - this.getFilters(); this.updateGridCols(); this.refreshData(); window.addEventListener('resize', this.updateGridCols); @@ -157,33 +156,35 @@ export class GroupsComponent implements OnInit, OnDestroy { this.selectedNode = null; } - getFilters(): void { - this.subscriptions.add( - this.dataService.getFilters().subscribe( - (data) => { - this.savedFilterNames = data.map((filter: { name: string; uuid: string; }) => [filter.name, filter.uuid]); - }, - (error) => { - console.error('Error fetching filters:', error); - } - ) - ); - } + // Función para obtener los filtros guardados actualmente deshabilitada + // getFilters(): void { + // this.subscriptions.add( + // this.dataService.getFilters().subscribe( + // (data) => { + // this.savedFilterNames = data.map((filter: { name: string; uuid: string; }) => [filter.name, filter.uuid]); + // }, + // (error) => { + // console.error('Error fetching filters:', error); + // } + // ) + // ); + // } - loadSelectedFilter(savedFilter: [string, string]): void { - this.subscriptions.add( - this.dataService.getFilter(savedFilter[1]).subscribe( - (response) => { - if (response) { - console.log('Filter:', response.filters); - } - }, - (error) => { - console.error('Error:', error); - } - ) - ); - } + // Función para cargar un filtro seleccionado actu + // loadSelectedFilter(savedFilter: [string, string]): void { + // this.subscriptions.add( + // this.dataService.getFilter(savedFilter[1]).subscribe( + // (response) => { + // if (response) { + // console.log('Filter:', response.filters); + // } + // }, + // (error) => { + // console.error('Error:', error); + // } + // ) + // ); + // } search(): void { this.loading = true; @@ -220,7 +221,8 @@ export class GroupsComponent implements OnInit, OnDestroy { this.dataService.getOrganizationalUnits().subscribe({ next: (data) => { - this.treeDataSource.data = data.map((unidad) => this.convertToTreeData(unidad)); + this.originalTreeData = data.map((unidad) => this.convertToTreeData(unidad)); + this.treeDataSource.data = [...this.originalTreeData]; if (selectedNodeIdOrUuid) { this.selectedNode = this.findNodeByIdOrUuid(this.treeDataSource.data, selectedNodeIdOrUuid); @@ -253,7 +255,7 @@ export class GroupsComponent implements OnInit, OnDestroy { }); } - private expandPathToNode(node: TreeNode): void { + public expandPathToNode(node: TreeNode): void { const path: TreeNode[] = []; let currentNode: TreeNode | null = node; @@ -543,29 +545,54 @@ export class GroupsComponent implements OnInit, OnDestroy { hasChild = (_: number, node: FlatNode): boolean => node.expandable; isLeafNode = (_: number, node: FlatNode): boolean => !node.expandable; - filterTree(searchTerm: string, filterType: string): void { - const filterNodes = (nodes: TreeNode[]): TreeNode[] => { - const filteredNodes: TreeNode[] = []; - for (const node of nodes) { - const matchesName = node.name.toLowerCase().includes(searchTerm.toLowerCase()); - const matchesType = filterType ? node.type.toLowerCase() === filterType.toLowerCase() : true; - const filteredChildren = node.children ? filterNodes(node.children) : []; - - if ((matchesName && matchesType) || filteredChildren.length > 0) { - filteredNodes.push({ ...node, children: filteredChildren }); - } - } - return filteredNodes; + filterTree(searchTerm: string): void { + const expandPaths: TreeNode[][] = []; + + const filterNodes = (nodes: TreeNode[], parentPath: TreeNode[] = []): TreeNode[] => { + return nodes + .map((node) => { + const matchesName = node.name.toLowerCase().includes(searchTerm.toLowerCase()); + const filteredChildren = node.children ? filterNodes(node.children, [...parentPath, node]) : []; + + if (matchesName) { + expandPaths.push([...parentPath, node]); + return { + ...node, + children: node.children, + } as TreeNode; + } else if (filteredChildren.length > 0) { + return { + ...node, + children: filteredChildren, + } as TreeNode; + } + return null; + }) + .filter((node): node is TreeNode => node !== null); }; - - const filteredData = filterNodes(this.originalTreeData); - this.treeDataSource.data = filteredData; + + if (!searchTerm) { + this.treeDataSource.data = [...this.originalTreeData]; + this.treeControl.collapseAll(); + } else { + this.treeDataSource.data = filterNodes(this.originalTreeData); + expandPaths.forEach((path) => this.expandPath(path)); + } + } + + private expandPath(path: TreeNode[]): void { + path.forEach((pathNode) => { + const flatNode = this.treeControl.dataNodes?.find((n) => n.id === pathNode.id); + if (flatNode) { + this.treeControl.expand(flatNode); + } + }); } onTreeFilterInput(event: Event): void { const input = event.target as HTMLInputElement; - const searchTerm = input?.value || ''; - this.filterTree(searchTerm, this.selectedTreeFilter); + const searchTerm = input?.value.trim() || ''; + this.filterTree(searchTerm); } onClientFilterInput(event: Event): void { diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index bb3166b..2f3e361 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -430,7 +430,7 @@ "addClientMenu": "Add client", "filters": "Filters", "searchClient": "Search client", - "searchTree": "Search in tree", + "searchTree": "Search organizational unit", "filterByType": "Filter by type", "all": "All", "classroomsGroup": "Classroom groups", diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 038289f..30e4b65 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -432,7 +432,7 @@ "addClientMenu": "Añadir cliente", "filters": "Filtros", "searchClient": "Buscar cliente", - "searchTree": "Buscar en árbol", + "searchTree": "Buscar unidad organizativa", "filterByType": "Filtrar por tipo", "all": "Todos", "classroomsGroup": "Grupos de aulas",