Skip to content

Commit

Permalink
Merge pull request #92 from paulmojicatech/updated-charts
Browse files Browse the repository at this point in the history
Updated charts
  • Loading branch information
paulmojicatech authored Dec 18, 2022
2 parents 0a31a2e + 015920f commit e7886e8
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const fantasyFootballRoutes: Route[] = [
)
]
},
{
path: 'player-details/:id',
loadComponent: () => import('../player-detail/player-detail.component').then(c => c.PlayerDetailComponent)
},
{
path: '',
pathMatch: 'full',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { PositionTypes } from '../../../../../../libs/fantalytic-shared/src/index';

export const FANTASY_FOOTBALL_STAT_HEADER_MAP = {
[PositionTypes.QB]: {
passingYds: 'Yds',
ints: 'Ints',
passingYdsPerAttempt: 'Yds Per Att',
tds: 'TDs'
},
[PositionTypes.RB]: {
rushingYds: 'Yds',
rushAttempts: 'Attempts',
rushingTds: 'TDs',
rushing20Yds: '20+ Yd'
},
[PositionTypes.WR]: {
receptions: 'Rec',
receivingYds: ' Yds',
receivingTds: 'TDs',
receiving20Plus: '20+ Yd',
receiving40Plus: '40+ Yd',
receivingTargets: 'Targets'
},
[PositionTypes.DEF]: {
rushYdsAllowed: 'Rush Yds',
ydsPerCarry: 'Yds / Carry',
rushTdsAllowed: 'Rush TDs',
completionPctAllowed: 'Comp %',
passYdsAllowed: 'Pass Yds',
ints: 'Ints',
sacks: 'Sacks'
}
};
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
<ion-content>
<section class="tabs-container">
<div class="year-container">
<select>
<select (change)="updateSelectedYear($event)">
<option *ngFor="let year of AVAILABLE_YEARS" [value]="year" [selected]="year === (selectedYear$ | async)">{{year}}</option>
</select>
<select (change)="handleStatTypeChanged($event)" *ngIf="availableStatsSub$ | async as availableStats">
<option *ngFor="let statType of availableStats" [value]="statType">{{statType}}</option>
</select>
</div>
<ion-accordion-group (ionChange)="handleAccordionStateChange($event)">
<ion-accordion *ngFor="let pos of positionsMap; let i = index;" [value]="pos.value">
<ion-accordion *ngFor="let pos of positionsMapSub$ | async; let i = index;" [value]="pos.value">
<ion-item slot="header">
<ion-label>{{pos.label}}</ion-label>
</ion-item>
<div class="pos-container" slot="content">
<ng-template #positionTable let-positionData="positionData">
<div class="table-header">
<span class="ion-padding">{{positionData.header[0]}}</span>
<span class="ion-padding">{{positionData.header[1]}}</span>
<span *ngIf="pos.label !== 'Defenses'; else team" class="ion-padding">Player</span>
<ng-template #team>
<span class="ion-padding">Team</span>
</ng-template>
<span class="ion-padding">{{currentStatHeaderSub$ | async}}</span>
</div>

<ion-item *ngFor="let stat of positionData.stats; trackBy: statTrackByFn;">
<ion-item *ngFor="let stat of positionData.stats; trackBy: statTrackByFn;" (click)="handlePlayerSelected(stat.id)">
<div class="stats-container">
<span class="player-container">
<ion-avatar>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
/* eslint-disable quote-props */
/* eslint-disable max-len */
import { CommonModule } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { Store } from '@ngrx/store';
import { combineLatest, filter, map, Observable, take } from 'rxjs';
import { loadDefenses, loadQbs, loadRbs, loadReceivers } from './ngrx/actions/fantasy-football.actions';
import { getDefenses, getQbs, getRbs, getReceivers, getSelectedYear } from './ngrx/selectors/fantasy-football.selectors';
import { BehaviorSubject, combineLatest, filter, map, Observable, Subject, take, takeUntil } from 'rxjs';
import { PositionTypes } from '../../../../../libs/fantalytic-shared/src';
import { loadDefenses, loadQbs, loadRbs, loadReceivers, setPositionType, updateYearFilter } from './ngrx/actions/fantasy-football.actions';
import { getDefenses, getPosition, getQbs, getRbs, getReceivers, getSelectedYear } from './ngrx/selectors/fantasy-football.selectors';

@Component({
selector: 'pmt-fantasy-football',
Expand All @@ -14,131 +17,160 @@ import { getDefenses, getQbs, getRbs, getReceivers, getSelectedYear } from './ng
templateUrl: './fantasy-football.component.html',
styleUrls: ['./fantasy-football.component.scss'],
})
export class FantasyFootballComponent implements OnInit {
export class FantasyFootballComponent implements OnInit, OnDestroy {


readonly AVAILABLE_YEARS = [2018,2019,2020,2021,2022];

positionsMap: {label: string; ctx: {positionData: {header: string[]; stats: any[]}}; value: string}[] = [
positionsMapSub$ = new BehaviorSubject<{label: string; ctx: {positionData: {stats: any[]}}; value: string; availableStats: {[key: string]: string}}[]>([
{
label: 'QBs',
ctx: {
positionData: {
header: ['Passing', 'Yds'],
stats: []
},
},
value: 'qb'
value: 'qb',
availableStats: {
'Passing Yds': 'passingYds',
'Passing TDs': 'tds',
'Interceptions': 'ints',
'Passing Yds Per Attempt': 'passingYdsPerAttempt'
}
},
{
label: 'RBs',
ctx: {
positionData: {
header: ['Rushing', 'Yds'],
stats: []
},
},
value: 'rb'
value: 'rb',
availableStats: {
'Rushing Yds': 'rushingYds',
'Rushing Attempts': 'rushAttempts',
'Rushing TDs': 'rushingTds',
'20+ Runs': 'rushing20Yds'
}
},
{
label: 'Receivers',
ctx: {
positionData: {
header: ['Receiving', 'Yds'],
stats: []
}
},
value: 'rec'
value: 'rec',
availableStats: {
'Receiving Yds': 'receivingYds',
'Receptions': 'receptions',
'Receiving TDs': 'receivingTds',
'Targets': 'receivingTargets',
'20+ Catches': 'receiving20Plus',
'40+ Catches': 'receiving40Plus'
}
},
{
label: 'Defenses',
ctx: {
positionData: {
header: ['Defense', 'Yds Allowed'],
stats: []
},
},
value: 'def'
value: 'def',
availableStats: {
'Rushing Yds Allowed': 'rushYdsAllowed',
'Rushing TDs Allowed': 'rushTdsAllowed',
'Completion Pct Allowed': 'completionPctAllowed',
'Passing Yds Allowed': 'passYdsAllowed',
'Interceptions': 'ints',
'Sacks': 'sacks'
}
},
];
]);

availableStatsSub$ = new BehaviorSubject<string[]>(['Passing Yds', 'Passing TDs', 'Interceptions', 'Passing Yds Per Attempt']);
currentStatHeaderSub$ = new BehaviorSubject<string>('Passing Yds');
private _store = inject(Store);
private _router = inject(Router);

selectedYear$ = this._store.select(getSelectedYear);
qbs$ = combineLatest([this._store.select(getQbs), this.selectedYear$]).pipe(
filter(([qbs]) => !!qbs),
map(([qbs, year]) => qbs.filter(qb => qb.year === year).map(qb => ({player: qb.player, stat: qb.passingYds, id: qb.id, imgUrl: qb.imageUrl})).sort((prev, next) => {
if (prev.stat > next.stat) {
return -1;
}
return 1;
}))
);
rbs$ = combineLatest([this._store.select(getRbs), this.selectedYear$]).pipe(
filter(([rbs]) => !!rbs),
map(([rbs, year]) => rbs.filter(rb => rb.year === year).map(rb => ({player: rb.player, stat: rb.rushingYds, id: rb.id, imgUrl: rb.imageUrl})).sort((prev, next) => {
if (prev.stat > next.stat) {
return -1;
}
return 1;
}))
);

recs$ = combineLatest([this._store.select(getReceivers), this.selectedYear$]).pipe(
filter(([recs]) => !!recs),
map(([recs, year]) => recs.filter(rec => rec.year === year).map(rec => ({player: rec.player, stat: rec.receivingYds, id: rec.id, imgUrl: rec.imageUrl})).sort((prev, next) => {
if (prev.stat > next.stat) {
return -1;
}
return 1;
}))
);

def$ = combineLatest([this._store.select(getDefenses), this.selectedYear$]).pipe(
filter(([def]) => !!def),
map(([defs, year]) => defs.filter(def => def.year === year).map(def => ({player: def.team, stat: def.passYdsAllowed + def.rushYdsAllowed, id: def.id, imgUrl: def.imageUrl})).sort((prev, next) => {
if (prev.stat < next.stat) {
return -1;
}
return 1;
}))
);
private _currentPosition$ = this._store.select(getPosition);
qbs$ = this._store.select(getQbs).pipe(filter(qbs => !!qbs));
rbs$ = this._store.select(getRbs).pipe(filter(rbs => !!rbs));
recs$ = this._store.select(getReceivers).pipe(filter(recs => !!recs));
def$ = this._store.select(getDefenses).pipe(filter(defs => !!defs));

private _onDestroySub$ = new Subject<void>();

ngOnInit(): void {
this._store.dispatch(loadQbs());
this._store.dispatch(loadRbs());
this._store.dispatch(loadReceivers());
this._store.dispatch(loadDefenses());
this.qbs$.pipe(take(1)).subscribe(stats => {
this.positionsMap[0] = {...this.positionsMap[0], ctx: {positionData: {...this.positionsMap[0].ctx.positionData, stats}}};
combineLatest([this.selectedYear$, this.qbs$, this.rbs$, this.recs$, this.def$, this._currentPosition$, this.currentStatHeaderSub$]).pipe(
takeUntil(this._onDestroySub$)
).subscribe(([year, qbs, rbs, recs, defs, position, currentStatHeader]) => {
switch (position) {
case PositionTypes.QB: {
const updatedState = this.getUpdatedPositionStats(qbs, year, currentStatHeader, 0);
this.positionsMapSub$.next(updatedState);
break;
}
case PositionTypes.RB: {
const updatedState = this.getUpdatedPositionStats(rbs, year, currentStatHeader, 1);
this.positionsMapSub$.next(updatedState);
break;
}
case PositionTypes.WR: {
const updatedState = this.getUpdatedPositionStats(recs, year, currentStatHeader, 2);
this.positionsMapSub$.next(updatedState);
break;
}
case PositionTypes.DEF: {
const updatedState = this.getUpdatedPositionStats(defs, year, currentStatHeader, 3);
this.positionsMapSub$.next(updatedState);
break;
}
default:
break;
}
});
}

ngOnDestroy(): void {
this._onDestroySub$.next();
}

handleAccordionStateChange(ev: any): void {
switch (ev.detail.value) {
case 'qb': {
this._store.dispatch(setPositionType(PositionTypes.QB));
const updatedStats = Object.keys(this.positionsMapSub$.getValue()[0].availableStats);
this.availableStatsSub$.next(updatedStats);
this.currentStatHeaderSub$.next('Passing Yds');
break;
}
case 'rb': {
if (!this.positionsMap[1].ctx.positionData.stats.length) {
this.rbs$.pipe(take(1)).subscribe(stats => {
this.positionsMap[1] = {...this.positionsMap[1], ctx: {positionData: {...this.positionsMap[1].ctx.positionData, stats}}};
});
break;
}
this._store.dispatch(setPositionType(PositionTypes.RB));
const updatedStats = Object.keys(this.positionsMapSub$.getValue()[1].availableStats);
this.availableStatsSub$.next(updatedStats);
this.currentStatHeaderSub$.next('Rushing Yds');
break;
}
case 'rec': {
if (!this.positionsMap[2].ctx.positionData.stats.length) {
this.recs$.pipe(take(1)).subscribe(stats => {
this.positionsMap[2] = {...this.positionsMap[2], ctx: {positionData: {...this.positionsMap[2].ctx.positionData, stats}}};
});
break;
}
this._store.dispatch(setPositionType(PositionTypes.WR));
const updatedStats = Object.keys(this.positionsMapSub$.getValue()[2].availableStats);
this.availableStatsSub$.next(updatedStats);
this.currentStatHeaderSub$.next('Receiving Yds');
break;
}
case 'def': {
if (!this.positionsMap[3].ctx.positionData.stats.length) {
this.def$.pipe(take(1)).subscribe(stats => {
this.positionsMap[3] = {...this.positionsMap[3], ctx: {positionData: {...this.positionsMap[3].ctx.positionData, stats}}};
});
break;
}
this._store.dispatch(setPositionType(PositionTypes.DEF));
const updatedStats = Object.keys(this.positionsMapSub$.getValue()[3].availableStats);
this.availableStatsSub$.next(updatedStats);
this.currentStatHeaderSub$.next('Rushing Yds Allowed');
break;
}
default:
break;
Expand All @@ -149,4 +181,45 @@ export class FantasyFootballComponent implements OnInit {
return stat.id ?? stat.player;
}

updateSelectedYear(event: Event): void {
const year = +(event.target as HTMLSelectElement).value;
this._store.dispatch(updateYearFilter(year));
}

handleStatTypeChanged(event: Event): void {
const statType = (event.target as HTMLSelectElement).value;
this.currentStatHeaderSub$.next(statType);

}

handlePlayerSelected(playerId: string): void {
this._router.navigate(['tabs', 'fantasy-football', `player-details`, playerId]);
}

private getUpdatedPositionStats(positionData: any[], year: number, currentStatHeader: string, index: number): {label: string; ctx: {positionData: {stats: any[]}}; value: string; availableStats: {[key: string]: string}}[] {
const currentPositionMapState = this.positionsMapSub$.getValue();
const updatedPositionMap = {
...currentPositionMapState[index],
ctx: {
positionData: {
stats: positionData.filter(pos => pos.year === year).map(pos => (
{
id: pos.id,
player: pos.player,
imgUrl: pos.imageUrl,
stat: +pos[currentPositionMapState[index].availableStats[currentStatHeader]]
}
)).sort((prev, next) => {
if (prev.stat > next.stat) {
return -1;
}
return 1;
})
}
}
};
currentPositionMapState[index] = updatedPositionMap;
return currentPositionMapState;
}

}
Loading

0 comments on commit e7886e8

Please sign in to comment.