From 1b74470fcb25ca7153f9f60c77f4cb6581842dac Mon Sep 17 00:00:00 2001 From: Split Date: Thu, 26 Feb 2026 13:04:28 -0500 Subject: [PATCH] Rebuild frontend with HTMX + Tailwind + Askama templates --- src/main.rs | 427 +++++++++----------------- templates/base.html | 280 +++-------------- templates/components/chart.html | 55 ++++ templates/components/match_row.html | 26 ++ templates/components/nav.html | 21 +- templates/components/player_card.html | 13 + templates/pages/about.html | 183 +++++------ templates/pages/daily.html | 78 +++++ templates/pages/home.html | 44 ++- templates/pages/leaderboard.html | 21 +- templates/pages/match_form.html | 79 +++++ templates/pages/matches.html | 66 ++++ templates/pages/player.html | 132 ++++++++ templates/pages/player_form.html | 36 +++ templates/pages/players.html | 33 ++ 15 files changed, 844 insertions(+), 650 deletions(-) create mode 100644 templates/components/chart.html create mode 100644 templates/components/match_row.html create mode 100644 templates/components/player_card.html create mode 100644 templates/pages/daily.html create mode 100644 templates/pages/match_form.html create mode 100644 templates/pages/matches.html create mode 100644 templates/pages/player.html create mode 100644 templates/pages/player_form.html create mode 100644 templates/pages/players.html diff --git a/src/main.rs b/src/main.rs index 628fa1b..ccf4ecf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ use axum::{ routing::{get, post}, Router, - response::{Html, Redirect}, + response::{Html, Redirect, IntoResponse}, extract::{Form, State, Path, Query}, http::StatusCode, }; +use askama::Template; use sqlx::SqlitePool; use pickleball_elo::db; use pickleball_elo::elo::{EloRating, EloCalculator, calculate_weighted_score, calculate_effective_opponent_rating}; @@ -179,6 +180,111 @@ const COMMON_CSS: &str = r#" .rating-down { color: #dc3545; } "#; +// ========== ASKAMA TEMPLATE STRUCTS ========== + +#[derive(Template, Clone, Debug)] +#[template(path = "pages/home.html")] +struct HomeTemplate { + player_count: i64, + match_count: i64, + session_count: i64, +} + +#[derive(Template, Clone, Debug)] +#[template(path = "pages/leaderboard.html")] +struct LeaderboardTemplate { + leaderboard: Vec<(i32, PlayerData)>, +} + +#[derive(Clone, Debug, Serialize)] +struct PlayerData { + id: i64, + name: String, + rating: f64, + singles_rating: f64, + email: String, + rating_display: String, + has_email: bool, +} + +#[derive(Template, Clone, Debug)] +#[template(path = "pages/players.html")] +struct PlayersTemplate { + players: Vec, +} + +#[derive(Template, Clone, Debug)] +#[template(path = "pages/player.html")] +struct PlayerTemplate { + player: PlayerData, + match_count: i64, + win_rate: f64, + win_rate_display: String, + chart_labels: String, + chart_data: String, + recent_matches: Vec, +} + +#[derive(Clone, Debug, Serialize)] +struct MatchData { + id: i64, + match_type: String, + score: String, + rating_change: f64, + rating_change_display: String, + date: String, +} + +#[derive(Template, Clone, Debug)] +#[template(path = "pages/matches.html")] +struct MatchesTemplate { + matches: Vec, +} + +#[derive(Clone, Debug)] +struct MatchDisplay { + id: i64, + match_type: String, + team1_players: Vec, + team1_score: i32, + team2_score: i32, + team2_players: Vec, + match_date: String, + match_time: String, +} + +#[derive(Clone, Debug)] +struct PlayerMatchData { + id: i64, + name: String, + rating_change: f64, + rating_change_display: String, +} + +#[derive(Template, Clone, Debug)] +#[template(path = "pages/match_form.html")] +struct MatchFormTemplate { + players: Vec, +} + +#[derive(Template)] +#[template(path = "pages/daily.html")] +struct DailyTemplate { + daily_matches: Vec, + leaderboard: Vec<(i32, PlayerData)>, +} + +#[derive(Template)] +#[template(path = "pages/about.html")] +struct AboutTemplate; + +#[derive(Template)] +#[template(path = "pages/player_form.html")] +struct PlayerFormTemplate { + player: Option, + editing: bool, +} + #[tokio::main] async fn main() { println!("๐Ÿ“ Pickleball ELO Tracker v3.0"); @@ -255,7 +361,7 @@ fn nav_html() -> &'static str { /// **Parameters:** None /// /// **Returns:** HTML page with dashboard stats and navigation menu -async fn index_handler(State(state): State) -> Html { +async fn index_handler(State(state): State) -> impl IntoResponse { let player_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM players") .fetch_one(&state.pool).await.unwrap_or(0); let match_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM matches") @@ -263,66 +369,11 @@ async fn index_handler(State(state): State) -> Html { let session_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM sessions") .fetch_one(&state.pool).await.unwrap_or(0); - let html = format!(r#" - - - - - - Pickleball ELO Tracker - - - -
-

