generated from bisco/codex-bootstrap
merge: allow returning from player detail to results
This commit is contained in:
@@ -126,6 +126,12 @@ button {
|
|||||||
background: #e7eeeb;
|
background: #e7eeeb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compact {
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 0 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.sort {
|
.sort {
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
@@ -152,12 +158,16 @@ button {
|
|||||||
|
|
||||||
.workspace {
|
.workspace {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) 390px;
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workspace.detail-open {
|
||||||
|
grid-template-columns: minmax(0, 1fr) 390px;
|
||||||
|
}
|
||||||
|
|
||||||
.table-wrap,
|
.table-wrap,
|
||||||
.detail {
|
.detail {
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
@@ -229,6 +239,13 @@ td small {
|
|||||||
top: 14px;
|
top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.role {
|
.role {
|
||||||
margin: 6px 0 0;
|
margin: 6px 0 0;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -311,6 +328,10 @@ dd {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workspace.detail-open {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.table-wrap {
|
.table-wrap {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
}
|
}
|
||||||
@@ -364,6 +385,11 @@ dd {
|
|||||||
h1 {
|
h1 {
|
||||||
font-size: 2.6rem;
|
font-size: 2.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 520px) {
|
@media (max-width: 520px) {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
|
|
||||||
<p *ngIf="errorMessage" class="alert">{{ errorMessage }}</p>
|
<p *ngIf="errorMessage" class="alert">{{ errorMessage }}</p>
|
||||||
|
|
||||||
<section class="workspace" aria-label="Scouting workspace">
|
<section class="workspace" [class.detail-open]="selectedPlayer" aria-label="Scouting workspace">
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -129,10 +129,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="detail" *ngIf="selectedPlayer">
|
<aside class="detail" *ngIf="selectedPlayer">
|
||||||
<div>
|
<div class="detail-header">
|
||||||
<p class="eyebrow">Selected profile</p>
|
<div>
|
||||||
<h2>{{ selectedPlayer.name }}</h2>
|
<p class="eyebrow">Selected profile</p>
|
||||||
<p class="role">{{ selectedPlayer.position }} · {{ selectedPlayer.role || 'Role pending' }}</p>
|
<h2>{{ selectedPlayer.name }}</h2>
|
||||||
|
<p class="role">{{ selectedPlayer.position }} · {{ selectedPlayer.role || 'Role pending' }}</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="secondary compact" (click)="returnToResults()">Back to results</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-strip">
|
<div class="profile-strip">
|
||||||
<span>{{ selectedPlayer.nationality || '-' }}</span>
|
<span>{{ selectedPlayer.nationality || '-' }}</span>
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ describe('AppComponent', () => {
|
|||||||
assert.equal(component.resultCount, 2);
|
assert.equal(component.resultCount, 2);
|
||||||
assert.equal(component.averagePoints, '14.00');
|
assert.equal(component.averagePoints, '14.00');
|
||||||
assert.equal(component.topEfficiencyPlayer?.name, 'Luca Marini');
|
assert.equal(component.topEfficiencyPlayer?.name, 'Luca Marini');
|
||||||
|
assert.equal(component.selectedPlayer, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reacts to filter changes without requiring the search button', () => {
|
it('reacts to filter changes without requiring the search button', () => {
|
||||||
@@ -131,4 +132,19 @@ describe('AppComponent', () => {
|
|||||||
|
|
||||||
assert.equal(component.players[0].name, 'Mateo Santos');
|
assert.equal(component.players[0].name, 'Mateo Santos');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('opens and closes a player profile without losing the filtered list', () => {
|
||||||
|
const api = {
|
||||||
|
searchPlayers: () => of({ count: samplePlayers.length, results: samplePlayers }),
|
||||||
|
} as unknown as PlayerApiService;
|
||||||
|
const component = new AppComponent(api);
|
||||||
|
|
||||||
|
component.search();
|
||||||
|
component.selectPlayer(samplePlayers[0]);
|
||||||
|
component.returnToResults();
|
||||||
|
|
||||||
|
assert.equal(component.selectedPlayer, null);
|
||||||
|
assert.equal(component.players.length, 2);
|
||||||
|
assert.equal(component.resultCount, 2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -84,9 +84,10 @@ export class AppComponent {
|
|||||||
|
|
||||||
this.playerApi.searchPlayers(this.filters).subscribe({
|
this.playerApi.searchPlayers(this.filters).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.players = this.sortPlayers(response.results);
|
const players = this.sortPlayers(response.results);
|
||||||
|
this.players = players;
|
||||||
this.resultCount = response.count;
|
this.resultCount = response.count;
|
||||||
this.selectedPlayer = this.players[0] ?? null;
|
this.selectedPlayer = this.matchSelectedPlayer(players);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
@@ -126,13 +127,17 @@ export class AppComponent {
|
|||||||
sortBy(key: SortKey): void {
|
sortBy(key: SortKey): void {
|
||||||
this.activeSort = key;
|
this.activeSort = key;
|
||||||
this.players = this.sortPlayers(this.players);
|
this.players = this.sortPlayers(this.players);
|
||||||
this.selectedPlayer = this.players[0] ?? null;
|
this.selectedPlayer = this.matchSelectedPlayer(this.players);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectPlayer(player: PlayerSummary): void {
|
selectPlayer(player: PlayerSummary): void {
|
||||||
this.selectedPlayer = player;
|
this.selectedPlayer = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnToResults(): void {
|
||||||
|
this.selectedPlayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
statValue(player: PlayerSummary, key: keyof NonNullable<PlayerSummary['stats']>): string {
|
statValue(player: PlayerSummary, key: keyof NonNullable<PlayerSummary['stats']>): string {
|
||||||
const value = player.stats?.[key];
|
const value = player.stats?.[key];
|
||||||
return value === undefined || value === null ? '-' : String(value);
|
return value === undefined || value === null ? '-' : String(value);
|
||||||
@@ -170,6 +175,13 @@ export class AppComponent {
|
|||||||
return Number(player.stats?.[key] ?? 0);
|
return Number(player.stats?.[key] ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private matchSelectedPlayer(players: PlayerSummary[]): PlayerSummary | null {
|
||||||
|
if (!this.selectedPlayer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return players.find((player) => player.id === this.selectedPlayer?.id) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
private cancelPendingSearch(): void {
|
private cancelPendingSearch(): void {
|
||||||
if (this.pendingSearch) {
|
if (this.pendingSearch) {
|
||||||
clearTimeout(this.pendingSearch);
|
clearTimeout(this.pendingSearch);
|
||||||
|
|||||||
Reference in New Issue
Block a user