Added Hooks

This commit is contained in:
Charles Showalter 2022-05-13 13:26:38 -07:00
parent df23d64061
commit bb8ea8cfae
19 changed files with 286 additions and 177 deletions

View File

@ -39,4 +39,4 @@
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.6.2"
}
}
}

View File

@ -0,0 +1,11 @@
<pagination
[boundaryLinks]="true"
[totalItems]="totalCount"
(pageChanged)="onPagerChange($event)"
[itemsPerPage]="pageSize"
previousText="&lsaquo;"
nextText="&rsaquo;"
firstText="&laquo;"
lastText="&raquo;"
>
</pagination>

View File

@ -0,0 +1,22 @@
import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
@Component({
selector: 'app-pager',
templateUrl: './pager.component.html',
styleUrls: ['./pager.component.scss']
})
export class PagerComponent implements OnInit {
@Input() totalCount: number;
@Input() pageSize: number;
@Output() pageChanged = new EventEmitter<number>();
constructor() { }
ngOnInit(): void {
}
onPagerChange(e: any){
this.pageChanged.emit(e.page);
}
}

View File

@ -0,0 +1,10 @@
<header>
<span *ngIf="totalCount && totalCount > 0">
Showing
<strong>
{{(pageNumber-1) * pageSize+1}} -
{{pageNumber*pageSize > this.totalCount ? this.totalCount : pageNumber*pageSize}}
</strong> of <strong>{{totalCount}}</strong> Results
</span>
<span *ngIf="totalCount === 0">There are <strong>0 </strong>results for this filter.</span>
</header>

View File

@ -0,0 +1,18 @@
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-paging-header',
templateUrl: './paging-header.component.html',
styleUrls: ['./paging-header.component.scss']
})
export class PagingHeaderComponent implements OnInit {
@Input() pageNumber: number;
@Input() pageSize: number;
@Input() totalCount: number;
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,4 @@
export interface IBrand {
id: number;
name: string;
}

View File

@ -0,0 +1,4 @@
export interface IType {
id: number;
name: string;
}

View File

@ -0,0 +1,8 @@
export class ShopParams {
brandId: number = 0;
typeId: number = 0;
sort = 'name';
pageNumber: number = 1;
pageSize: number = 6;
search: string;
}

View File

@ -1,12 +1,24 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PaginationModule } from 'ngx-bootstrap/pagination';
import { PagingHeaderComponent } from './components/paging-header/paging-header.component';
import { PagerComponent } from './components/pager/pager.component';
@NgModule({
declarations: [],
declarations: [
PagingHeaderComponent,
PagerComponent
],
imports: [
CommonModule
CommonModule,
PaginationModule.forRoot()
],
exports: [
PaginationModule,
PagingHeaderComponent,
PagerComponent
]
})
export class SharedModule { }

View File

@ -0,0 +1,13 @@
<div class="card h-100 shadow-sm">
<img src="{{product.pictureUrl}}" alt="{{product.name}}" class="img-fluid bg-light">
<div class="card-body d-flex flex-column">
<a href="">
<h6 class="text-uppercase">{{product.name}}</h6>
</a>
<span class="mb-2">{{product.price | currency}}</span>
<div class="btn-group mt-auto">
<button type="button" class="btn btn-sm btn-outline-secondary fa fa-shopping-cart me-2"></button>
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
import { Component, Input, OnInit } from '@angular/core';
import { IProduct } from 'src/app/shared/models/product';
@Component({
selector: 'app-product-item',
templateUrl: './product-item.component.html',
styleUrls: ['./product-item.component.scss']
})
export class ProductItemComponent implements OnInit {
@Input() product: IProduct
constructor() { }
ngOnInit(): void {
}
}

View File