๐Ÿ“ Pickleball ELO Tracker

-

Pure ELO Rating System v3.0

- -
-
-
{}
-
Matches
-
-
-
{}
-
Players
-
-
-
{}
-
Sessions
-
-
- - {} - -
-

๐Ÿ“Š How Ratings Work

- -

One unified rating โ€” Singles and doubles matches both contribute to a single ELO rating. Everyone starts at 1500.

- -

Per-point scoring โ€” Your rating change depends on your actual point performance (points won รท total points), not just whether you won or lost. Winning 11-2 earns more than winning 11-9.

- -

Smart doubles scoring โ€” In doubles, we calculate your "effective opponent" using:
- Effective Opponent = Opp1 + Opp2 - Teammate

- -

This means:

-
    -
  • Strong teammate โ†’ lower effective opponent โ†’ less credit for winning
  • -
  • Weak teammate โ†’ higher effective opponent โ†’ more credit for winning
  • -
- -

The formula:
- - Rating Change = 32 ร— (Actual Performance - Expected Performance) -

- -

Fair, transparent, and no mysterious "volatility" numbers. Just skill vs. expectations.

-
-
- - - "#, COMMON_CSS, match_count, player_count, session_count, nav_html()); - - Html(html) + HomeTemplate { + player_count, + match_count, + session_count, + } } /// Displays the match history page with recent matches and results @@ -1526,54 +1577,31 @@ async fn create_match( /// **Parameters:** None /// /// **Returns:** HTML page with players table -async fn players_list_handler(State(state): State) -> Html { - // UNIFIED RATING: just get singles_rating as the unified rating +async fn players_list_handler(State(state): State) -> impl IntoResponse { + // UNIFIED RATING: use rating field (merged singles/doubles) let players: Vec<(i64, String, Option, f64)> = sqlx::query_as( - "SELECT id, name, email, singles_rating FROM players ORDER BY singles_rating DESC" + "SELECT id, name, email, rating FROM players ORDER BY rating DESC" ) .fetch_all(&state.pool) .await .unwrap_or_default(); - let player_rows: String = players.iter() - .map(|(id, name, _email, rating)| { - format!(r#" - {} - {:.0} - "#, id, name, rating) + let players = players.into_iter() + .map(|(id, name, email, rating)| { + let has_email = email.is_some(); + PlayerData { + id, + name, + rating, + singles_rating: rating, + email: email.unwrap_or_default(), + rating_display: format!("{:.1}", rating), + has_email, + } }) .collect(); - let html = format!(r#" - - - - - - All Players - Pickleball ELO - - - -
-

๐Ÿ‘ฅ All Players ({})

