Feature Requests

Add Venue Filter to Analytics
## Overview Add venue filtering to the analytics filter panel so users can analyze their performance at specific venues (LGS, tournaments, online platforms). Type: Feature | Priority: Medium | Complexity: XS (30-60 min) --- ## Technical Context Backend Status: COMPLETE - Venue filtering is fully implemented: AnalyticsFilters dataclass has venue_id field (apps/backend/apps/analytics/services/ base.py:29 ) OpenAPI schema includes venue_id parameter (apps/backend/apps/analytics/views/ analytics.py:107 -112) Query filtering applied in _apply_match_filters and _apply_game_filters Frontend Status: MISSING - Venue filter not exposed in filter panel despite backend support. --- ## Implementation ### Files to Modify apps/frontend/src/components/analytics/analytics-filters.tsx: Add to AnalyticsFiltersState interface: - venue_id?: string Add to AnalyticsApiFilters interface: - venue_id?: string Update toApiFilters() to include venue_id Add venue data fetching: - Use venuesListOptions from hey-api - staleTime: 300000 (5 min, matches other filters) Add Select component for venue (single-select, not multi) - Label: "Venue" - Placeholder: "All venues" Update activeFilterCount to include venue_id ### Patterns to Follow Use Select component (single value) - backend uses venue_id not venue_ids Follow format filter implementation pattern Position after Format filter in grid --- ## Acceptance Criteria Venue filter appears in desktop card view and mobile sheet Shows active venues from team Filters all analytics endpoints when selected Included in active filter count badge Clear All and Apply Filters work correctly Design system compliant (sharp corners, borders, 150ms transitions) TypeScript compiles without errors --- ## Questions Multi-venue filtering: Should backend be enhanced to support venue_ids (comma-separated list) like other filters? Filter position: After Format, or new row to avoid crowding? Venue grouping: Group by type in dropdown or flat alphabetical list? --- ## No Backend Changes Required All backend infrastructure is complete.
3
·
complete
Fix matches filtering lag and broken format filter
## Summary Two issues on the matches page: Lag in filtering : Noticeable lag when using filters Format filter not working : Format filter does not apply correctly --- ## Technical Analysis ### Issue 1: Filtering Lag Root Cause : No debouncing on filter changes. The MatchesFilters component calls onFiltersChange immediately on every filter change, triggering instant API calls. Affected Files : apps/frontend/src/components/matches/filters/matches-filters.tsx - Filter UI (line 74-79) apps/frontend/src/hooks/useMatches.ts - Hook that manages filter state (line 81-84) Fix : Add debouncing to useMatches.ts using the existing useDebounce hook: ```typescript const debouncedFilters = useDebounce(filters, 300); // Use debouncedFilters in queryParams instead of filters ``` ### Issue 2: Format Filter Not Working Current Flow : Frontend sends format=<uuid> via query params Backend MatchFilter has format = filters.UUIDFilter(field_name="format_id") Investigation Needed : Debug to confirm the format UUID is being passed correctly. Check browser network tab and Django backend logs. Affected Files : apps/frontend/src/components/matches/filters/matches-filters.tsx (line 229-247) apps/backend/apps/matches/views/match.py - MatchFilter class (line 42) --- ## Implementation Steps Add debouncing (300ms) to useMatches.ts filter state Add console logging to debug format filter Write backend test for format filtering Verify all filters work: date, result, deck, format, player ## Acceptance Criteria [ ] Filters debounced by 300ms (no lag) [ ] Format filter correctly filters matches [ ] All other filters still work [ ] Loading state shown during filter application [ ] Backend tests cover format filtering ## Complexity: Medium (M)
3
·
complete
InlineMatchFormRow: Add Opponent Session Deck Pin + Auto-Open New Row Preference
## Summary Two UX improvements for fast match entry during playtesting sessions: Opponent Session Deck Pinning - Pin opponent's deck/archetype for a session (like user's deck pin) Auto-Open New Row Preference - Toggle to auto-open new form row after submitting --- ## Feature 1: Opponent Session Deck Pinning Current: Users can pin their OWN deck as a "session deck" that pre-populates new match forms. Requested: Also allow pinning the OPPONENT's deck/archetype. Useful when testing against the same deck repeatedly. Affected Files: apps/frontend/src/contexts/session-deck-context.tsx - Add sessionOpponentDeck state, setSessionOpponentDeck() , isSessionOpponentDeck() methods. Add second sessionStorage key. apps/frontend/src/components/matches/forms/InlineMatchFormRow.tsx (lines 329-348) - Pass onSetSessionDeck and isSessionDeck props to opponent's DeckArchetypeSelect component. apps/frontend/src/components/matches/hooks/useInlineMatchForm.ts (lines 62-88, 148-163) - Update createInitialFormState() and sync effect to pre-populate opponent fields from session. --- ## Feature 2: Auto-Open New Row Preference Current: Form closes after submit. User clicks "Add Match" again for next entry. Requested: Toggle to keep form open after submit. New form appears with session decks preserved. Affected Files: apps/frontend/src/components/matches/tables/inline-matches-table.tsx (lines 89, 117-120) - Add autoOpenNext state with localStorage persistence. Modify handleFormSuccess() to conditionally keep isAdding true. --- ## Acceptance Criteria Feature 1: [ ] Pin button appears in opponent's DeckArchetypeSelect [ ] Session opponent deck persists in sessionStorage [ ] New forms pre-populate opponent field from session [ ] Visual indicator when opponent deck is pinned Feature 2: [ ] Toggle near "Add Match" button or in form actions [ ] When enabled, new form opens after submit [ ] Session deck values preserved in new form [ ] Opponent name input auto-focused [ ] Preference persists in localStorage [ ] Default: OFF --- ## Questions Auto-open toggle placement: Next to "Add Match" button or in form row actions? Should mobile sheet also support auto-open? Different icon/color for opponent session deck indicator? --- ## Estimated Complexity: S-M (3-5 hours) Feature 1: ~2-3 hours (context + component changes) Feature 2: ~1-2 hours (state + localStorage + UI)
3
·
complete
Alternating Play/Draw Button Should Cycle Through Both Starting Patterns
## Summary When clicking the "Alternate" quick fill button in the "Edit Games" modal, the user can currently set games to alternate play/draw starting with play first (G1=play, G2=draw, G3=play, etc.). Feature Request : Clicking the "Alternate" button again should swap to the opposite starting pattern (G1=draw, G2=play, G3=draw, etc.), allowing users to quickly toggle between both alternation patterns. --- ## Current Behavior The applyAlternate() function always starts with play on Game 1: Click 1 : G1=play, G2=draw, G3=play, G4=draw... Click 2 : Same as Click 1 (no change) ## Desired Behavior The button should cycle through both alternation patterns: Click 1 : G1=play, G2=draw, G3=play, G4=draw... (starts with play) Click 2 : G1=draw, G2=play, G3=draw, G4=play... (starts with draw) Click 3 : Back to G1=play pattern (repeats) --- ## Technical Context ### Files to Modify ** apps/frontend/src/components/matches/forms/game-details-utils.ts ** - Current applyAlternate() function (lines 209-214) always returns games with onPlay: idx % 2 === 0 - Option A : Add a new function applyAlternateFromDraw() that starts with draw - Option B : Modify applyAlternate() to accept a startWithPlay: boolean parameter ** apps/frontend/src/components/matches/forms/GameDetailsModal.tsx ** - The handleAlternate() handler (line 106) needs to detect the current pattern and swap to the opposite - May need to add local state or use a ref to track which alternation pattern was last applied - Alternatively, detect current pattern by checking games[0].onPlay value ** apps/frontend/src/components/matches/forms/QuickFillButtons.tsx ** - The tooltip (line 116) says "Alternate - G1=play, G2=draw, G3=play, etc." - Should update to indicate the cycling behavior, e.g., "Alternate - Toggle between play-first and draw-first patterns" ** apps/frontend/src/components/matches/mobile/game-editing-sheet-mobile.tsx ** - Same change needed for mobile consistency (line 124) - The button text "Alternate" (lines 192-201) may need a visual indicator of current state ### Recommended Implementation Approach Detection-based approach (simpler, no new state required): ```typescript // In game-details-utils.ts - add new function export function applyAlternate(games: GameEntry[], startWithPlay: boolean = true): GameEntry[] { return games.map ((g, idx) => ({ ...g, onPlay: startWithPlay ? idx % 2 === 0 : idx % 2 !== 0, })); } // In GameDetailsModal.tsx - detect current pattern and swap const handleAlternate = () => { // Check if currently in play-first alternating pattern const isPlayFirst = games.length > 0 && games[0].onPlay === true; const isAlternating = games.every((g, idx) => g.onPlay === (idx % 2 === 0) || g.onPlay === (idx % 2 !== 0) ); // If already alternating, swap starting position; otherwise default to play-first const startWithPlay = isAlternating && isPlayFirst ? false : true; onGamesChange(applyAlternate(games, startWithPlay)); }; ### Type Definition Reference ```typescript // From apps/frontend/src/components/matches/forms/types.ts interface GameEntry { id: string; gameNumber: number; winner: "me" | "opponent" | "draw"; onPlay?: boolean; // true = on play, false = on draw, undefined = unknown player1Battlefield?: string | null; player2Battlefield?: string | null; } --- ## Acceptance Criteria [ ] Clicking "Alternate" button the first time sets G1=play, G2=draw, G3=play pattern [ ] Clicking "Alternate" button again swaps to G1=draw, G2=play, G3=draw pattern [ ] Clicking a third time returns to the play-first pattern [ ] Works correctly for any number of games (1-15) [ ] Works in both desktop modal ( GameDetailsModal.tsx ) and mobile sheet ( game-editing-sheet-mobile.tsx ) [ ] Tooltip/label updated to indicate cycling behavior [ ] If games are in a non-alternating state (e.g., all play or mixed), first click defaults to play-first pattern --- ## Questions for Stakeholder Visual feedback : Should the button show any visual indication of which alternation mode is currently active? (e.g., subtle icon change or toggle state) Default behavior : When games are in a completely random state (not alternating), should the first click always default to play-first, or should it detect the majority pattern? Edge case : If a user manually changes one game's play/draw after using Alternate, should the next Alternate click still cycle, or reset to play-first? --- ## Estimated Complexity T-shirt Size: XS (Extra Small) This is a straightforward logic change in a single utility function with minimal UI updates. The implementation is well-isolated to the quick fill feature and has no backend changes required.
3
·
complete