Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: player search #13

Merged
merged 2 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion apps/dashboard/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { NgModule } from '@angular/core';
import { NgModule, isDevMode } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { StoreModule } from '@ngrx/store';
import { StoreRouterConnectingModule, routerReducer } from '@ngrx/router-store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './components/app/app.component';
Expand All @@ -23,6 +26,16 @@ const COMPONENTS = [
BrowserAnimationsModule,
FontAwesomeModule,
AppRoutingModule,
StoreModule.forRoot({
router: routerReducer,
}),
StoreRouterConnectingModule.forRoot(),
StoreDevtoolsModule.instrument({
maxAge: 25,
logOnly: !isDevMode(),
autoPause: true,
trace: false,
}),
],
providers: [{
provide: ENV_CONFIG,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class PlayerModalStore extends ComponentStore<PlayerModalState> {

public readonly getPlayers = this.effect<number>(page$ => page$.pipe(
switchMap(page => forkJoin([
this.playersService.find(page),
this.playersService.find({ page }),
this.playersService.count(),
]).pipe(
tapResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ <h1 class="text-white uppercase text-4xl px-10">Players</h1>
<a class="text-black bg-white bg-opacity-30 hover:bg-opacity-90 uppercase rounded py-2.5 px-5 text-xs whitespace-nowrap" routerLink="./create">Create Player</a>
<div class="flex flex-col w-full items-end px-10">
<div class="flex flex-row flex-grow w-full justify-end gap-10">
<dashboard-search class="flex flex-col flex-grow w-full max-w-sm"></dashboard-search>
<dashboard-search class="flex flex-col flex-grow w-full max-w-sm" [search]="vm.search" (onSearch)="onSearch($event)"></dashboard-search>
<dashboard-pagination class="flex flex-col" [page]="vm.page" [count]="vm.count" [perPage]="perPage" (pageChange)="onPageChange($event)"></dashboard-pagination>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';

import { PageEvent } from '@dashboard/components/pagination';
import { PlayersListStore } from './players-list.store';
import { SearchEvent } from '@dashboard/components/search';

@Component({
selector: 'pool-overlay-players-list-page',
Expand All @@ -19,13 +20,24 @@ export class PlayersListPageComponent {
) { }

public onPageChange({ page }: PageEvent): void {
const newPage = page > 1 ? page : undefined;

this.router.navigate(['.'], {
relativeTo: this.activatedRoute,
queryParams: { page },
queryParams: { page: newPage },
queryParamsHandling: 'merge',
});
}

public onSearch({ search }: SearchEvent): void {
const newSearch = search?.length ? search : undefined;

this.router.navigate(['.'], {
relativeTo: this.activatedRoute,
queryParams: { search: newSearch },
});
}

public deletePlayerById({ playerId }: { playerId: number }): void {
this.playersListStore.deletePlayerById(playerId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,45 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Store, createSelector } from '@ngrx/store';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { forkJoin } from 'rxjs';
import { combineLatest, forkJoin } from 'rxjs';
import { concatMap, map, switchMap, takeUntil } from 'rxjs/operators';

import { IPlayer } from '@pool-overlay/models';
import { PlayersService } from '../../../shared/services/players.service';
import { PlayersService, PlayerFindOptions } from '../../../shared/services/players.service';
import { selectQueryParam } from '../../../shared/utils/router.selectors';

export interface PlayersListState {
loaded: boolean;
page: number;
count: number;
players: IPlayer[];
}

@Injectable()
export class PlayersListStore extends ComponentStore<PlayersListState> {
constructor(
route: ActivatedRoute,
private playersService: PlayersService,
private store: Store,
) {
super({
loaded: false,
page: 1,
count: 0,
players: []
});

route.queryParamMap.pipe(
map(params => Number(params.get('page'))),
combineLatest([
this.store.select(this.selectPage),
this.store.select(this.selectSearch),
]).pipe(
takeUntil(this.destroy$),
).subscribe(page => {
const newPage = page ? page : 1;
this.setPage(newPage);
this.getPlayers(newPage);
});
).subscribe(([page, search]) => this.getPlayers({ page, search }));
}

public readonly setLoaded = this.updater<boolean>((state, loaded) => ({
...state,
loaded,
}));

public readonly setPage = this.updater<number>((state, page) => ({
...state,
page,
}));

public readonly setCount = this.updater<number>((state, count) => ({
...state,
count,
Expand All @@ -69,27 +62,37 @@ export class PlayersListStore extends ComponentStore<PlayersListState> {
};
});

private readonly selectPage = createSelector(
selectQueryParam('page'),
(page) => Number(page) > 0 ? Number(page) : 1
);
private readonly selectSearch = createSelector(
selectQueryParam('search'),
search => search ? String(search) : ''
);

public readonly loaded$ = this.select(state => state.loaded);
public readonly page$ = this.select(state => state.page);
public readonly count$ = this.select(state => state.count);
public readonly players$ = this.select(state => state.players);
public readonly vm$ = this.select(
this.loaded$,
this.page$,
this.store.select(this.selectPage),
this.store.select(this.selectSearch),
this.count$,
this.players$,
(loaded, page, count, players) => ({
(loaded, page, search, count, players) => ({
loaded,
page,
search,
count,
players,
})
);

public readonly getPlayers = this.effect<number>(page$ => page$.pipe(
switchMap(page => forkJoin([
this.playersService.find(page),
this.playersService.count(),
public readonly getPlayers = this.effect<PlayerFindOptions>(options$ => options$.pipe(
switchMap(({ page, search }) => forkJoin([
this.playersService.find({ page, search }),
this.playersService.count({ search }),
]).pipe(
tapResponse(
([players, { count }]) => {
Expand Down
27 changes: 23 additions & 4 deletions apps/dashboard/src/app/shared/services/players.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import { EnvironmentConfig, ENV_CONFIG } from '../../models/environment-config.m
import { IPlayer } from '@pool-overlay/models';
import { ICount } from '../../models/count.model';

export interface PlayerFindOptions {
page: number;
search?: string;
}

export interface PlayerCountOptions {
search?: string;
}

@Injectable()
export class PlayersService {
private apiURL: string;
Expand All @@ -20,8 +29,13 @@ export class PlayersService {
this.apiVersion = config.environment.apiVersion;
}

public find(page = 1): Observable<IPlayer[]> {
const url = `${this.apiURL}/${this.apiVersion}/${this.endpoint}?page=${page}`;
public find({ page, search }: PlayerFindOptions = { page: 1 }): Observable<IPlayer[]> {
let url = `${this.apiURL}/${this.apiVersion}/${this.endpoint}?page=${page}`;

if (search) {
url = url + `&search=${search}`;
}

return this.http.get<IPlayer[]>(url);
}

Expand All @@ -30,8 +44,13 @@ export class PlayersService {
return this.http.get<IPlayer>(url);
}

public count(): Observable<ICount> {
const url = `${this.apiURL}/${this.apiVersion}/${this.endpoint}/count`;
public count({ search }: PlayerCountOptions = {}): Observable<ICount> {
let url = `${this.apiURL}/${this.apiVersion}/${this.endpoint}/count`;

if (search) {
url = url + `?search=${search}`;
}

return this.http.get<ICount>(url);
}

Expand Down
13 changes: 13 additions & 0 deletions apps/dashboard/src/app/shared/utils/router.selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { getSelectors } from '@ngrx/router-store';

export const {
selectCurrentRoute, // select the current route
selectFragment, // select the current route fragment
selectQueryParams, // select the current route query params
selectQueryParam, // factory function to select a query param
selectRouteParams, // select the current route params
selectRouteParam, // factory function to select a route param
selectRouteData, // select the current route data
selectUrl, // select the current url
selectTitle, // select the title if available
} = getSelectors();
1 change: 1 addition & 0 deletions libs/dashboard/components/search/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { SearchModule } from './lib/search.module';
export { SearchEvent } from './lib/search.component';
14 changes: 8 additions & 6 deletions libs/dashboard/components/search/src/lib/search.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { faMagnifyingGlass } from '@fortawesome/pro-regular-svg-icons';

let idCounter = 0;

export interface SearchEvent {
search: string;
}

@Component({
selector: 'dashboard-search',
templateUrl: './search.component.html'
Expand All @@ -20,7 +24,7 @@ export class SearchComponent {
}

@Output()
public onSearch = new EventEmitter<{ search: string }>();
public onSearch = new EventEmitter<SearchEvent>();

public faMagnifyingGlass = faMagnifyingGlass;
public form: FormGroup;
Expand All @@ -31,13 +35,11 @@ export class SearchComponent {
this.id = `dashboard-search-${++idCounter}`;

this.form = this._fb.group({
search: '',
search: new FormControl(''),
});
}

public submit(): void {
this.onSearch.emit({
search: '',
});
this.onSearch.emit(this.form.value);
}
}
35 changes: 31 additions & 4 deletions libs/go/api/players.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ func (server *Server) handlePlayers() http.Handler {

// Players handler for GET method. Returns a page of players.
func (server *Server) handlePlayersGet(w http.ResponseWriter, r *http.Request) {
// where clause for find and count
where := make(map[string]interface{})

// get query vars
v := r.URL.Query()

Expand All @@ -70,17 +73,25 @@ func (server *Server) handlePlayersGet(w http.ResponseWriter, r *http.Request) {
return
}

// get search
search := v.Get("search")

// only add search to where if there is a length
if len(search) > 0 {
where["name"] = search
}

// get count of players to test page ceiling
var count int64
countResult := server.db.Model(&models.Player{}).Count(&count)
countResult := server.db.Model(&models.Player{}).Where(where).Count(&count)
if countResult.Error != nil {
server.handleError(w, r, http.StatusInternalServerError, ErrInternalServerError)
return
}

// check if page is beyond maximum
totalPages := int(math.Ceil(float64(count) / playersPerPage))
if pageNum > totalPages {
if pageNum > totalPages && totalPages != 0 {
server.handleError(w, r, http.StatusUnprocessableEntity, ErrInvalidPageNumber)
return
}
Expand All @@ -91,6 +102,7 @@ func (server *Server) handlePlayersGet(w http.ResponseWriter, r *http.Request) {
offset := pageNum*playersPerPage - playersPerPage
playersResult := server.db.
Select("id", "name", "flag_id", "fargo_observable_id", "fargo_id", "fargo_rating").
Where(where).
Order("name").
Limit(playersPerPage).
Offset(offset).
Expand Down Expand Up @@ -152,9 +164,24 @@ func (server *Server) handlePlayersCount() http.Handler {

// Players count handler for GET method.
func (server *Server) handlePlayersCountGet(w http.ResponseWriter, r *http.Request) {
// get count of players
var count int64
countResult := server.db.Model(&models.Player{}).Count(&count)

// where clause for count
where := make(map[string]interface{})

// get query vars
v := r.URL.Query()

// get search
search := v.Get("search")

// only add search to where if there is a length
if len(search) > 0 {
where["name"] = search
}

// get count of players
countResult := server.db.Model(&models.Player{}).Where(where).Count(&count)
if countResult.Error != nil {
server.handleError(w, r, http.StatusInternalServerError, ErrInternalServerError)
return
Expand Down
Loading
Loading