diff --git a/src/app/shared/components/paginator/paginator.component.html b/src/app/shared/components/paginator/paginator.component.html
new file mode 100644
index 000000000..d4fb096df
--- /dev/null
+++ b/src/app/shared/components/paginator/paginator.component.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/shared/components/paginator/paginator.component.scss b/src/app/shared/components/paginator/paginator.component.scss
new file mode 100644
index 000000000..f6a62f420
--- /dev/null
+++ b/src/app/shared/components/paginator/paginator.component.scss
@@ -0,0 +1,25 @@
+.page{
+ font-family: Innerspace;
+ font-style: normal;
+ font-weight: bold;
+ font-size: 13px;
+ line-height: 15px;
+ color: #444444;
+ pointer-events: none;
+ padding: 1rem;
+ min-width: 20px !important;
+
+ &-selected{
+ color: #3849F9;
+ }
+}
+.arrow, .page-selected, .page-unselected{
+ pointer-events: all;
+}
+.arrow:hover{
+ color: #3849F9;
+}
+.page-selected:hover, .page-unselected:hover, .arrow:hover{
+ cursor: pointer;
+ opacity: 0.5;
+}
diff --git a/src/app/shared/components/paginator/paginator.component.spec.ts b/src/app/shared/components/paginator/paginator.component.spec.ts
new file mode 100644
index 000000000..ac0437ba1
--- /dev/null
+++ b/src/app/shared/components/paginator/paginator.component.spec.ts
@@ -0,0 +1,35 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+
+import { PaginatorComponent } from './paginator.component';
+
+describe('PaginatorComponent', () => {
+ let component: PaginatorComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ MatButtonModule,
+ MatIconModule
+ ],
+ declarations: [PaginatorComponent]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PaginatorComponent);
+ component = fixture.componentInstance;
+ component.currentPage = {
+ element: 1,
+ isActive: true
+ }
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/components/paginator/paginator.component.ts b/src/app/shared/components/paginator/paginator.component.ts
new file mode 100644
index 000000000..58b21a6f1
--- /dev/null
+++ b/src/app/shared/components/paginator/paginator.component.ts
@@ -0,0 +1,169 @@
+import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
+import { Constants } from '../../constants/constants';
+import { PaginationElement } from '../../models/paginationElement.model';
+@Component({
+ selector: 'app-paginator',
+ templateUrl: './paginator.component.html',
+ styleUrls: ['./paginator.component.scss']
+})
+export class PaginatorComponent implements OnInit, OnChanges {
+
+ private readonly FIRST_PAGINATION_PAGE = 1;
+ private readonly MAX_PAGE_PAGINATOR_DISPLAY = 7;
+ private readonly PAGINATION_DOTS = '...';
+ private readonly PAGINATION_SHIFT_DELTA = 3;
+
+ @Input() currentPage: PaginationElement;
+ @Input() totalEntities: number;
+
+ carouselPageList: PaginationElement[] = [];
+ totalPageAmount: number;
+ size: number = Constants.WORKSHOPS_PER_PAGE;
+
+ @Output() pageChange = new EventEmitter();
+
+ constructor() { }
+
+ ngOnInit(): void {
+ this.totalPageAmount = this.getTotalPageAmount();
+
+ this.createPageList();
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (!changes.currentPage.isFirstChange()) {
+ const currentPage = this.carouselPageList.find((page: PaginationElement) => page.element === this.currentPage.element);
+ const isForward: boolean = this.checkIsForwardScrollDirection(changes);
+ const isRecreationAllowed: boolean = this.checkCarouseleRecreationIsAllowed(isForward, currentPage);
+
+ if (isRecreationAllowed) {
+ this.createPageList();
+ }
+ }
+ }
+
+ private checkIsForwardScrollDirection(changes: SimpleChanges): boolean {
+ return changes.currentPage.previousValue.element < changes.currentPage.currentValue.element;
+ }
+
+ private checkCarouseleRecreationIsAllowed(isForward: boolean, currentPage: PaginationElement): boolean {
+ if (isForward) {
+ return this.carouselPageList.indexOf(currentPage) >= this.PAGINATION_SHIFT_DELTA
+ } else {
+ return this.carouselPageList.indexOf(currentPage) <= this.PAGINATION_SHIFT_DELTA
+ }
+ }
+
+ private createPageList(): void {
+ this.carouselPageList = [];
+
+ let firstPage = +this.currentPage.element - this.PAGINATION_SHIFT_DELTA;
+ firstPage = firstPage < this.FIRST_PAGINATION_PAGE ? this.FIRST_PAGINATION_PAGE : firstPage;
+
+ let lastPage = +this.currentPage.element + this.PAGINATION_SHIFT_DELTA;
+ lastPage = lastPage > this.totalPageAmount ? this.totalPageAmount : lastPage;
+
+ let pageList = this.createDisplayedPageList(firstPage);
+
+ if (this.totalPageAmount < this.MAX_PAGE_PAGINATOR_DISPLAY) {
+ this.carouselPageList = pageList;
+ } else {
+ this.createCarouselPageList(pageList, pageList[0]?.element !== this.FIRST_PAGINATION_PAGE, true);
+ }
+
+ }
+
+ onPageChange(page: PaginationElement): void {
+ this.pageChange.emit(page);
+ }
+
+ onArroveClick(isForward: boolean): void {
+ let page: PaginationElement = {
+ element: '',
+ isActive: true,
+ }
+ if (isForward) {
+ page.element = +this.currentPage.element + 1;
+ } else {
+ page.element = +this.currentPage.element - 1;
+ }
+
+ this.pageChange.emit(page);
+ }
+
+ private getTotalPageAmount(): number {
+ return Math.ceil(this.totalEntities / this.size);
+ }
+
+ private createDisplayedPageList(startPage: number): PaginationElement[] {
+ let start: number;
+ let end: number;
+ if (this.totalPageAmount > this.MAX_PAGE_PAGINATOR_DISPLAY) {
+ const isMaxAmountFit = (startPage + this.MAX_PAGE_PAGINATOR_DISPLAY) < this.totalPageAmount;
+ start = (isMaxAmountFit) ? startPage : this.totalPageAmount - this.MAX_PAGE_PAGINATOR_DISPLAY;
+ end = this.MAX_PAGE_PAGINATOR_DISPLAY;
+ } else {
+ start = startPage;
+ end = this.totalPageAmount;
+ }
+
+ let pageList: PaginationElement[] = [];
+ while (pageList.length < end) {
+ pageList.push({
+ element: start,
+ isActive: true
+ });
+ start++;
+ }
+ return pageList;
+ }
+
+ private createCarouselPageList(pageList: PaginationElement[], isOnStart?: boolean, isOnEnd?: boolean) {
+ if (isOnStart) {
+ let start: PaginationElement[] = [
+ {
+ element: this.FIRST_PAGINATION_PAGE,
+ isActive: true
+ }
+ ];
+
+ if (pageList[0]?.element !== 2) {
+ start.push(
+ {
+ element: this.PAGINATION_DOTS,
+ isActive: false
+ })
+ }
+
+ this.carouselPageList = this.carouselPageList.concat(start);
+ };
+
+ if (pageList[0]?.element === 2) {
+ pageList.pop();
+ }
+
+ if (pageList[pageList.length - 1]?.element === this.totalPageAmount - 1) {
+ pageList.shift();
+ }
+
+ this.carouselPageList = this.carouselPageList.concat(pageList);
+
+
+ if (isOnEnd) {
+ let end: PaginationElement[] = [
+ {
+ element: this.totalPageAmount,
+ isActive: true
+ }
+ ];
+ if (pageList[pageList.length - 1]?.element !== this.totalPageAmount - 1) {
+ end.unshift({
+ element: this.PAGINATION_DOTS,
+ isActive: false
+ })
+ }
+ this.carouselPageList = this.carouselPageList.concat(end);
+ }
+ }
+
+}
diff --git a/src/app/shared/constants/constants.ts b/src/app/shared/constants/constants.ts
index 831a7a11a..4f044b566 100644
--- a/src/app/shared/constants/constants.ts
+++ b/src/app/shared/constants/constants.ts
@@ -13,6 +13,7 @@ export class Constants {
static readonly PHONE_LENGTH = 10;
static readonly PROVIDER_ENTITY_TYPE = 1;
static readonly WORKSHOP_ENTITY_TYPE = 2;
+ static readonly WORKSHOPS_PER_PAGE = 8;
static readonly RATE_ONE_STAR = 1;
static readonly RATE_TWO_STAR = 2;
diff --git a/src/app/shared/models/paginationElement.model.ts b/src/app/shared/models/paginationElement.model.ts
new file mode 100644
index 000000000..58e969897
--- /dev/null
+++ b/src/app/shared/models/paginationElement.model.ts
@@ -0,0 +1,4 @@
+export interface PaginationElement {
+ element: number | string,
+ isActive: boolean
+}
\ No newline at end of file
diff --git a/src/app/shared/services/workshops/app-workshop/app-workshops.service.ts b/src/app/shared/services/workshops/app-workshop/app-workshops.service.ts
index ebb799a64..5bd37c253 100644
--- a/src/app/shared/services/workshops/app-workshop/app-workshops.service.ts
+++ b/src/app/shared/services/workshops/app-workshop/app-workshops.service.ts
@@ -1,6 +1,7 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
+import { Constants } from 'src/app/shared/constants/constants';
import { Direction } from 'src/app/shared/models/category.model';
import { WorkshopCard, WorkshopFilterCard } from '../../../models/workshop.model';
@@ -57,6 +58,14 @@ export class AppWorkshopsService {
filters.directions.forEach((direction: Direction) => params = params.set('DirectionIds', direction.id.toString()));
}
+ if (filters.currentPage) {
+ const size: number = Constants.WORKSHOPS_PER_PAGE;
+ const from: number = size * (+filters.currentPage.element - 1);
+
+ params = params.set('Size', size.toString());
+ params = params.set('From', from.toString());
+ }
+
return params;
}
/**
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index d44c26993..97f98e1b4 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -42,6 +42,7 @@ import { FullSearchBarComponent } from './components/full-search-bar/full-search
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MessageBarComponent } from './components/message-bar/message-bar.component';
import { ShowTooltipIfTruncatedDirective } from './directives/show-tooltip-if-truncated.directive';
+import { PaginatorComponent } from './components/paginator/paginator.component';
import { StarsComponent } from './components/stars/stars.component';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatIconModule } from '@angular/material/icon';
@@ -84,6 +85,7 @@ import { FooterComponent } from '../footer/footer.component';
FullSearchBarComponent,
MessageBarComponent,
ShowTooltipIfTruncatedDirective,
+ PaginatorComponent,
StarsComponent,
FooterComponent,
],
@@ -136,6 +138,7 @@ import { FooterComponent } from '../footer/footer.component';
MessageBarComponent,
MatProgressBarModule,
ShowTooltipIfTruncatedDirective,
+ PaginatorComponent,
ReactiveFormsModule,
StarsComponent,
FooterComponent,
diff --git a/src/app/shared/store/filter.actions.ts b/src/app/shared/store/filter.actions.ts
index c3c73d1c7..c884156c0 100644
--- a/src/app/shared/store/filter.actions.ts
+++ b/src/app/shared/store/filter.actions.ts
@@ -1,5 +1,6 @@
import { Direction } from "../models/category.model";
import { City } from "../models/city.model";
+import { PaginationElement } from "../models/paginationElement.model";
import { WorkingHours } from "../models/workingHours.model";
export class SetCity {
static readonly type = '[app] Set City';
@@ -70,4 +71,8 @@ export class SetMinAge {
export class SetMaxAge {
static readonly type = '[filter] Set Max Age';
constructor(public payload: number) { }
+}
+export class PageChange {
+ static readonly type = '[filter] Change Page';
+ constructor(public payload: PaginationElement) { }
}
\ No newline at end of file
diff --git a/src/app/shared/store/filter.state.ts b/src/app/shared/store/filter.state.ts
index 4fdfde1ed..87b837528 100644
--- a/src/app/shared/store/filter.state.ts
+++ b/src/app/shared/store/filter.state.ts
@@ -22,8 +22,10 @@ import {
FilterChange,
SetMinAge,
SetMaxAge,
+ PageChange,
} from './filter.actions';
import { AppWorkshopsService } from '../services/workshops/app-workshop/app-workshops.service';
+import { PaginationElement } from '../models/paginationElement.model';
export interface FilterStateModel {
directions: Direction[];
maxAge: number;
@@ -38,10 +40,11 @@ export interface FilterStateModel {
city: City;
searchQuery: string;
order: string;
- filteredWorkshops: WorkshopCard[];
+ filteredWorkshops: WorkshopFilterCard;
topWorkshops: WorkshopCard[];
withDisabilityOption: boolean;
isLoading: boolean;
+ currentPage: PaginationElement
}
@State({
name: 'filter',
@@ -59,17 +62,21 @@ export interface FilterStateModel {
city: undefined,
searchQuery: '',
order: '',
- filteredWorkshops: [],
+ filteredWorkshops: undefined,
topWorkshops: [],
withDisabilityOption: false,
- isLoading: false
+ isLoading: false,
+ currentPage: {
+ element: 1,
+ isActive: true
+ }
}
})
@Injectable()
export class FilterState {
@Selector()
- static filteredWorkshops(state: FilterStateModel): WorkshopCard[] { return state.filteredWorkshops }
+ static filteredWorkshops(state: FilterStateModel): WorkshopFilterCard { return state.filteredWorkshops }
@Selector()
static topWorkshops(state: FilterStateModel): WorkshopCard[] { return state.topWorkshops }
@@ -161,7 +168,7 @@ export class FilterState {
return this.appWorkshopsService
.getFilteredWorkshops(state)
- .subscribe((filterResult: WorkshopFilterCard) => patchState(filterResult ? { filteredWorkshops: filterResult.entities, isLoading: false } : { filteredWorkshops: [], isLoading: false }),
+ .subscribe((filterResult: WorkshopFilterCard) => patchState(filterResult ? { filteredWorkshops: filterResult, isLoading: false } : { filteredWorkshops: undefined, isLoading: false }),
() => patchState({ isLoading: false }))
}
@@ -193,6 +200,12 @@ export class FilterState {
dispatch(new FilterChange());
}
+ @Action(PageChange)
+ pageChange({ patchState, dispatch }: StateContext, { payload }: PageChange) {
+ patchState({ currentPage: payload });
+ dispatch(new FilterChange());
+ }
+
@Action(FilterChange)
filterChange({ }: StateContext, { }: FilterChange) { }
}
diff --git a/src/app/shell/result/result.component.html b/src/app/shell/result/result.component.html
index 456040f3f..938fd32d9 100644
--- a/src/app/shell/result/result.component.html
+++ b/src/app/shell/result/result.component.html
@@ -3,7 +3,8 @@
Знайдено Х гуртків
-
@@ -18,7 +19,7 @@ Знайдено Х гуртків
@@ -29,7 +30,7 @@ Знайдено Х гуртків
\ No newline at end of file
diff --git a/src/app/shell/result/result.component.spec.ts b/src/app/shell/result/result.component.spec.ts
index d812809ab..4d3a87d18 100644
--- a/src/app/shell/result/result.component.spec.ts
+++ b/src/app/shell/result/result.component.spec.ts
@@ -77,5 +77,5 @@ class MockWorkshopCardsListComponent {
template: ''
})
class MockWorkshopMapViewListComponent {
- @Input() workshops: Workshop[];
+ @Input() filteredWorkshops: Workshop[];
}
diff --git a/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.html b/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.html
index d4396b92f..c2bf07697 100644
--- a/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.html
+++ b/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.html
@@ -1,18 +1,15 @@
-
+
+
+
-
+
За результатами пошуку нічого не знайдено
diff --git a/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.spec.ts b/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.spec.ts
index 956e64cb1..e133d1c6b 100644
--- a/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.spec.ts
+++ b/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.spec.ts
@@ -6,6 +6,8 @@ import { Component, Input } from '@angular/core';
import { NgxPaginationModule } from 'ngx-pagination';
import { Workshop } from '../../../shared/models/workshop.model';
import { User } from 'src/app/shared/models/user.model';
+import { PaginationElement } from 'src/app/shared/models/paginationElement.model';
+import { NgxsModule } from '@ngxs/store';
const MockUser = {
role: '',
@@ -20,12 +22,13 @@ describe('WorkshopCardsListComponentt', () => {
declarations: [
WorkshopCardsListComponent,
MockOrderingComponent,
- MockListWorkshopCardComponent
+ MockListWorkshopCardComponent,
+ MockListWorkshopCardPaginatorComponent
],
imports: [
FlexLayoutModule,
CommonModule,
- NgxPaginationModule
+ NgxsModule.forRoot([]),
],
})
.compileComponents();
@@ -58,3 +61,11 @@ class MockListWorkshopCardComponent {
@Input() isMainPage: boolean;
@Input() userRole: string;
}
+@Component({
+ selector: 'app-paginator',
+ template: ''
+})
+class MockListWorkshopCardPaginatorComponent {
+ @Input() totalEntities: number;
+ @Input() currentPage: PaginationElement;
+}
diff --git a/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.ts b/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.ts
index 27893f31d..c5a79e2e8 100644
--- a/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.ts
+++ b/src/app/shell/result/workshop-cards-list/workshop-cards-list.component.ts
@@ -1,5 +1,8 @@
-import { Component, Input, OnInit } from '@angular/core';
-import { Workshop, WorkshopCard } from '../../../shared/models/workshop.model';
+import { Component, Input, OnChanges, OnInit } from '@angular/core';
+import { Store } from '@ngxs/store';
+import { PaginationElement } from 'src/app/shared/models/paginationElement.model';
+import { PageChange } from 'src/app/shared/store/filter.actions';
+import { Workshop, WorkshopCard, WorkshopFilterCard } from '../../../shared/models/workshop.model';
@Component({
selector: 'app-workshop-cards-list',
@@ -8,10 +11,18 @@ import { Workshop, WorkshopCard } from '../../../shared/models/workshop.model';
})
export class WorkshopCardsListComponent implements OnInit {
- @Input() workshops: WorkshopCard[];
- currentPage: number = 1;
+ @Input() workshops: WorkshopFilterCard;
+ currentPage: PaginationElement = {
+ element: 1,
+ isActive: true
+ };
- constructor() { }
+ constructor(private store: Store) { }
ngOnInit(): void { }
+
+ onPageChange(page: PaginationElement): void {
+ this.currentPage = page;
+ this.store.dispatch(new PageChange(page));
+ }
}
diff --git a/src/app/shell/result/workshop-map-view-list/workshop-map-view-list.component.html b/src/app/shell/result/workshop-map-view-list/workshop-map-view-list.component.html
index e8977fc14..ee8cf5c4d 100644
--- a/src/app/shell/result/workshop-map-view-list/workshop-map-view-list.component.html
+++ b/src/app/shell/result/workshop-map-view-list/workshop-map-view-list.component.html
@@ -2,14 +2,13 @@
@@ -18,10 +17,11 @@
-
+
-
+
@@ -45,7 +45,8 @@ {{ selectedWorkshop.title }}

-
{{ selectedWorkshop.address.city }}, {{ selectedWorkshop.address.street }} {{ selectedWorkshop.address.buildingNumber }}
+
{{ selectedWorkshop.address.city }}, {{ selectedWorkshop.address.street }} {{
+ selectedWorkshop.address.buildingNumber }}