Scoreboard webapp to upload hanchan scores and show players' performance
| src | ||
| static | ||
| supabase | ||
| .gitignore | ||
| netlify.toml | ||
| package.json | ||
| README.md | ||
| svelte.config.js | ||
| tsconfig.json | ||
| vite.config.ts | ||
AZRM Scoreboard
Frontend implementation for the database schema (seasons/players/matches/results/standings/ratings). Schema v1.2
Features
Public (no login required):
- Seasons list
- Season standings (cumulative points + basic stats)
- Player page: season stats + match history + cumulative point history + rating history
- Match page: final results (E/S/W/N seats)
Admin (login required):
- Create seasons
- Create players
- Create matches (draft)
- Enter match results (E/S/W/N + raw points)
- Finalize match (computes placement + club_points + ratings; makes it public)
- Import season matches from Excel on
/admin/seasons
Setup
- Create a Supabase project.
- Run the schema SQL (mahjong_supabase_schema.sql) in Supabase SQL Editor.
- Copy
.env.example->.envand fill:- PUBLIC_SUPABASE_URL
- PUBLIC_SUPABASE_ANON_KEY
- Install and run:
npm installnpm run dev
Promote an admin
After signing up once, set your auth user's profile row:
update public.profiles set is_admin=true where id = '<auth-user-uuid>';
Notes
- Public pages query read-only views:
- v_season_standings, v_season_player_stats, v_player_match_history, v_player_point_history, v_rating_history, v_final_results
- Admin pages write to base tables and call the
finalize_matchRPC.
Excel import format
Use /admin/seasons -> Import season matches (Excel) and upload a workbook with this header:
Date, Game, Tbl, E Player, S Player, W Player, N Player, E Pts, S Pts, W Pts, N Pts, Ex
Date:M/D/YYYY(example2/4/2026) or Excel date cellGame: positive integerTbl:A(automatic table) orM(manual)Players: real first name (or full display name)Pts: raw points per seatEx: extra out-of-play sticks (integer >= 0)
Importer behavior:
- If a player first name does not exist, importer auto-creates a player with:
real_first_name= imported value- display defaults set to show first name
- If a name matches multiple players, import fails with an ambiguity error.
Player naming model:
display_nameis optionalreal_first_nameis optionalreal_last_nameis optional- At least one of
display_nameorreal_first_namemust exist - Players can choose visibility flags for display/first/last (must show display or first)
For existing databases, run:
supabase/migrations/20260219_add_match_import_fields.sqlsupabase/migrations/20260219_rework_players_identity.sqlsupabase/migrations/20260220_fix_rating_state_conflict_targets.sql