- {} - - - - - - - - {} -
NameRating
-
- - - "#, COMMON_CSS, players.len(), nav_html(), player_rows); - - Html(html) + PlayersTemplate { players } } /// Displays the top 10 ranked players in singles and doubles @@ -1585,58 +1613,35 @@ async fn players_list_handler(State(state): State) -> Html { /// **Parameters:** None /// /// **Returns:** HTML page with unified leaderboard -async fn leaderboard_handler(State(state): State) -> Html { - // UNIFIED RATING: Single leaderboard using singles_rating as unified rating - let players: Vec<(i64, String, f64)> = sqlx::query_as( - r#"SELECT DISTINCT p.id, p.name, p.singles_rating +async fn leaderboard_handler(State(state): State) -> impl IntoResponse { + // UNIFIED RATING: Single leaderboard using rating field + let players: Vec<(i64, String, f64, Option)> = sqlx::query_as( + r#"SELECT DISTINCT p.id, p.name, p.rating, p.email FROM players p JOIN match_participants mp ON p.id = mp.player_id - ORDER BY p.singles_rating DESC LIMIT 10"# + ORDER BY p.rating DESC"# ) .fetch_all(&state.pool) .await .unwrap_or_default(); - let player_rows: String = players.iter().enumerate() - .map(|(i, (id, name, rating))| { - let medal = match i { 0 => "๐Ÿฅ‡", 1 => "๐Ÿฅˆ", 2 => "๐Ÿฅ‰", _ => "" }; - format!(r#"
-
{}{}. {}
- {:.0} -
"#, medal, i + 1, id, name, rating) + let leaderboard = players.into_iter().enumerate() + .map(|(i, (id, name, rating, email))| { + let has_email = email.is_some(); + let player_data = PlayerData { + id, + name, + rating, + singles_rating: rating, + email: email.unwrap_or_default(), + rating_display: format!("{:.1}", rating), + has_email, + }; + ((i + 1) as i32, player_data) }) .collect(); - let html = format!(r#" - - - - - - Leaderboard - Pickleball ELO - - - -
-

๐Ÿ“ Leaderboard

- {} - -

๐Ÿ“Š Top Players (Unified ELO)

-
{}
- -

- Unified rating: singles and doubles matches both contribute to one rating.
- Learn how ratings work โ†’ -

-
- - - "#, COMMON_CSS, nav_html(), player_rows); - - Html(html) + LeaderboardTemplate { leaderboard } } /// Returns JSON API endpoint for leaderboard data @@ -3396,138 +3401,6 @@ async fn daily_public_handler( /// About page explaining the ELO rating system /// /// **Endpoint:** `GET /about` -async fn about_handler() -> Html { - let html = format!(r#" - - - - - - About - Pickleball ELO - - - -
- {} -
-

๐Ÿ“Š How the Rating System Works

-

A unified ELO system designed for recreational pickleball

- -
-

๐ŸŽฏ The Basics

-

One rating for everything. Whether you play singles or doubles, all matches contribute to a single ELO rating. Everyone starts at 1500.

-

The system rewards skill and adapts to your performance. Beat higher-rated players? Big gains. Lose to lower-rated players? Bigger losses. It's that simple.

-
- -
-

๐Ÿ“ˆ Per-Point Scoring

-

Unlike traditional ELO that only cares about winning or losing, our system considers how you played:

-
- Performance = Points Won รท Total Points -
-

This means:

-
    -
  • Winning 11-2 earns more than winning 11-9
  • -
  • Losing 9-11 costs less than losing 2-11
  • -
  • Close games = smaller rating swings for everyone
  • -
-
- Example: You win 11-7 against someone equally rated.
- Performance = 11 รท 18 = 0.611
- Expected (equal ratings) = 0.500
- You outperformed expectations โ†’ rating goes up! -
-
- -
-

๐Ÿ‘ฅ The Doubles Problem

-

In doubles, you have a partner. If your partner is much stronger than you, you'll probably win more โ€” but how much credit should you get?

-

We solve this with the Effective Opponent formula:

-
- Effective Opponent = Opponent 1 + Opponent 2 โˆ’ Teammate -
-

This creates a personalized opponent rating for each player. Here's how it works:

-
    -
  • Strong teammate โ†’ Lower effective opponent โ†’ Less credit for winning, less blame for losing
  • -
  • Weak teammate โ†’ Higher effective opponent โ†’ More credit for winning, more blame for losing
  • -
-
- Example: You're 1500 playing with a 1600 partner against two 1550 opponents.
- Your effective opponent = 1550 + 1550 โˆ’ 1600 = 1500
- Your partner's effective opponent = 1550 + 1550 โˆ’ 1500 = 1600

- If you win, you gain less than your partner because their effective opponent was harder! -
-
- -
-

๐Ÿ”ข The Math

-

For those who want the details:

- -

Expected Performance

-
- Expected = 1 / (1 + 10^((Opponent โˆ’ You) / 400)) -
-

This is the standard ELO expectation formula. If you're 200 points above your opponent, you're expected to score about 76% of points.

- -

Rating Change

-
- ฮ” Rating = K ร— (Actual โˆ’ Expected) -
-

We use K = 32, which is standard for casual/club play. This means:

-
    -
  • Maximum gain/loss per match: ยฑ32 points
  • -
  • Typical swing for competitive matches: ยฑ10-15 points
  • -
  • Close match against equal opponent: ยฑ2-5 points
  • -
-
- -
-

โ“ Why This System?

-

We tried Glicko-2 first (with rating deviation and volatility), but it was:

-
    -
  • Confusing โ€” nobody understood what "RD 150" meant
  • -
  • Opaque โ€” the math was hidden behind complexity
  • -
  • Overkill โ€” designed for chess with thousands of games, not rec pickleball
  • -
-

Pure ELO with per-point scoring is:

-
    -
  • Transparent โ€” you can calculate changes by hand
  • -
  • Fair โ€” accounts for margin of victory and partner strength
  • -
  • Simple โ€” one number that goes up when you play well
  • -
-
- -
-

๐Ÿ† Rating Tiers

- - - - - - - -
Below 1400Developing
1400-1500Intermediate
1500-1600Solid
1600-1700Strong
1700+โญ Rising Star
1900+๐Ÿ‘‘ Elite
-
- -

- โ† Back to Home -

-
-
- - - "#, COMMON_CSS, nav_html()); - - Html(html) +async fn about_handler() -> impl IntoResponse { + AboutTemplate } diff --git a/templates/base.html b/templates/base.html index 4aa32ec..dbd1743 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,277 +1,73 @@ - + {% block title %}Pickleball ELO Tracker{% endblock %} + + + + + + {% block extra_css %}{% endblock %} - -
+ +
{% block content %}{% endblock %}
diff --git a/templates/components/chart.html b/templates/components/chart.html new file mode 100644 index 0000000..411d7d9 --- /dev/null +++ b/templates/components/chart.html @@ -0,0 +1,55 @@ +
+

๐Ÿ“ˆ ELO Rating History

+ +
+ + diff --git a/templates/components/match_row.html b/templates/components/match_row.html new file mode 100644 index 0000000..c51c32c --- /dev/null +++ b/templates/components/match_row.html @@ -0,0 +1,26 @@ + + {{ match_type }} + + {% for player in team1_players %} +
+ {{ player.name }} + +{{ player.rating_change | round(1) }} +
+ {% endfor %} + + {{ team1_score }}-{{ team2_score }} + + {% for player in team2_players %} +
+ {{ player.name }} + {{ player.rating_change | round(1) }} +
+ {% endfor %} + + {{ match_date }} + +
+ +
+ + diff --git a/templates/components/nav.html b/templates/components/nav.html index 702acea..7a782a5 100644 --- a/templates/components/nav.html +++ b/templates/components/nav.html @@ -1,10 +1,11 @@ - + diff --git a/templates/components/player_card.html b/templates/components/player_card.html new file mode 100644 index 0000000..90a33d7 --- /dev/null +++ b/templates/components/player_card.html @@ -0,0 +1,13 @@ +
+

+ {{ player.name }} +

+
{{ player.singles_rating | round(1) }}
+ {% if player.email %} +

๐Ÿ“ง {{ player.email }}

+ {% endif %} + +
diff --git a/templates/pages/about.html b/templates/pages/about.html index eed86ba..7f51c2c 100644 --- a/templates/pages/about.html +++ b/templates/pages/about.html @@ -3,92 +3,101 @@ {% block title %}About - Pickleball ELO Tracker{% endblock %} {% block content %} -

โ“ About Pickleball ELO

-{% include "components/nav.html" %} - -
-

๐Ÿ“Š What is ELO?

-

- The ELO rating system is a method for calculating the relative skill levels of players. - It was invented for chess but works great for any competitive sport. -

-

- In pickleball, your ELO rating represents your skill level. Everyone starts at 1500. - When you play a match, your rating goes up or down based on: -

-
    -
  • Your performance: Points won รท total points (not just win/loss)
  • -
  • Your opponent's skill: Playing a higher-rated opponent is worth more
  • -
  • The upset factor: Beating someone stronger gains more points
  • -
-
- -
-

๐ŸŽพ Unified Rating (v3.0)

-

- In version 3, we switched from separate singles/doubles ratings to a unified rating. - Both singles and doubles matches contribute to the same ELO rating, because: -

-
    -
  • One rating is simpler to understand
  • -
  • Players who excel in both formats get fairly rewarded
  • -
  • Pure ELO is transparent and bias-free
  • -
-
- -
-

โš–๏ธ Smart Doubles Scoring

-

- In doubles matches, we calculate your "effective opponent" to make scoring fair: -

-

- Effective Opponent = Opp1 + Opp2 - Teammate -

-

- This means: -

-
    -
  • Playing with a strong teammate โ†’ harder opponents count as weaker โ†’ less credit for winning
  • -
  • Playing with a weak teammate โ†’ harder opponents count as stronger โ†’ more credit for winning
  • -
-

- This approach is fair, symmetric, and makes strategic sense. -

-
- -
-

๐Ÿงฎ The Formula

-

- Rating Change = K ร— (Actual Performance โˆ’ Expected Performance) -

-

Where:

-
    -
  • K = 32 (standard, adjustable for casual/competitive play)
  • -
  • Actual Performance = Your points รท Total points
  • -
  • Expected Performance = Based on the ELO formula (derived from rating difference)
  • -
-

- The expected performance formula: E = 1 / (1 + 10^((Opp_Rating - Your_Rating) / 400)) -

-
- -
-

๐Ÿ“ˆ Why This System Works

-
    -
  • Transparent: No hidden formulas or magic numbers. You can verify your rating changes.
  • -
  • Fair: You're rewarded for actual performance, not just wins.
  • -
  • Balanced: Winning 11-2 and 11-9 are different and rated differently.
  • -
  • Skill-based: Beating stronger players earns more; losing to them costs less.
  • -
  • Predictable: Ratings converge to true skill over time.
  • -
-
- -
-

๐Ÿ“ Version History

-
    -
  • v3.0.0 - Unified ELO rating, modular architecture, pure ELO calculator
  • -
  • v2.0.0 - Glicko-2 system with separate singles/doubles ratings
  • -
  • v1.0.0 - Initial release with basic ELO
  • -
+
+

โ„น๏ธ About the Rating System

+ + {% include "components/nav.html" %} + +
+

๐ŸŽฏ ELO Basics

+

+ This system uses ELO rating, a proven method for measuring competitive skill. Everyone starts at 1500. +

+

+ Unlike traditional win/loss records, ELO captures: +

+
    +
  • โœ… Skill level of opponents โ€” Beating better players earns more points
  • +
  • โœ… Individual performance โ€” Point differential matters (11-2 vs 11-9)
  • +
  • โœ… Consistency โ€” Your rating reflects your true skill over time
  • +
+
+ +
+

๐Ÿ“Š Unified Rating

+

+ One rating covers all match types (singles and doubles). This is more realistic: +

+
    +
  • ๐Ÿ“ Singles: Your direct 1v1 skill shows immediately
  • +
  • ๐Ÿ‘ฅ Doubles: Teamwork and court awareness matter just as much
  • +
+

+ Your "ELO Rating" represents your expected performance in ANY match type. A 1700-rated player will usually beat a 1500-rated player, whether playing singles or doubles. +

+
+ +
+

๐Ÿงฎ How Points Change

+

+ The core formula is simple: +

+
+ Rating Change = 32 ร— (Actual Performance - Expected Performance) +
+

+ Where: +

+
    +
  • Actual Performance = Points You Scored รท Total Points
  • +
  • Expected Performance = Calculated from rating difference
  • +
+

+ Example: +

+
    +
  • You (1600) beat them (1400) 11-5: Actual = 11/16 = 0.6875
  • +
  • You were expected to win ~70% of points (rating difference)
  • +
  • You performed worse than expected โ†’ smaller gain (~+8)
  • +
  • They performed better than expected โ†’ smaller loss (~-8)
  • +
+
+ +
+

๐Ÿ‘ฅ Doubles Scoring

+

+ In doubles, we calculate your effective opponent rating: +

+
+ Effective Opponent = (Opp1 + Opp2 - Teammate) รท 2 +
+

+ This accounts for teammate strength: +

+
    +
  • ๐ŸŸข Strong teammate: Lower effective opponent โ†’ less credit for winning
  • +
  • ๐Ÿ”ด Weak teammate: Higher effective opponent โ†’ more credit for winning
  • +
+

+ Why? Winning with a weaker partner takes more individual skill. Winning with a stronger partner is easier. The system recognizes this. +

+
+ +
+

โœจ Why This System?

+
    +
  • ๐ŸŽฏ Transparent: No hidden "k-factors" or magical numbers
  • +
  • โš–๏ธ Fair: Rewards individual skill, not just team wins
  • +
  • ๐Ÿ“ˆ Responsive: Converges quickly to true skill level
  • +
  • ๐Ÿ”„ Unified: One rating works for all match types
  • +
+
+ +
+

๐Ÿš€ Get Started

+

+ Ready to join? Add yourself as a player, then record your first match. Your journey begins at 1500! ๐Ÿ“ +

+
{% endblock %} diff --git a/templates/pages/daily.html b/templates/pages/daily.html new file mode 100644 index 0000000..501a069 --- /dev/null +++ b/templates/pages/daily.html @@ -0,0 +1,78 @@ +{% extends "base.html" %} + +{% block title %}Daily Summary - Pickleball ELO Tracker{% endblock %} + +{% block content %} +
+

๐Ÿ“ˆ Daily Summary

+ + {% include "components/nav.html" %} + +
+

๐ŸŽฏ Today's Matches

+ {% if daily_matches.is_empty() %} +

No matches recorded today.

+ {% else %} +
+ + + + + + + + + + + + {% for match in daily_matches %} + + + + + + + + {% endfor %} + +
TypeTeam 1ScoreTeam 2Time
{{ match.match_type }} + {% for player in match.team1_players %} +
+ {{ player.name }} + {{ player.rating_change_display }} +
+ {% endfor %} +
{{ match.team1_score }}-{{ match.team2_score }} + {% for player in match.team2_players %} +
+ {{ player.name }} + {{ player.rating_change_display }} +
+ {% endfor %} +
{{ match.match_time }}
+
+ {% endif %} +
+ +
+

๐Ÿ‘ฅ Leaderboard

+ {% if leaderboard.is_empty() %} +

No players with matches yet.

+ {% else %} +
+ {% for (rank, player) in leaderboard %} +
+
{{ rank }}
+ +
{{ player.rating_display }}
+
+ {% endfor %} +
+ {% endif %} +
+
+{% endblock %} diff --git a/templates/pages/home.html b/templates/pages/home.html index 5b0cbba..936310f 100644 --- a/templates/pages/home.html +++ b/templates/pages/home.html @@ -3,49 +3,47 @@ {% block title %}Home - Pickleball ELO Tracker{% endblock %} {% block content %} -
-

๐Ÿ“ Pickleball ELO Tracker

-

Pure ELO Rating System v3.0

+
+

๐Ÿ“ Pickleball ELO Tracker

+

Pure ELO Rating System v3.0

-
+
-
{{ match_count }}
-
Matches
+
{{ match_count }}
+
Matches
-
{{ player_count }}
-
Players
+
{{ player_count }}
+
Players
-
{{ session_count }}
-
Sessions
+
{{ session_count }}
+
Sessions
{% include "components/nav.html" %} -
-

๐Ÿ“Š How Ratings Work

+
+

๐Ÿ“Š How Ratings Work

-

One unified rating โ€” Singles and doubles matches both contribute to a single ELO rating. Everyone starts at 1500.

+

One unified rating โ€” Singles and doubles matches both contribute to a single ELO rating. Everyone starts at 1500.

-

Per-point scoring โ€” Your rating change depends on your actual point performance (points won รท total points), not just whether you won or lost. Winning 11-2 earns more than winning 11-9.

+

Per-point scoring โ€” Your rating change depends on your actual point performance (points won รท total points), not just whether you won or lost. Winning 11-2 earns more than winning 11-9.

-

Smart doubles scoring โ€” In doubles, we calculate your "effective opponent" using:
- Effective Opponent = Opp1 + Opp2 - Teammate

+

Smart doubles scoring โ€” In doubles, we calculate your "effective opponent" using:
+ Effective Opponent = Opp1 + Opp2 - Teammate

-

This means:

-
    +

    This means:

    +
    • Strong teammate โ†’ lower effective opponent โ†’ less credit for winning
    • Weak teammate โ†’ higher effective opponent โ†’ more credit for winning
    -

    The formula:
    - - Rating Change = 32 ร— (Actual Performance - Expected Performance) -

    +

    The formula:
    + Rating Change = 32 ร— (Actual Performance - Expected Performance)

    -

    Fair, transparent, and no mysterious "volatility" numbers. Just skill vs. expectations.

    +

    Fair, transparent, and no mysterious "volatility" numbers. Just skill vs. expectations.

{% endblock %} diff --git a/templates/pages/leaderboard.html b/templates/pages/leaderboard.html index 0efcbb6..4c67149 100644 --- a/templates/pages/leaderboard.html +++ b/templates/pages/leaderboard.html @@ -3,26 +3,25 @@ {% block title %}Leaderboard - Pickleball ELO Tracker{% endblock %} {% block content %} -

๐Ÿ“Š Leaderboard

+

๐Ÿ“Š Leaderboard

+ {% include "components/nav.html" %} {% if leaderboard.is_empty() %} -
- No players with matches yet. Record a match to see the leaderboard! +
+ No players with matches yet. Record a match to see the leaderboard!
{% else %} -
+
{% for (rank, player) in leaderboard %}
{{ rank }}
-
- + -
{{ player.rating | round(1) }}
+
{{ player.rating_display }}
{% endfor %}
diff --git a/templates/pages/match_form.html b/templates/pages/match_form.html new file mode 100644 index 0000000..6f89a97 --- /dev/null +++ b/templates/pages/match_form.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} + +{% block title %}Record Match - Pickleball ELO Tracker{% endblock %} + +{% block content %} +
+

