Skip to content

Commit 604aaab

Browse files
committed
End of section 9
1 parent f026a20 commit 604aaab

14 files changed

+348
-24
lines changed

client/src/app/app.component.html

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
<app-header></app-header>
22

33
<div class="container mt-6">
4-
<h1 class="text-3xl font-bold underline">Welcome to {{title}}</h1>
5-
<ul>
6-
@for (product of products; track product.id) {
7-
<li>{{product.name}}</li>
8-
}
9-
</ul>
4+
<app-shop></app-shop>
105
</div>

client/src/app/app.component.ts

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,14 @@
1-
import { Component, inject, OnInit } from '@angular/core';
1+
import { Component } from '@angular/core';
22
import { RouterOutlet } from '@angular/router';
33
import { HeaderComponent } from "./layout/header/header.component";
4-
import { HttpClient } from '@angular/common/http';
5-
import { Product } from './shared/models/product';
6-
import { Pagination } from './shared/models/pagination';
4+
import { ShopComponent } from "./features/shop/shop.component";
75

86
@Component({
97
selector: 'app-root',
10-
imports: [RouterOutlet, HeaderComponent],
8+
imports: [RouterOutlet, HeaderComponent, ShopComponent],
119
templateUrl: './app.component.html',
1210
styleUrl: './app.component.scss'
1311
})
14-
export class AppComponent implements OnInit {
15-
baseUrl = 'https://localhost:5001/api/'
16-
private http = inject(HttpClient);
12+
export class AppComponent {
1713
title = 'Skinet';
18-
products: Product[] = [];
19-
20-
ngOnInit(): void {
21-
this.http.get<Pagination<Product>>(this.baseUrl + 'products').subscribe({
22-
next: response => this.products = response.data,
23-
error: error => console.log(error),
24-
complete: () => console.log('complete')
25-
})
26-
}
2714
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { HttpClient, HttpParams } from '@angular/common/http';
2+
import { inject, Injectable } from '@angular/core';
3+
import { Pagination } from '../../shared/models/pagination';
4+
import { Product } from '../../shared/models/product';
5+
import { ShopParams } from '../../shared/models/shopParams';
6+
7+
@Injectable({
8+
providedIn: 'root'
9+
})
10+
export class ShopService {
11+
baseUrl = 'https://localhost:5001/api/'
12+
private http = inject(HttpClient);
13+
types: string[] = [];
14+
brands: string[] = [];
15+
16+
getProducts(shopParams: ShopParams) {
17+
let params = new HttpParams();
18+
19+
if (shopParams.brands.length > 0) {
20+
params = params.append('brands', shopParams.brands.join(','));
21+
}
22+
23+
if (shopParams.types.length > 0) {
24+
params = params.append('types', shopParams.types.join(','));
25+
}
26+
27+
if (shopParams.sort) {
28+
params = params.append('sort', shopParams.sort);
29+
}
30+
31+
if (shopParams.search) {
32+
params = params.append('search', shopParams.search);
33+
}
34+
35+
params = params.append('pageSize', shopParams.pageSize);
36+
params = params.append('pageIndex', shopParams.pageNumber);
37+
38+
return this.http.get<Pagination<Product>>(this.baseUrl + 'products', { params })
39+
}
40+
41+
getBrands() {
42+
if (this.brands.length > 0) return;
43+
44+
return this.http.get<string[]>(this.baseUrl + 'products/brands').subscribe({
45+
next: response => this.brands = response
46+
})
47+
}
48+
49+
getTypes() {
50+
if (this.types.length > 0) return;
51+
52+
return this.http.get<string[]>(this.baseUrl + 'products/types').subscribe({
53+
next: response => this.types = response
54+
})
55+
}
56+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<div>
2+
<h3 class="text-3xl text-center pt-6 mb-3">Filters</h3>
3+
<mat-divider></mat-divider>
4+
<div class="flex p-4">
5+
<div class="w-1/2">
6+
<h4 class="font-semibold text-xl text-primary">Brands</h4>
7+
<mat-selection-list [(ngModel)]="selectedBrands" [multiple]="true">
8+
@for (brand of shopService.brands; track $index) {
9+
<mat-list-option [value]="brand">{{brand}}</mat-list-option>
10+
}
11+
</mat-selection-list>
12+
</div>
13+
14+
<div class="w-1/2">
15+
<h4 class="font-semibold text-xl text-primary">Types</h4>
16+
<mat-selection-list [(ngModel)]="selectedTypes" [multiple]="true">
17+
@for (type of shopService.types; track $index) {
18+
<mat-list-option [value]="type">{{type}}</mat-list-option>
19+
}
20+
</mat-selection-list>
21+
</div>
22+
</div>
23+
24+
<div class="flex justify-end p-4">
25+
<button mat-flat-button (click)="applyFilters()">Apply Filters</button>
26+
</div>
27+
</div>

client/src/app/features/shop/filters-dialog/filters-dialog.component.scss

Whitespace-only changes.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Component, inject } from '@angular/core';
2+
import { ShopService } from '../../../core/services/shop.service';
3+
import { MatDivider } from '@angular/material/divider';
4+
import { MatListOption, MatSelectionList } from '@angular/material/list';
5+
import { MatButton } from '@angular/material/button';
6+
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
7+
import { FormsModule } from '@angular/forms';
8+
9+
@Component({
10+
selector: 'app-filters-dialog',
11+
imports: [
12+
MatDivider,
13+
MatSelectionList,
14+
MatListOption,
15+
MatButton,
16+
FormsModule
17+
],
18+
templateUrl: './filters-dialog.component.html',
19+
styleUrl: './filters-dialog.component.scss'
20+
})
21+
export class FiltersDialogComponent {
22+
shopService = inject(ShopService);
23+
private dialogRef = inject(MatDialogRef<FiltersDialogComponent>);
24+
data = inject(MAT_DIALOG_DATA);
25+
26+
selectedBrands: string[] = this.data.selectedBrands;
27+
selectedTypes: string[] = this.data.selectedTypes;
28+
29+
applyFilters() {
30+
this.dialogRef.close({
31+
selectedBrands: this.selectedBrands,
32+
selectedTypes: this.selectedTypes
33+
});
34+
}
35+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@if (product) {
2+
<mat-card appearance="raised">
3+
<img src="{{product.pictureUrl}}" alt="image of {{product.name}}">
4+
<mat-card-content class="mt-2">
5+
<h2 class="text-sm font-semibold uppercase">{{product.name}}</h2>
6+
<p class="font-light">{{product.price | currency}}</p>
7+
</mat-card-content>
8+
<mat-card-actions>
9+
<button mat-stroked-button class="w-full">
10+
<mat-icon>add_shopping_cart</mat-icon>
11+
Add to cart
12+
</button>
13+
</mat-card-actions>
14+
</mat-card>
15+
}

client/src/app/features/shop/product-item/product-item.component.scss

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Component, Input } from '@angular/core';
2+
import { Product } from '../../../shared/models/product';
3+
import { MatCard, MatCardActions, MatCardContent } from '@angular/material/card';
4+
import { CurrencyPipe } from '@angular/common';
5+
import { MatButton } from '@angular/material/button';
6+
import { MatIcon } from '@angular/material/icon';
7+
8+
@Component({
9+
selector: 'app-product-item',
10+
imports: [
11+
MatCard,
12+
MatCardContent,
13+
CurrencyPipe,
14+
MatCardActions,
15+
MatButton,
16+
MatIcon
17+
],
18+
templateUrl: './product-item.component.html',
19+
styleUrl: './product-item.component.scss'
20+
})
21+
export class ProductItemComponent {
22+
@Input() product?: Product
23+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<div class="flex flex-col gap-3">
2+
<div class="flex justify-between">
3+
<mat-paginator
4+
class="!bg-white"
5+
(page)="handlePageEvent($event)"
6+
[length]="products?.count"
7+
[pageSize]="shopParams.pageSize"
8+
[showFirstLastButtons]="true"
9+
[pageSizeOptions]="pageSizeOptions"
10+
[pageIndex]="shopParams.pageNumber-1"
11+
aria-label="Select page"
12+
>
13+
</mat-paginator>
14+
15+
<form
16+
#searchForm="ngForm"
17+
(ngSubmit)="onSearchChange()"
18+
class="relative flex items-center w-full max-w-md mx-4"
19+
>
20+
<input
21+
type="text"
22+
class="block w-full p-4 text-sm text-gray-900 border border-gray-300 rounded-lg"
23+
placeholder="Search"
24+
name="search"
25+
[(ngModel)]="shopParams.search"
26+
/>
27+
28+
<button
29+
mat-icon-button type="submit"
30+
class="absolute inset-y-0 right-8 top-2 flex items-center pl-3 custom-search-button"
31+
>
32+
<mat-icon>search</mat-icon>
33+
</button>
34+
</form>
35+
36+
<div class="flex gap-3">
37+
<button class="match-input-height" mat-stroked-button (click)="openFiltersDialog()">
38+
<mat-icon>filter_list</mat-icon>
39+
Filters
40+
</button>
41+
42+
<button class="match-input-height" mat-stroked-button [matMenuTriggerFor]="sortMenu">
43+
<mat-icon>swap_vert</mat-icon>
44+
Sort
45+
</button>
46+
</div>
47+
</div>
48+
49+
<div class="grid grid-cols-5 gap-4">
50+
@for (product of products?.data; track product.id) {
51+
<app-product-item [product]="product"></app-product-item>
52+
}
53+
</div>
54+
</div>
55+
56+
<mat-menu #sortMenu="matMenu">
57+
<mat-selection-list [multiple]="false" (selectionChange)="onSortChange($event)">
58+
@for (sort of sortOptions; track $index) {
59+
<mat-list-option [value]="sort.value" [selected]="sort.value===shopParams.sort">
60+
{{sort.name}}
61+
</mat-list-option>
62+
}
63+
</mat-selection-list>
64+
</mat-menu>

0 commit comments

Comments
 (0)