feat: bootstrap HoopScout scouting app

This commit is contained in:
bisco
2026-06-03 21:37:15 +02:00
parent c4b1b6ee15
commit cc188468bc
52 changed files with 14505 additions and 126 deletions
+141
View File
@@ -0,0 +1,141 @@
<main class="shell">
<header class="topbar">
<div>
<p class="eyebrow">Private basketball scouting</p>
<h1>HoopScout</h1>
</div>
<div class="status">
<span>{{ resultCount }}</span>
<small>matches</small>
</div>
</header>
<section class="filters" aria-label="Player filters">
<label>
Search
<input type="search" [(ngModel)]="filters.q" placeholder="Name, role, team, nationality">
</label>
<label>
Position
<select [(ngModel)]="filters.position">
<option *ngFor="let position of positions" [value]="position">{{ position || 'Any' }}</option>
</select>
</label>
<label>
Role
<input type="text" [(ngModel)]="filters.role" placeholder="3 and D, handler, rim runner">
</label>
<label>
League
<select [(ngModel)]="filters.league">
<option *ngFor="let league of leagues" [value]="league">{{ league || 'Any' }}</option>
</select>
</label>
<label>
Min PPG
<input type="number" [(ngModel)]="filters.minPoints" min="0" step="0.1">
</label>
<label>
Min APG
<input type="number" [(ngModel)]="filters.minAssists" min="0" step="0.1">
</label>
<label>
Min RPG
<input type="number" [(ngModel)]="filters.minRebounds" min="0" step="0.1">
</label>
<label>
Min EFF
<input type="number" [(ngModel)]="filters.minEfficiency" min="0" step="0.1">
</label>
<div class="actions">
<button type="button" class="primary" (click)="search()">Search</button>
<button type="button" class="secondary" (click)="clearFilters()">Reset</button>
</div>
</section>
<p *ngIf="errorMessage" class="alert">{{ errorMessage }}</p>
<section class="workspace" aria-label="Scouting workspace">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Player</th>
<th>Pos</th>
<th>League</th>
<th>Team</th>
<th>PPG</th>
<th>APG</th>
<th>RPG</th>
<th>EFF</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="let player of players"
[class.selected]="selectedPlayer?.id === player.id"
(click)="selectPlayer(player)"
>
<td>
<strong>{{ player.name }}</strong>
<small>{{ player.nationality || '-' }} · {{ player.height_cm || '-' }} cm · {{ player.weight_kg || '-' }} kg</small>
</td>
<td>{{ player.position }}</td>
<td>{{ player.league?.code || '-' }}</td>
<td>{{ player.team?.name || '-' }}</td>
<td>{{ statValue(player, 'points_per_game') }}</td>
<td>{{ statValue(player, 'assists_per_game') }}</td>
<td>{{ statValue(player, 'rebounds_per_game') }}</td>
<td>{{ statValue(player, 'efficiency_rating') }}</td>
</tr>
</tbody>
</table>
<div *ngIf="!loading && players.length === 0" class="empty">No players match the current filters.</div>
<div *ngIf="loading" class="empty">Loading scouting board...</div>
</div>
<aside class="detail" *ngIf="selectedPlayer">
<div>
<p class="eyebrow">Selected profile</p>
<h2>{{ selectedPlayer.name }}</h2>
<p class="role">{{ selectedPlayer.position }} · {{ selectedPlayer.role || 'Role pending' }}</p>
</div>
<dl class="bio-grid">
<div>
<dt>League</dt>
<dd>{{ selectedPlayer.league?.name || '-' }}</dd>
</div>
<div>
<dt>Team</dt>
<dd>{{ selectedPlayer.team?.name || '-' }}</dd>
</div>
<div>
<dt>Born</dt>
<dd>{{ selectedPlayer.birth_year || '-' }}</dd>
</div>
<div>
<dt>Size</dt>
<dd>{{ selectedPlayer.height_cm || '-' }} cm / {{ selectedPlayer.weight_kg || '-' }} kg</dd>
</div>
</dl>
<div class="metric-grid">
<div>
<span>PPG</span>
<strong>{{ statValue(selectedPlayer, 'points_per_game') }}</strong>
</div>
<div>
<span>TS%</span>
<strong>{{ statValue(selectedPlayer, 'true_shooting_percentage') }}</strong>
</div>
<div>
<span>USG%</span>
<strong>{{ statValue(selectedPlayer, 'usage_percentage') }}</strong>
</div>
<div>
<span>Games</span>
<strong>{{ statValue(selectedPlayer, 'games_played') }}</strong>
</div>
</div>
</aside>
</section>
</main>