โœ… Record a Match

+ + {% include "components/nav.html" %} + +
+
+ + +
+ +
+

Team 1

+
+ + +
+
+ + +
+
+ + +
+
+ +
+

Team 2

+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + Cancel +
+
+
+{% endblock %} diff --git a/templates/pages/matches.html b/templates/pages/matches.html new file mode 100644 index 0000000..822e79d --- /dev/null +++ b/templates/pages/matches.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} + +{% block title %}Match History - Pickleball ELO Tracker{% endblock %} + +{% block content %} +

๐ŸŽฏ Match History

+ +{% include "components/nav.html" %} + +{% if matches.is_empty() %} +
+ No matches recorded yet. Record the first match! +
+{% else %} +
+
+ + + + + + + + + + + + + {% for match in matches %} + + + + + + + + + {% endfor %} + +
TypeTeam 1ScoreTeam 2Date
{{ match.match_type }} + {% for player in match.team1_players %} +
+ {{ player.name }} + + {% if player.rating_change >= 0.0 %}+{% endif %}{{ player.rating_change_display }} + +
+ {% endfor %} +
{{ match.team1_score }}-{{ match.team2_score }} + {% for player in match.team2_players %} +
+ {{ player.name }} + + {% if player.rating_change >= 0.0 %}+{% endif %}{{ player.rating_change_display }} + +
+ {% endfor %} +
{{ match.match_date }} +
+ +
+
+
+
+{% endif %} +{% endblock %} diff --git a/templates/pages/player.html b/templates/pages/player.html new file mode 100644 index 0000000..9d78c57 --- /dev/null +++ b/templates/pages/player.html @@ -0,0 +1,132 @@ +{% extends "base.html" %} + +{% block title %}{{ player.name }} - Pickleball ELO Tracker{% endblock %} + +{% block content %} +
+ {% include "components/nav.html" %} + +
+
+
+