@ -2,192 +2,62 @@
<div class="row">
<section class="col-3">
<h5 class="text-warning ml-3">Sort</h5>
<select class="custom-select mb-3" style="width: 100%;">
<option selected>Name</option>
<option>Price: Low to High</option>
<option>Price: High to Low</option>
<select class="form-select mb-3" style="width: 100%;" (change)="onSortSelected($event.target.value)">
<option *ngFor="let sort of sortOptions"
[value]="sort.value"
>
{{sort.name}}
</option>
</select>
<h5 class="text-warning ml-3">Brands</h5>
<ul class="list-group my-3">
<li class="list-group-item active" aria-current="true">An active item</li>
<li class="list-group-item">A second item</li>
<li class="list-group-item">A third item</li>
<li class="list-group-item">A fourth item</li>
<li class="list-group-item">And a fifth one</li>
<li class="list-group-item"
*ngFor="let brand of brands"
[class.active]="brand.id === this.shopParams.brandId"
[value]="brand.id"
(click)="onBrandSelected(brand.id)"
>{{brand.name}}</li>
</ul>
<h5 class="text-warning ml-3">Type</h5>
<ul class="list-group my-3">
<li class="list-group-item active" aria-current="true">An active item</li>
<li class="list-group-item">A second item</li>
<li class="list-group-item">A third item</li>
<li class="list-group-item">A fourth item</li>
<li class="list-group-item">And a fifth one</li>
<li class="list-group-item"
*ngFor="let type of productType"
[class.active]="type.id === this.shopParams.typeId"
[value]="type.id"
(click)="onTypeSelected(type.id)"
>{{type.name}}</li>
</ul>
</section>
<section class="col-9">
<div class="d-flex justify-content-between align-items-center pb-2">
<header>
<span>Showing <strong>10</strong> of <strong>18</strong> Results</span>
</header>
<app-paging-header
[totalCount]="totalCount"
[pageSize]="this.shopParams.pageSize"
[pageNumber]="this.shopParams.pageNumber"
></app-paging-header>
<div class="row g-2 align-items-center">
<div class="col-sm">
<input style="width:300px" placeholder="Search" type="text" class="form-control">
<input (keyup.enter)="onSearch()" style="width:300px" #search placeholder="Search" type="text" class="form-control">
</div>
<div class="col-sm">
<button class="btn btn-outline-primary my-3 me-2">Search</button>
<button class="btn btn-outline-success my-3">Reset</button>
<button (click)="onSearch()" class="btn btn-outline-primary my-3 me-2">Search</button>
<button (click)="OnReset()" class="btn btn-outline-success my-3">Reset</button>
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
<div class="col">
<div class="card shadow-sm">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#43484b;"></rect><text x="50%" y="50%" fill="#eceeef" dy=".3em" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#dddad6;">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card shadow-sm">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#43484b;"></rect><text x="50%" y="50%" fill="#eceeef" dy=".3em" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#dddad6;">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card shadow-sm">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#43484b;"></rect><text x="50%" y="50%" fill="#eceeef" dy=".3em" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#dddad6;">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card shadow-sm">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#43484b;"></rect><text x="50%" y="50%" fill="#eceeef" dy=".3em" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#dddad6;">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card shadow-sm">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#43484b;"></rect><text x="50%" y="50%" fill="#eceeef" dy=".3em" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#dddad6;">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card shadow-sm">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#43484b;"></rect><text x="50%" y="50%" fill="#eceeef" dy=".3em" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#dddad6;">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card shadow-sm">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#43484b;"></rect><text x="50%" y="50%" fill="#eceeef" dy=".3em" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#dddad6;">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card shadow-sm">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#43484b;"></rect><text x="50%" y="50%" fill="#eceeef" dy=".3em" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#dddad6;">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card shadow-sm">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#43484b;"></rect><text x="50%" y="50%" fill="#eceeef" dy=".3em" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#dddad6;">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
<div class="col mb-3" *ngFor="let item of products">
<app-product-item [product]="item"></app-product-item>
</div>
</div>
<div class="d-flex justify-content-center" *ngIf="totalCount > 0">
<app-pager
[pageSize]="shopParams.pageSize"
[totalCount]="totalCount"
(pageChanged)="onPageChange($event)"
></app-pager>
</div>
</section>
</div>
</div>

View File

