From ec49fb3587809e161f9f4c1d178a1f8ebbcd9819 Mon Sep 17 00:00:00 2001 From: Chamika Date: Mon, 5 Jan 2026 19:36:18 +0000 Subject: [PATCH 01/12] Add Fixture sync feature Introduce tab navigation for the availability page --- design/sync/TODO.md | 177 ++++++++ frontend/src/lib/api/availability.ts | 23 +- frontend/src/lib/types/availability.ts | 9 + .../routes/availability/[teamId]/+page.svelte | 384 ++++++++++++------ worker/src/database.ts | 25 ++ worker/src/index.ts | 133 +++++- worker/src/types.ts | 9 + worker/wrangler.toml | 1 - 8 files changed, 638 insertions(+), 123 deletions(-) create mode 100644 design/sync/TODO.md diff --git a/design/sync/TODO.md b/design/sync/TODO.md new file mode 100644 index 0000000..175fdd2 --- /dev/null +++ b/design/sync/TODO.md @@ -0,0 +1,177 @@ +# Fixture Date Resync Feature - Implementation Tasks + +## Overview +Enable users to resynchronize fixture dates with the ELTTL website after matches are rescheduled. The system will automatically clear availability and selections when dates change, while preserving data for unchanged fixtures. + +## Backend Implementation + +### 1. Database Service Extensions +**File:** `worker/src/database.ts` + +- [ ] Add `getFixtureByTeams(teamId: string, homeTeam: string, awayTeam: string): Promise` + - Query fixtures table matching team_id, home_team, and away_team + - Used to match existing fixtures with scraped data + +- [ ] Add `updateFixtureDate(fixtureId: string, matchDate: string, dayTime: string): Promise` + - Update match_date and day_time for a fixture + - Recalculate is_past using isPastDate() + - Update updated_at timestamp (add this column if needed) + +- [ ] Add `clearAvailabilityForFixture(fixtureId: string): Promise` + - Delete all availability records for a fixture + - Called when fixture date changes + +### 2. API Endpoint +**File:** `worker/src/index.ts` + +- [ ] Create `POST /api/availability/{teamId}/sync` endpoint + - Validate teamId exists in database + - Fetch team.elttl_url from database + - Call `scrapeTeamFixtures(elttl_url)` to get current fixtures + - For each scraped fixture: + - Match to existing fixture using `getFixtureByTeams()` + - If match found: + - Compare match_date and day_time + - If changed: + - Call `updateFixtureDate()` + - Call `clearAvailabilityForFixture()` + - Call `clearFinalSelections()` + - Track as updated fixture + - If unchanged, track as unchanged + - If no match found: + - Create new fixture using `createFixture()` + - Initialize availability for all players (default false) + - Track as new fixture + - Return SyncResponse with counts + +### 3. Type Definitions +**File:** `worker/src/types.ts` + +- [ ] Add `SyncResponse` interface: + ```typescript + export interface SyncResponse { + success: boolean; + fixtures_updated: number; + fixtures_unchanged: number; + fixtures_new: number; + updated_fixture_ids: string[]; + message: string; + } + ``` + +## Frontend Implementation + +### 4. API Client +**File:** `frontend/src/lib/api/availability.ts` + +- [ ] Add `syncFixtures(teamId: string): Promise` + - POST to `/api/availability/${teamId}/sync` + - Handle response with proper error handling + - Return parsed SyncResponse + +### 5. Type Definitions +**File:** `frontend/src/lib/types/availability.ts` + +- [ ] Add `SyncResponse` interface (matching backend) + +### 6. UI Implementation +**File:** `frontend/src/routes/availability/[teamId]/+page.svelte` + +#### Tab Structure +- [ ] Create tab state management + - Add `currentTab` reactive variable (default: 'fixtures') + - Add tab navigation buttons/component + +- [ ] **Tab 1: Fixtures** + - Move existing fixture display logic here + - Show upcoming fixtures (existing upcoming section) + - Show past fixtures (existing past section) + - Keep all existing functionality (availability checkboxes, selections, edit mode) + +- [ ] **Tab 2: Stats** + - Move existing "Season Stats Summary" section here + - Display PlayerSummaryCard components + - Keep all existing stats functionality + +- [ ] **Tab 3: Manage** + - Add "Sync Fixtures" button + - Add confirmation dialog before sync + - Add loading state during sync operation + - Display sync results (fixtures updated/unchanged/new) + - Show error messages if sync fails + - Auto-reload team data after successful sync + +#### Sync Functionality +- [ ] Add `handleSync()` function + - Show confirmation dialog + - Set loading state + - Call `syncFixtures(teamId)` + - Display notification with results + - Reload team data to show updates + - Handle errors gracefully + +- [ ] Add sync state variables + - `isSyncing: boolean` - Loading state + - `syncResult: SyncResponse | null` - Last sync result + - `showSyncConfirm: boolean` - Confirmation dialog state + +## Testing + +### Backend Tests +**File:** `worker/src/database.integration.test.ts` + +- [ ] Test `getFixtureByTeams()` - matches correct fixture +- [ ] Test `getFixtureByTeams()` - returns null when no match +- [ ] Test `updateFixtureDate()` - updates date and recalculates is_past +- [ ] Test `clearAvailabilityForFixture()` - removes all availability records + +### API Tests +**File:** `worker/src/index.ts` (integration tests) + +- [ ] Test sync endpoint with unchanged fixtures (idempotent) +- [ ] Test sync endpoint with changed fixture dates +- [ ] Test sync endpoint with new fixtures +- [ ] Test sync endpoint clears availability/selections on date change +- [ ] Test sync endpoint with invalid team ID +- [ ] Test sync endpoint with scraping errors + +### Frontend Tests +**File:** `frontend/e2e/availability-validation.test.ts` + +- [ ] Test tab navigation (all three tabs render correctly) +- [ ] Test sync button click triggers confirmation +- [ ] Test successful sync updates fixture list +- [ ] Test sync error displays error message +- [ ] Test sync loading state + +## Edge Cases & Considerations + +### Fixture Matching +- [ ] Handle case-insensitive team name matching (ELTTL may change capitalization) +- [ ] Handle venue changes without triggering availability clear +- [ ] Handle fixtures with duplicate home/away teams (cup competitions?) + +### Data Integrity +- [ ] Ensure idempotency - calling sync multiple times doesn't change data +- [ ] Preserve player data even if player no longer appears in scrape +- [ ] Handle timezone issues with date comparisons + +### UI/UX +- [ ] Add loading skeletons during sync +- [ ] Show clear feedback about what changed +- [ ] Prevent concurrent sync operations +- [ ] Add "last synced" timestamp display +- [ ] Consider adding auto-sync on page load (optional) + +### Error Handling +- [ ] Handle ELTTL website unavailable +- [ ] Handle malformed HTML from scraper +- [ ] Handle database transaction failures +- [ ] Display user-friendly error messages + +## Future Enhancements +- [ ] Add webhook for automatic sync when ELTTL updates +- [ ] Track sync history in database +- [ ] Add "deleted fixture" handling (fixtures removed from ELTTL) +- [ ] Bulk notification to players when fixtures are rescheduled +- [ ] Add sync frequency limits to prevent abuse diff --git a/frontend/src/lib/api/availability.ts b/frontend/src/lib/api/availability.ts index d02b7d1..609ec86 100644 --- a/frontend/src/lib/api/availability.ts +++ b/frontend/src/lib/api/availability.ts @@ -6,6 +6,7 @@ import type { ImportTeamResponse, UpdateAvailabilityRequest, SetFinalSelectionRequest, + SyncResponse, ApiError } from '$lib/types/availability'; @@ -15,7 +16,8 @@ export type { Fixture, Player, TeamData, - PlayerSummary + PlayerSummary, + SyncResponse } from '$lib/types/availability'; export type AvailabilityMap = Record; @@ -123,3 +125,22 @@ export async function getPlayerSummary(teamId: string): Promise const data = await response.json(); return data.summary; } + +/** + * Sync fixtures from ELTTL URL + */ +export async function syncFixtures(teamId: string): Promise { + const response = await fetch(`${API_BASE_URL}/availability/${teamId}/sync`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + const error: ApiError = await response.json(); + throw new Error(error.error || 'Failed to sync fixtures'); + } + + return response.json(); +} diff --git a/frontend/src/lib/types/availability.ts b/frontend/src/lib/types/availability.ts index ac37fe8..5630178 100644 --- a/frontend/src/lib/types/availability.ts +++ b/frontend/src/lib/types/availability.ts @@ -62,6 +62,15 @@ export interface SetFinalSelectionRequest { playerIds: string[]; } +export interface SyncResponse { + success: boolean; + fixtures_updated: number; + fixtures_unchanged: number; + fixtures_new: number; + updated_fixture_ids: string[]; + message: string; +} + export interface ApiError { error: string; } diff --git a/frontend/src/routes/availability/[teamId]/+page.svelte b/frontend/src/routes/availability/[teamId]/+page.svelte index e854ca4..7548855 100644 --- a/frontend/src/routes/availability/[teamId]/+page.svelte +++ b/frontend/src/routes/availability/[teamId]/+page.svelte @@ -2,15 +2,15 @@