Skip to content

Commit b985e6d

Browse files
committed
Adds new catalog filters
1 parent 6c7f85b commit b985e6d

File tree

3 files changed

+195
-30
lines changed

3 files changed

+195
-30
lines changed

frontend/projects/ui/src/app/catalog/catalog.component.html

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,59 @@
11
<ui5-card class="medium">
2-
<ui5-card-header slot="header" title-text="Catalog">
3-
</ui5-card-header>
42
<div class="card-content">
5-
<div class="flex-row samples-margin catalog-filters">
6-
<div class="catalog-filter">
7-
<ui5-label>Category</ui5-label><br>
8-
<ui5-multi-combobox placeholder="Type your value" (selection-change)="onCategoriesChange($event)">
9-
<ui5-mcb-item *ngFor="let cat of categories" text="{{cat}}"></ui5-mcb-item>
10-
</ui5-multi-combobox>
3+
@if (categoryTree && categoryTree.length > 0) {
4+
<div class="filter-headline">
5+
<div class="filter-title">
6+
<ui5-card-header slot="header" title-text="Catalog">
7+
</ui5-card-header>
8+
<span>
9+
(
10+
<a href="#category-tree" (click)="toggleFilterVisibility($event)">{{filtersVisible ? 'hide' : 'show'}} filters</a>
11+
@if (selectedCategories.length || selectedProviders.length) {
12+
/ <a href="#category-tree" (click)="clearAllFilters($event)">clear filters</a>
13+
}
14+
)
15+
</span>
16+
</div>
17+
<div class="filter-search">
18+
<div class="catalog-filter">
19+
<ui5-input id="searchInput" placeholder="Enter search criteria ...">
20+
<ui5-icon id="searchIcon" slot="icon" name="search"></ui5-icon>
21+
</ui5-input>
22+
</div>
23+
</div>
1124
</div>
12-
<div class="catalog-filter">
13-
<ui5-label>Provider</ui5-label><br>
14-
<ui5-multi-combobox placeholder="Type your value" (selection-change)="onProvidersChange($event)">
15-
<ui5-mcb-item *ngFor="let provider of providers" text="{{provider}}"></ui5-mcb-item>
16-
</ui5-multi-combobox>
25+
<div id="category-tree" class="flex-row samples-margin catalog-categories" [class.is-visible]="filtersVisible">
26+
<ui5-card *ngFor="let cat of categoryTree" class="small category-card">
27+
<div class="category-label">
28+
<ui5-checkbox text="{{cat.name}}"
29+
(change)="onCategoryChange($event, cat.name)">
30+
</ui5-checkbox>
31+
</div>
32+
<ul class="category-list">
33+
<li *ngFor="let provider of cat.providers" class="category-list-item">
34+
<ui5-checkbox text="{{provider}}"
35+
(change)="onProviderChange($event, provider)">
36+
</ui5-checkbox>
37+
</li>
38+
</ul>
39+
</ui5-card>
1740
</div>
18-
<div class="catalog-filter">
19-
<br>
20-
<ui5-input id="searchInput" placeholder="Enter search criteria ...">
21-
<ui5-icon id="searchIcon" slot="icon" name="search"></ui5-icon>
22-
</ui5-input>
41+
}
42+
@else {
43+
<div class="filter-headline">
44+
<div class="filter-title">
45+
<ui5-card-header slot="header" title-text="Catalog">
46+
</ui5-card-header>
47+
</div>
48+
<div class="filter-search">
49+
<div class="catalog-filter">
50+
<ui5-input id="searchInput" placeholder="Enter search criteria ...">
51+
<ui5-icon id="searchIcon" slot="icon" name="search"></ui5-icon>
52+
</ui5-input>
53+
</div>
54+
</div>
2355
</div>
24-
</div>
56+
}
2557
</div>
2658
</ui5-card>
2759
@if (fetchFinalized && filteredItems().length === 0) {

frontend/projects/ui/src/app/catalog/catalog.component.scss

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,86 @@
2929
display: flex;
3030
justify-content: center;
3131
padding: 40px 0;
32-
}
32+
}
33+
34+
.filter-headline {
35+
display: flex;
36+
align-items: center;
37+
justify-content: space-between;
38+
padding: 10px 15px 10px 0;
39+
gap: 2rem;
40+
}
41+
42+
.filter-title {
43+
display: flex;
44+
align-items: center;
45+
46+
span {
47+
position: relative;
48+
top: 2px;
49+
}
50+
}
51+
52+
.catalog-categories {
53+
display: none;
54+
padding-bottom: 1.5rem;
55+
justify-content: flex-start;
56+
column-gap: 1.3%;
57+
row-gap: .5rem;
58+
flex-wrap: wrap;
59+
60+
&.is-visible {
61+
display: flex;
62+
}
63+
64+
ui5-card::part(root) {
65+
box-shadow: none;
66+
}
67+
68+
.category-card {
69+
width: 100%;
70+
71+
.category-label {
72+
border-bottom: 1px solid #eee;
73+
padding: .5rem 0 0;
74+
75+
ui5-checkbox {
76+
display: block;
77+
}
78+
79+
ui5-checkbox::part(label) {
80+
font-size: 1.2em;
81+
font-weight: bold;
82+
}
83+
}
84+
85+
.category-list {
86+
margin: 0;
87+
padding: 0 0 .5rem;
88+
list-style: none;
89+
width: auto;
90+
}
91+
92+
.category-list-item {
93+
border: none;
94+
}
95+
}
96+
97+
@media only screen and (min-width: 600px) {
98+
.category-card {
99+
width: 49%;
100+
}
101+
}
102+
103+
@media only screen and (min-width: 800px) {
104+
.category-card {
105+
width: 32%;
106+
}
107+
}
108+
109+
@media only screen and (min-width: 1200px) {
110+
.category-card {
111+
width: 24%;
112+
}
113+
}
114+
}