{{ player.name }}

+ {% if player.has_email %} +

๐Ÿ“ง {{ player.email }}

+ {% endif %} +
+ โœ๏ธ Edit +
+ +
+
+
ELO Rating
+
{{ player.rating_display }}
+
+
+
Matches Played
+
{{ match_count }}
+
+
+
Win Rate
+
{{ win_rate_display }}
+
+
+
+ + {% if chart_data.is_empty() %} +
+ No rating history yet. Record a match to see the chart! +
+ {% else %} +
+

๐Ÿ“ˆ Rating History

+ +
+ + + {% endif %} + +
+

๐Ÿ“‹ Recent Matches

+ {% if recent_matches.is_empty() %} +

No matches yet.

+ {% else %} +
+ + + + + + + + + + + {% for match in recent_matches %} + + + + + + + {% endfor %} + +
TypeScoreRating ChangeDate
{{ match.match_type }}{{ match.score }} + + {{ match.rating_change_display }} + + {{ match.date }}
+
+ {% endif %} +
+
+{% endblock %} diff --git a/templates/pages/player_form.html b/templates/pages/player_form.html new file mode 100644 index 0000000..1cbaad8 --- /dev/null +++ b/templates/pages/player_form.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block title %}{% if editing %}Edit{% else %}Add{% endif %} Player - Pickleball ELO Tracker{% endblock %} + +{% block content %} +
+

{% if editing %}โœ๏ธ Edit Player{% else %}โž• Add Player{% endif %}

+ + {% include "components/nav.html" %} + +
+
+ + +
+ +
+ + +
+ + {% if editing %} +
+ + +

This is the unified rating for all match types.

+
+ {% endif %} + +
+ + Cancel +
+
+
+{% endblock %} diff --git a/templates/pages/players.html b/templates/pages/players.html new file mode 100644 index 0000000..fe4598d --- /dev/null +++ b/templates/pages/players.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} + +{% block title %}Players - Pickleball ELO Tracker{% endblock %} + +{% block content %} +

๐Ÿ‘ฅ Players

+ +{% include "components/nav.html" %} + +{% if players.is_empty() %} +
+ No players yet. Add the first player! +
+{% else %} +
+ {% for player in players %} +
+

+ {{ player.name }} +

+
{{ player.rating_display }}
+ {% if player.has_email %} +

๐Ÿ“ง {{ player.email }}

+ {% endif %} + +
+ {% endfor %} +
+{% endif %} +{% endblock %}