https://github.com/maestor/fantrax-stats-parser-ui
Show my NHL fantasy league team stats in simple sortable table with different filters, made with Angular.
https://github.com/maestor/fantrax-stats-parser-ui
angular angularmaterial sports-data sports-stats typescript
Last synced: about 1 month ago
JSON representation
Show my NHL fantasy league team stats in simple sortable table with different filters, made with Angular.
- Host: GitHub
- URL: https://github.com/maestor/fantrax-stats-parser-ui
- Owner: maestor
- License: mit
- Created: 2025-02-11T10:27:21.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2026-04-24T11:37:04.000Z (about 1 month ago)
- Last Synced: 2026-04-24T13:05:21.240Z (about 1 month ago)
- Topics: angular, angularmaterial, sports-data, sports-stats, typescript
- Language: TypeScript
- Homepage: https://colorado-ffhl-stats.vercel.app
- Size: 2.39 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Roadmap: docs/roadmap.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
# FFHL Stats application
## Purpose
This repo is the UI for [node-fantrax-stats-parser](https://github.com/maestor/node-fantrax-stats-parser), a backend that parses FFHL hockey stats. The app turns that data into browsable player, goalie, career, leaderboard, and draft views, and it also serves as a place to practice modern Angular in a real project.
Live showcase: https://ffhl-stats.vercel.app/
## Contributor Docs
For contributor and agent guidance, start at [docs/README.md](docs/README.md).
The docs in this repo intentionally focus on project-specific architecture, workflow, testing, accessibility, and local exceptions rather than re-teaching generic Angular concepts.
This repo also carries project-local agent skills under `.agents/skills/`. Keep those committed when the repo workflow depends on them, and use the matching skill when a task touches testing strategy, API contract drift, verification depth, browser UI validation, or accessibility-first UI work.
For CSS/theming work specifically, read [docs/styling-guide.md](docs/styling-guide.md) before changing shared styles, tokens, or Material overrides.
## Features
- 📊 **Player Statistics**: View and analyze player performance across seasons
- 🥅 **Goalie Statistics**: Dedicated view for goalie-specific metrics
- 🏒 **Team Selector**: Choose a team from the shared settings drawer on every route. Your selection is remembered across reloads and used when you return to stats views
- ⏳ **Start From Season**: Set the lower bound for combined stats
- 🔄 **Report Switching**: Toggle between regular season and playoffs
- 📅 **Season Selection**: Filter data by specific seasons or view combined stats
- 📈 **Stats Per Game**: Calculate and display per-game averages
- 🎯 **Minimum Games Filter**: Filter players/goalies by minimum games played
- 🏒 **Position Filter**: Filter skaters by position with position-aware scoring comparisons
- ⚖️ **Player Comparison**: Compare two players or goalies side by side in a dialog
- 🔍 **Search & Sort**: Interactive table with search and column sorting
- 📌 **Sticky Headers**: Table headers remain visible while scrolling with full horizontal scroll support
- 🧮 **Score Ranking**: Composite score sorting helps surface the most impactful players and goalies
- 🏷️ **Compact Headers**: Short stat labels with tooltips for the full names
- 📇 **Player Card**: Open a detailed card for any player or goalie with combined career stats, season breakdowns, and charts
- 📉 Graphs tab includes per-season charts for key stats
- 🎯 Radar view shows score breakdowns at a glance
- 🏆 Career best seasons are highlighted in the season breakdown
- ⬅️➡️ Move between players or goalies without closing the card
- 🏆 **All-Time Leaderboards**: Browse all-time team rankings for regular season, playoffs, and transactions
- Expand team rows to see season-by-season breakdowns
- Ties, trophies, and transaction summaries are presented clearly in the standings
- The shared selected team receives keyboard focus by default unless selected-team highlighting is disabled from the settings drawer
- Finals view presents season-by-season championship matchups as expandable cards with responsive category breakdowns and compact stat labels
- 📚 **Career Listings**: Browse all-time player and goalie career tables plus career highlights
- Search and sort the main career tables
- Long lists stay responsive
- Career highlights are grouped into compact jumpable sections
- 📝 **Draft Pages**: Browse entry drafts, opening drafts, and draft statistics under `Varaukset`
- Team-by-team draft history and summary views
- Played-status markers show whether drafted players reached the drafting team or elsewhere in the league
- Draft statistics cards are grouped into sectioned rankings with jump navigation
- The full draft team listing now scrolls inside a bounded route-level window so the draft tabs stay reachable; when a selected team auto-opens on route entry, it aligns to the top of that list
- The shared team setting opens or highlights the selected team by default across draft views, and the shared settings drawer toggle can disable that behavior on both draft and leaderboard routes
- ⚙️ **Shared Settings Drawer**: The same settings entry point is available across stats, career, draft, and leaderboard routes
- 🗂️ **Global Navigation**: Bottom sheet menu for switching between views (hockey stats, player careers, leaderboards, info/help)
- Keyboard navigation is supported inside the menu
- 🔗 **Direct Player Links**: Share player and goalie cards with direct links, including selected tabs
- 🔎 **SEO / Share Basics**: Public pages include route-aware titles and share metadata
- 💾 **Smart Caching**: Reduces repeated API calls
- 🌐 **Internationalization**: Finnish UI with room for additional languages
- 🎨 **Material Design**: Clean UI with Angular Material components
- 🌓 **Automatic Dark Mode**: Follows device/browser `prefers-color-scheme` (no manual toggle)
- 📦 **Installable PWA**: Installable on desktop/mobile; app shell is cached for offline-friendly reloads (live stats still require the backend)
- 📱 **Mobile Responsive**: Optimized for all screen sizes with adaptive layouts and a shared settings drawer
- 🕒 **Last Updated Indicator**: Shows the backend data last-modified timestamp inside the shared settings drawer on every route
- 🏷️ **Route Subtitles**: Lightweight subtitles help show which section you are in across stats, career, draft, and leaderboard views
More details:
- **Theming / Automatic Dark Mode**: [docs/project-overview.md](docs/project-overview.md)
- **PWA / Installable App**: [docs/project-overview.md](docs/project-overview.md)
## Installation and use
### Prerequisites
Running backend, instructions find from [node-fantrax-stats-parser](https://github.com/maestor/node-fantrax-stats-parser).
### Backend API URL configuration
This UI talks to a separate backend (see [node-fantrax-stats-parser](https://github.com/maestor/node-fantrax-stats-parser)). The backend API is documented at [https://ffhl-stats-api.vercel.app/api-docs](https://ffhl-stats-api.vercel.app/api-docs).
**Development**
- Angular calls the backend directly:
- `src/environments/environment.ts` → `apiUrl: 'http://localhost:3000'`
**Production / “Showcase” (Vercel)**
- The UI is deployed at https://ffhl-stats.vercel.app/
- Angular does **not** call the backend directly in production. Instead it calls `'/api'`:
- `src/environments/environment.production.ts` → `apiUrl: '/api'`
- Vercel rewrites `/api/` to a Serverless Function (`/api/proxy`) which:
- forwards the request to your real backend (`API_URL`)
- injects a secret `x-api-key` header (`API_KEY`) so the browser never sees the key
- only allows the UI's known read endpoints via `GET` and `OPTIONS`
- strips client `Authorization` and does not forward upstream `Set-Cookie` headers back to the browser
**Vercel environment variables (required)**
- `API_URL` = absolute backend base URL (e.g. `https://your-backend.example.com`)
- `API_KEY` = backend API key (kept server-side)
- `ALLOWED_ORIGINS` = comma-separated allowed origins for browser requests (supports `*` wildcard in origin)
- Example: `https://ffhl-stats.vercel.app,https://ffhl-stats-*-development.vercel.app`
- Use origin only (scheme + host + optional port), no path
**Vercel environment variables (optional)**
- `RATE_LIMIT_MAX` (default `120`) and `RATE_LIMIT_WINDOW_SEC` (default `60`) for best-effort per-IP rate limiting
After changing Vercel env vars, redeploy so they take effect.
```bash
1. Install Node.js (version 24.x)
2. Clone this repository
3. npm install
4. npm start
5. Navigate to http://localhost:4200
```
### Available Scripts
```bash
# Development
npm start # Dev server on http://localhost:4200 (live reload, HMR disabled)
npm run generate:types # Regenerate API types from OpenAPI spec
# Tests (Vitest + Testing Library)
npm test # Run once (no browser required)
npm run test:watch # Watch mode
npm run test:coverage # With coverage report
# E2E tests (Playwright / Chromium only) — requires backend running locally
npm run e2e # Run all (headless, Chromium)
npm run e2e:headed # Run with visible browser
npm run e2e:ui # Interactive UI mode
npm run e2e:smoke # Smoke tests only
npm run e2e:capture-fixtures # Re-capture API fixtures from live backend
# CI / verification
npm run verify # Headless unit tests + production build
npm run perf:audit # Local production performance audit (CWV-oriented)
# Production build
npm run build
```
### Production bundle budgets
Production builds enforce Angular bundle budgets in `angular.json`:
- `initial`: warning at `1 MB`, error at `1.5 MB`
- `anyComponentStyle`: warning at `4 kB`, error at `8 kB`
Best practice when `npm run verify` or `npm run build` shows a budget warning:
1. Treat it as a review item, not background noise.
2. Identify which bundle or stylesheet triggered it.
3. Prefer reducing eager code, moving heavy UI behind lazy imports, or removing duplicated CSS before raising budgets.
4. Raise a budget only when the size increase is understood, justified by real functionality, and the new threshold still acts as a meaningful guardrail.
Local test policy: run only one heavy test command at a time, and wait about 2 minutes between repeated `npm run verify` runs on local machines. See [docs/project-testing.md](docs/project-testing.md).
### Performance audit (Core Web Vitals oriented)
Run `npm run perf:audit` to execute a local lab-style performance audit against the production build.
- The command rebuilds the app in production mode
- Serves `dist/fantrax-stats-parser-ui/browser` locally
- Mocks API responses from `e2e/fixtures/data/` so results are deterministic and backend-independent
- Audits `/`, `/career/players`, and `/leaderboards/regular` in both desktop and mobile browser profiles
- Reports `LCP`, `CLS`, and an interaction-delay proxy for common user actions
- Logs the top layout-shift sources to make CLS regressions easier to diagnose
Important:
- This is a local guardrail, not a replacement for public PageSpeed Insights scores
- `LCP` and `CLS` map directly to Core Web Vitals thresholds
- The interaction number is a scripted lab proxy, not full field `INP`
- Use PageSpeed Insights / CrUX field data to judge the real shipped user experience
- Current local baseline as of `2026-03-09`: desktop `/` `CLS 0.040`, desktop `/career/players` `CLS 0.010`, desktop `/leaderboards/regular` `CLS 0.010`, mobile audited routes `CLS 0.000`
## Testing
This project uses **Testing Library** (`@testing-library/angular`) with **Vitest** for component/behavior tests, targeted **service-layer tests** for HTTP/cache/platform integrations, and **Playwright** for end-to-end tests. UI tests follow a user-centric, accessible-query approach. Run `npm test` to see the current test count and status.
For planning-heavy changes, save the approved implementation plan locally under `docs/plans/YYYY-MM-DD-*.md` before editing code. The `docs/plans/` directory is gitignored and exists for session-to-session continuity only.
📖 **[Read the complete Testing Documentation](docs/project-testing.md)**
E2E tests are organized into feature-based specs under `e2e/specs/`:
- `smoke.spec.ts` — Core page rendering and navigation
- `career.spec.ts` — Career players/goalies/highlights tabs, grouped highlight section navigation, paging, and route shell behavior
- `draft.spec.ts` — Draft route redirect/tab behavior plus entry-draft and opening-draft accordion rendering
- `leaderboards.spec.ts` — Leaderboards redirect, regular/playoff/transactions/finals tabs, tie-rank vs incremental position logic, expandable season details, and finals matchup cards
- `player-card.spec.ts` — Player card dialog (open/close, tabs, graphs, direct URLs)
- `team-switching.spec.ts` — Team selector and filter reset behavior
- `filters.spec.ts` — Report type, season, position, stats-per-game, and min games filters
- `player-comparison.spec.ts` — Player comparison selection, dialog, stats and radar tabs
- `mobile.spec.ts` — Mobile-specific player-card graph and accordion flow
**Local:** Backend API must be running on `localhost:3000` (see [node-fantrax-stats-parser](https://github.com/maestor/node-fantrax-stats-parser)).
**CI:** E2E tests run without a backend — API responses are served from JSON fixtures via Playwright's `page.route()` mocking.
In CI fixture mode, Playwright also blocks service workers so mocked `/api/**` requests cannot be swallowed by the production PWA layer.
Local Playwright runs against `http://localhost:4200`. The Playwright `webServer` starts `npm start` automatically when needed, or reuses an already-running frontend on port `4200`.
Recommended local flow:
```bash
# 1. Start the backend separately on http://localhost:3000
# 2. Run all E2E tests
npm run e2e
# or run one spec while iterating
npx playwright test e2e/specs/leaderboards.spec.ts
```
Important:
- Do not use `CI=true` for normal local E2E runs. In this repo that switches Playwright into the fixture-backed CI flow, which is meant for CI and explicit offline debugging, not routine local verification against your real backend.
- If you do need a real local `CI=true` run, make sure `http://localhost:4200` is free first so Playwright can start the CI-mode server instead of colliding with an already running frontend.
- `npx playwright test --headed` still uses the same `webServer` config. It does not disable server startup by itself; it simply opens the browser UI while Playwright starts or reuses the frontend.
- If the backend on `localhost:3000` is not running during a paired session, ask the user to start it. Do not silently swap to CI-mode mocking for routine local verification.
- When a Playwright selector needs visible Finnish UI copy, use `fi('...')` from `e2e/config/i18n.ts` or shared labels from `e2e/config/test-data.ts` instead of hard-coding Finnish strings in specs.
### Test Coverage Summary
Coverage is tracked via `npm run test:coverage` and as part of `npm run verify` (tests with coverage + production build). Vitest coverage now uses the **Istanbul** provider, and CI enforces minimum thresholds of **93% statements**, **85% branches**, **94% functions**, and **95% lines** via `angular.json` (`architect.test.options.coverageThresholds`). Angular's generated `ngDevMode` signal-branch coverage noise is no longer distorting the branch baseline, so branch percentages are reliable enough to gate more tightly again. Every contribution must still include tests for all new/changed logic (aim for **100% coverage for touched code paths**, including error/edge cases).
See [docs/project-testing.md](docs/project-testing.md) for detailed information about test patterns, best practices, and coverage.
Service-layer note:
- UI behavior should still be tested through rendered user flows
- UI tests should keep real app state services in place; only external/platform boundaries such as `ApiService`, `ViewportService`, and `PwaUpdateService` should normally be mocked
- Lower-level services such as `ApiService`, `CacheService`, and `PwaUpdateService` may use focused `TestBed` specs when the real HTTP/platform pipeline itself is what needs coverage
- Small UI-only helpers such as formatters or tooltip/view-model builders should normally stay covered through the owning behavior test; use a dedicated helper spec only when there is no realistic behavior/service-layer coverage path
## Accessibility
Accessibility is a core requirement of this project (not optional).
- Keyboard-only users must be able to use every feature
- Focus must be visible and predictable
- Collapsed/hidden content must not be tabbable
📖 **[Read the Accessibility Guide](docs/accessibility.md)**
## Technology Stack
- **Framework**: Angular 21
- **UI Library**: Angular Material 21
- **Language**: TypeScript 5.9
- **State Management**: Angular signals for synchronous UI state reads, RxJS 7.8 for async flows and compatibility streams
- **HTTP Client**: Angular HttpClient with caching
- **Testing**: Vitest + Testing Library (`@testing-library/angular`), Playwright (E2E)
- **i18n**: ngx-translate 17
## Angular Component Conventions
For Angular 21 component work in this repo:
- Standalone components are the default. Do not add redundant `standalone: true`.
- Prefer signal inputs for component APIs. Use `input.required()` whenever the parent must always provide the value; do not keep inputs optional "just in case".
- Prefer the `host` object in `@Component` metadata over `@HostListener` and `@HostBinding`.
- For app state services, prefer signal-facing read APIs in component code and keep observable APIs for async composition or compatibility only.
- After refactors, investigate the replaced implementation paths and delete leftovers that are proven unused. Do not keep dormant fallback code "just in case".
- Use `ChangeDetectionStrategy.OnPush` when the component is already safe for it through signal inputs, `async`-pipe flows, or local event-driven state. Do not apply it as a blanket rule to subscription-heavy components.
## Project Structure
```
api/ # Vercel API proxy
docs/ # Project docs (guides, standards)
e2e/ # Playwright end-to-end tests
public/i18n/ # Translation files
scripts/ # Helper scripts (coverage, etc.)
src/
├── app/
│ ├── base/ # Base components (navigation, footer)
│ ├── services/ # Core services (API, cache, stats, filters, team)
│ ├── shared/ # Shared components
│ │ ├── comparison-bar/ # Floating comparison selection bar
│ │ ├── comparison-dialog/ # Comparison dialog (stats + radar)
│ │ │ ├── comparison-stats/ # Side-by-side stat rows
│ │ │ └── comparison-radar/ # Radar chart overlay
│ │ ├── player-card/ # Player detail dialog
│ │ ├── settings-panel/# Settings UI (toggles/sliders)
│ │ ├── stats-table/ # Reusable stats table + virtualized career table
│ │ ├── table-card/ # Reusable paged card with semantic table layout
│ │ ├── top-controls/ # Header controls (team/season/report switchers)
│ │ └── table-columns.ts
│ ├── player-stats/ # Player stats page
│ ├── goalie-stats/ # Goalie stats page
│ ├── dashboard-shell/ # Lazy shell for dashboard routes
│ ├── career/ # Career listings + highlights (/career/players, /career/goalies, /career/highlights)
│ ├── leaderboards/ # All-time leaderboards (/leaderboards/regular, /leaderboards/playoffs, /leaderboards/transactions)
│ ├── player-route/ # Direct player card route handler
│ ├── goalie-route/ # Direct goalie card route handler
│ ├── utils/ # Utility functions (slug generation)
│ └── app.component.ts # Lightweight root shell
└── environments/ # Build-time environment config
```
## Development Notes
This project was originally generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.1.6 and has been upgraded to Angular 21.
### Key Architectural Decisions
1. **Standalone Components**: All components use Angular's standalone API (no modules)
2. **Reactive Patterns**: Angular signals for synchronous UI reads, with RxJS retained for async composition and compatibility
3. **Type Safety**: Strict TypeScript configuration enforced
4. **Immutable State**: Filter state updates create new objects
5. **Path Aliases**: `@base/*`, `@services/*`, `@shared/*` for clean imports
6. **Route-Shell Split**: Dashboard routes lazy-load their heavier shell so career and leaderboard browsing routes ship less startup UI code
### Mobile Responsiveness
The application is fully responsive with optimized layouts for all screen sizes:
- **Desktop (>960px)**: Full horizontal layout with all controls visible
- **Tablet (768px-960px)**: Shared settings drawer with optimized spacing and touch targets
- **Mobile (<768px)**: Shared settings drawer plus collapsible graph controls for better space utilization
- **Small Mobile (<480px)**: Stacked layouts with adjusted font sizes and padding
**Key mobile features:**
- Base settings and metadata live in a shared settings drawer on every route, with stats-only sections added on player/goalie routes
- Table headers remain sticky during vertical scrolling while maintaining horizontal scroll capability
- Player card graph controls collapse into a toggle button on mobile (<768px)
- Horizontal scrolling enabled for wide tables (bySeason view)
- Optimized table font sizes and padding for small screens
- All interactive elements remain accessible and touch-friendly
## Contributing
When contributing, please ensure:
1. All new/changed code has corresponding tests (aim 100% coverage for the code you touched)
2. For non-documentation-only changes, `npm run verify` passes (includes coverage gate + production build)
3. Follow existing code style and patterns
4. Run `npm run verify` before committing non-documentation-only changes
5. Update documentation when changes affect usage/behavior, scripts/workflows, or project standards
In addition, treat accessibility as a hard requirement for every change. If a feature is not keyboard-accessible and properly labeled, it is not considered done.
## License
This project is for personal use and learning purposes.