@ -1,5 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { IBrand } from '../shared/models/brand';
import { IProduct } from '../shared/models/product';
import { IType } from '../shared/models/producttype';
import { ShopParams } from '../shared/models/shopparams';
import { ShopService } from './shop.service';
@Component({
@ -8,19 +11,95 @@ import { ShopService } from './shop.service';
styleUrls: ['./shop.component.scss']
})
export class ShopComponent implements OnInit {
@ViewChild('search', {static: true}) searchTerm: ElementRef;
products: IProduct[];
brands: IBrand[];
productType: IType[];
shopParams = new ShopParams();
totalCount: number;
sortOptions = [
{name: 'Name', value: 'name'},
{name: 'Price: Low to High', value: 'priceAsc'},
{name: 'Price: High to Low', value: 'priceDesc'}
];
constructor(private shopService: ShopService) { }
ngOnInit(): void {
this.shopService.getProducts().subscribe(
this.getProducts();
this.getBrands();
this.getTypes();
}
getProducts(){
this.shopService.getProducts(this.shopParams).subscribe(
{
next: (response) => { this.products = response.data; },
next: (response) => {
this.products = response.data;
this.shopParams.pageNumber = response.pageIndex;
this.shopParams.pageSize = response.pageSize;
this.totalCount = response.count;
},
error: (e: any) => { console.log(e); },
complete: () => { console.log('complete'); }
}
);
}
getBrands(){
this.shopService.getBrands().subscribe(
{
next: (response) => { this.brands = [{id: 0, name: 'All'}, ...response]; },
error: (e: any) => { console.log(e); },
complete: () => { console.log('complete'); }
}
);
}
getTypes(){
this.shopService.getTypes().subscribe(
{
next: (response) => { this.productType = [{id: 0, name: 'All'}, ...response]; },
error: (e: any) => { console.log(e); },
complete: () => { console.log('complete'); }
}
);
}
onBrandSelected(brandId: number){
this.shopParams.brandId = brandId;
this.shopParams.pageNumber = 1;
this.getProducts();
}
onTypeSelected(typeId: number){
this.shopParams.typeId = typeId;
this.shopParams.pageNumber = 1;
this.getProducts();
}
onSortSelected(sort: string){
this.shopParams.sort = sort;
this.getProducts();
}
onPageChange(e: any){
if (this.shopParams.pageNumber !== e){
this.shopParams.pageNumber = e;
this.getProducts();
}
}
onSearch(){
this.shopParams.search = this.searchTerm.nativeElement.value;
this.shopParams.pageNumber = 1;
this.getProducts();
}
OnReset(){
this.searchTerm.nativeElement.value = '';
this.shopParams = new ShopParams();
this.getProducts();
}
}

View File

@ -1,15 +1,19 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ShopComponent } from './shop.component';
import { ProductItemComponent } from './product-item/product-item.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [
ShopComponent
ShopComponent,
ProductItemComponent
],
imports: [
CommonModule
CommonModule,
SharedModule
],
exports: [ShopComponent]
})

View File

@ -1,6 +1,10 @@
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { IBrand } from '../shared/models/brand';
import { IPagination } from '../shared/models/pagination';
import { IType } from '../shared/models/producttype';
import { map } from 'rxjs/operators'
import { ShopParams } from '../shared/models/shopparams';
@Injectable({
providedIn: 'root'
@ -10,7 +14,38 @@ export class ShopService {
constructor(private http: HttpClient) { }
getProducts(){
return this.http.get<IPagination>(this.baseURL + 'products');
getProducts(shopParams: ShopParams){
let params = new HttpParams();
if (shopParams.brandId !== 0){
params = params.append('brandId', shopParams.brandId.toString());
}
if(shopParams.typeId !== 0){
params = params.append('typeId', shopParams.typeId.toString());
}
if (shopParams.search){
params = params.append('search', shopParams.search);
}
params = params.append('sort', shopParams.sort);
params = params.append('pageIndex', shopParams.pageNumber.toString());
params = params.append('pageIndex', shopParams.pageSize.toString());
return this.http.get<IPagination>(this.baseURL + 'products', {observe: 'response', params})
.pipe(
map(response => {
return response.body;
})
);
}
getBrands(){
return this.http.get<IBrand[]>(this.baseURL + 'products/brands');
}
getTypes(){
return this.http.get<IType[]>(this.baseURL + 'products/types');
}
}

View File

@ -18,6 +18,8 @@
]
},
"angularCompilerOptions": {
"strictTemplates": true,
"strictDomEventTypes": false,
"enableI18nLegacyMessageIdFormat": false
}
}