frontend/projects/ui/src/app/catalog/catalog.component.ts

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import { CommonModule } from '@angular/common';
2-
import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal, WritableSignal } from '@angular/core';
2+
import {
3+
ChangeDetectionStrategy,
4+
Component,
5+
DestroyRef,
6+
inject,
7+
OnInit,
8+
QueryList,
9+
signal,
10+
ViewChildren,
11+
WritableSignal
12+
} from '@angular/core';
313
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
414
import { LuigiContextService } from '@luigi-project/client-support-angular';
5-
import { Ui5WebcomponentsModule } from '@ui5/webcomponents-ngx';
15+
import { CheckBoxComponent, Ui5WebcomponentsModule } from '@ui5/webcomponents-ngx';
616
import { IconComponent } from '@ui5/webcomponents-ngx/main/icon';
717
import { finalize } from 'rxjs/operators';
818
import { LOCAL_STORAGE_CATALOG_KEY } from '../app.constants';
@@ -20,14 +30,17 @@ import { CatalogCardListComponent } from './catalog-card-list/catalog-card-list.
2030
changeDetection: ChangeDetectionStrategy.OnPush
2131
})
2232
export class CatalogComponent implements OnInit {
33+
@ViewChildren(CheckBoxComponent) checkBoxes!: QueryList<CheckBoxComponent>;
2334
context?: CatalogContext;
2435
fetchFinalized = false;
36+
filtersVisible = false;
2537
items: WritableSignal<ExtensionClass[]> = signal([]);
2638
categories: Set<string> = new Set();
2739
providers: Set<string> = new Set();
2840
selectedCategories: string[] = [];
2941
selectedProviders: string[] = [];
3042
filteredItems: WritableSignal<ExtensionClass[]> = signal([]);
43+
categoryTree!: any[];
3144
private destroyRef = inject(DestroyRef);
3245

3346
constructor(private dataService: CatalogDataService, private luigiContextService: LuigiContextService) { }
@@ -38,13 +51,39 @@ export class CatalogComponent implements OnInit {
3851
});
3952
}
4053

41-
public onCategoriesChange(e: any) {
42-
this.selectedCategories = this.updateSelection(e);
54+
public toggleFilterVisibility(event: Event) {
55+
event.preventDefault();
56+
57+
this.filtersVisible = !this.filtersVisible;
58+
}
59+
60+
public clearAllFilters(event: Event) {
61+
event.preventDefault();
62+
this.checkBoxes?.toArray().forEach(item => item.checked = false);
63+
64+
this.selectedCategories.length = 0;
65+
this.selectedProviders.length = 0;
66+
4367
this.filterItems();
4468
}
4569

46-
public onProvidersChange(e: any) {
47-
this.selectedProviders = this.updateSelection(e);
70+
public onCategoryChange(event: any, category: string) {
71+
if (event.target._state.checked && !this.selectedCategories.includes(category)) {
72+
this.selectedCategories.push(category);
73+
} else {
74+
this.selectedCategories = this.selectedCategories.filter(item => item !== category);
75+
}
76+
77+
this.filterItems();
78+
}
79+
80+
public onProviderChange(event: any, provider: string) {
81+
if (event.target._state.checked && !this.selectedProviders.includes(provider)) {
82+
this.selectedProviders.push(provider);
83+
} else {
84+
this.selectedProviders = this.selectedProviders.filter(item => item !== provider);
85+
}
86+
4887
this.filterItems();
4988
}
5089

@@ -94,8 +133,22 @@ export class CatalogComponent implements OnInit {
94133
private updateCatalogItems(account: string, data: ExtensionClass[]) {
95134
const storageKey = account ? `${LOCAL_STORAGE_CATALOG_KEY}-${account}` : LOCAL_STORAGE_CATALOG_KEY;
96135
const globalStorage: string[] = JSON.parse(localStorage.getItem(storageKey) || '[]');
136+
const treeData: Record<string, any> = {};
97137

98138
data?.forEach(item => {
139+
const cat = item.category?.toLowerCase().replaceAll(' &', '').replaceAll(' ', '-');
140+
141+
if (cat) {
142+
if (!treeData[cat]) {
143+
treeData[cat] = {
144+
name: item.category,
145+
providers: new Set()
146+
}
147+
}
148+
149+
treeData[cat].providers.add(item.provider);
150+
}
151+
99152
item.category && this.categories.add(item.category);
100153
item.provider && this.providers.add(item.provider);
101154

@@ -109,11 +162,9 @@ export class CatalogComponent implements OnInit {
109162
}
110163
});
111164

165+
this.categoryTree = treeData ? Object.values(treeData) : [];
166+
112167
this.items.set(data);
113168
this.filterItems();
114169
}
115-
116-
private updateSelection(e: any): string[] {
117-
return e.detail.items.map((item: any) => item._state.text);
118-
}
119170
}

0 commit comments

Comments
 (0)