diff --git a/btc-UI/src/app/components/navbar/navbar.component.ts b/btc-UI/src/app/components/navbar/navbar.component.ts index c7f649c..5efc621 100755 --- a/btc-UI/src/app/components/navbar/navbar.component.ts +++ b/btc-UI/src/app/components/navbar/navbar.component.ts @@ -1,4 +1,3 @@ -// navbar.component.ts import { Component, OnInit, HostListener, OnDestroy, NgZone, Inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BtcService } from '../../service/btc.service'; @@ -24,7 +23,7 @@ export class NavbarComponent implements OnInit, OnDestroy { userName: string = ''; btid: string | null = null; - // component properties - add these at top-level in the component class + // Component properties consecTimeouts = 0; maxConsecTimeouts = 2; liveStatusOk = true; @@ -33,7 +32,7 @@ export class NavbarComponent implements OnInit, OnDestroy { showRaceModal = false; selectedVenue = 'Select Venue'; selectedRace: number = 1; - currentLegRaceDisplay: string = ''; // Display current leg's race or pool start + currentLegRaceDisplay: string = ''; showWalletModal = false; showResultModal = false; @@ -43,13 +42,12 @@ export class NavbarComponent implements OnInit, OnDestroy { raceData: any[] = []; objectKeys = Object.keys; - selectedRaceId: number = 0; // index into races array + selectedRaceId: number = 0; enabledHorseNumbers: number[] = []; multiLegBaseRaceIdx: number = 0; currentPool: string | null = null; - private prevEnabledKey = ''; // For memoization - + private prevEnabledKey = ''; wallet = { withdraw: 0, deposit: 0, @@ -71,11 +69,11 @@ export class NavbarComponent implements OnInit, OnDestroy { messages: string[] = ['System ready.', 'Please select a venue.', 'Races updated.', 'Live status stable.']; - // Stopbet-related properties (merged approach) - private stopbetStatuses: Map = new Map(); // raceNum => 'Y'|'N'|'S' + // Stopbet-related properties + private stopbetStatuses: Map = new Map(); private currentVenue: string = ''; private currentDate: string = ''; - private eventSource?: EventSource; + private eventSource: EventSource | undefined; formattedTicketLogs: { pool: string; @@ -139,51 +137,37 @@ export class NavbarComponent implements OnInit, OnDestroy { } } - // Set current venue and date - this.currentVenue = (this.raceCardData?.venue ?? '').toUpperCase(); + this.currentVenue = (this.raceCardData?.venue ?? '').toUpperCase() || localStorage.getItem('selectedVenue') || ''; this.currentDate = this.getTodayDate(); + console.log('[INIT] Current venue:', this.currentVenue, 'date:', this.currentDate); - // Setup stopbet if venue is available: - // 1) initialize StopbetService (if it uses BroadcastChannel or another approach) if (this.currentVenue) { try { this.stopbetService.initialize(this.currentVenue, this.currentDate); } catch (e) { - // initialization may not be necessary or supported by the service; ignore if fails - console.warn('[STOPBET] stopbetService.initialize failed or not required:', e); + console.warn('[STOPBET] stopbetService.initialize failed:', e); } - - // 2) fetch initial stopbets (HTTP fallback) and setup SSE stream - this.fetchInitialStopbets(); - this.setupSSE(); + } else { + console.warn('[INIT] No venue set, skipping stopbetService.initialize'); } - // 3) subscribe to StopbetService (if it provides an observable of statuses) try { - const statuses$ = (this.stopbetService as any).getStopbetStatuses?.(); + const statuses$ = this.stopbetService.getStopbetStatuses(); if (statuses$ && typeof statuses$.subscribe === 'function') { - this.stopbetSubscription = statuses$.subscribe((statuses: any) => { - // if the service gives you a map/object, merge into local stopbetStatuses - if (statuses && typeof statuses === 'object') { - try { - // expected structure: { raceNum: status, ... } or Map-like - Object.entries(statuses).forEach(([k, v]) => { - const rn = Number(k); - if (!Number.isNaN(rn)) this.stopbetStatuses.set(rn, String(v)); - }); - } catch { - // ignore malformed - } - } - // Trigger check/switch if current race is stopped + this.stopbetSubscription = statuses$.subscribe((statuses: Map) => { + this.stopbetStatuses.clear(); + statuses.forEach((value, key) => { + this.stopbetStatuses.set(key, value); + }); + console.log('[NAVBAR] Updated stopbetStatuses:', Object.fromEntries(this.stopbetStatuses)); this.checkAndSwitchIfStopped(); + this.updateEnabledHorseNumbers(); }); } } catch (e) { - console.warn('[STOPBET] subscribe to service failed:', e); + console.warn('[STOPBET] Subscribe to service failed:', e); } - // Periodic ABS/latest polling (unchanged) this.subscription = interval(5000) .pipe( switchMap(() => { @@ -198,15 +182,21 @@ export class NavbarComponent implements OnInit, OnDestroy { .subscribe((res: any) => { if (res) { this.liveStatusOk = res.success === true; - if (res.raceCardData) { this.raceCardData = res.raceCardData; localStorage.setItem('raceCardData', JSON.stringify(res.raceCardData)); + const newVenue = (this.raceCardData?.venue ?? '').toUpperCase(); + if (newVenue && newVenue !== this.currentVenue) { + console.log('[NAVBAR] Venue changed to', newVenue, 'reinitializing stopbetService'); + this.currentVenue = newVenue; + this.stopbetService.initialize(this.currentVenue, this.currentDate); + } } if (res.wallet) { this.wallet = res.wallet; } } + this.updateEnabledHorseNumbers(); }); // Subscribe to shared state updates @@ -216,7 +206,7 @@ export class NavbarComponent implements OnInit, OnDestroy { if (this.currentPool) { const leg = this.getLegIndexForRace(this.currentPool, data.value); this.currentLegRaceDisplay = `Leg ${leg + 1} (Race ${data.value}) for ${this.currentPool}`; - this.updateEnabledHorseNumbersForMultiLeg(this.multiLegBaseRaceIdx); + this.updateEnabledHorseNumbers(); } else { this.currentLegRaceDisplay = ''; this.updateEnabledHorseNumbers(); @@ -226,8 +216,9 @@ export class NavbarComponent implements OnInit, OnDestroy { const { label, baseRaceIdx } = data.value; this.currentPool = label; this.multiLegBaseRaceIdx = baseRaceIdx; + this.selectedRace = this.getValidRaceForLeg(label, 0); this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${label}`; - this.updateEnabledHorseNumbersForMultiLeg(baseRaceIdx); + this.updateEnabledHorseNumbers(); } if (data.type === 'multiLegPoolEnd') { this.currentPool = null; @@ -242,6 +233,15 @@ export class NavbarComponent implements OnInit, OnDestroy { this.selectedRace = data.value; this.updateEnabledHorseNumbers(); } + if (data.type === 'selectedVenue') { + const newVenue = data.value.toUpperCase(); + if (newVenue !== this.currentVenue) { + console.log('[NAVBAR] Selected venue changed to', newVenue); + this.currentVenue = newVenue; + localStorage.setItem('selectedVenue', newVenue); + this.stopbetService.initialize(this.currentVenue, this.currentDate); + } + } }); } @@ -253,70 +253,8 @@ export class NavbarComponent implements OnInit, OnDestroy { return `${year}/${month}/${day}`; } - /** - * HTTP-based initial fetch (fallback). - * Keeps internal stopbetStatuses Map updated with server data if available. - */ - private fetchInitialStopbets() { - if (!this.currentVenue || !this.currentDate) return; - this.http.get(`http://localhost:8080/stopbet/raw?venue=${this.currentVenue}&date=${this.currentDate}`).subscribe({ - next: (res: any) => { - if (res && res.ok && res.data) { - const key = `${this.currentVenue}:${this.currentDate}`; - const meeting = res.data[key] || {}; - for (const race in meeting) { - const raceNum = parseInt(race, 10); - this.stopbetStatuses.set(raceNum, meeting[race]); - } - // After loading, ensure current selected race is valid - this.checkAndSwitchIfStopped(); - } - }, - error: (err) => { - console.error('[STOPBET] Failed to fetch initial statuses:', err); - } - }); - } - - /** - * SSE stream setup for real-time stopbet updates. - */ - private setupSSE() { - try { - if (this.eventSource) { - this.eventSource.close(); - } - this.eventSource = new EventSource('http://localhost:8080/stopbet/stream'); - this.eventSource.onmessage = (event) => { - this.zone.run(() => { - try { - const data = JSON.parse(event.data); - if (data.type === 'stopbet' && data.venue === this.currentVenue && data.date === this.currentDate) { - const raceNum = parseInt(data.race, 10); - this.stopbetStatuses.set(raceNum, data.status); - // If current selected race is affected and now stopped, switch - if (this.selectedRace === raceNum && !this.isOpen(raceNum)) { - this.checkAndSwitchIfStopped(); - } - } - } catch (err) { - console.error('[STOPBET] SSE parse error:', err); - } - }); - }; - this.eventSource.onerror = (err) => { - console.error('[STOPBET] SSE error:', err); - // optionally attempt reconnect logic here - }; - } catch (err) { - console.warn('[STOPBET] SSE initialization failed:', err); - } - } - private isOpen(race: number): boolean { - // Prefer explicit map; if unknown assume open - const status = this.stopbetStatuses.get(race); - return status === 'N' || status === undefined; + return this.stopbetService.isOpen(race); } private getMaxRaces(): number { @@ -325,54 +263,136 @@ export class NavbarComponent implements OnInit, OnDestroy { private getOpenRaceStartingFrom(start: number) { const max = this.getMaxRaces(); - for (let r = start; r <= max; r++) { - if (this.isOpen(r)) { - return r; - } - } - for (let r = 1; r < start; r++) { - if (this.isOpen(r)) { - return r; - } - } - return start; + return this.stopbetService.getOpenRaceStartingFrom(start, max); } private checkAndSwitchIfStopped() { if (!this.isOpen(this.selectedRace)) { const nextOpen = this.getOpenRaceStartingFrom(this.selectedRace + 1); if (nextOpen !== this.selectedRace) { + console.log('[NAVBAR] Switching from stopped race', this.selectedRace, 'to', nextOpen); this.selectRace(nextOpen); } } } - private getLegIndexForRace(poolName: string, race: number): number { + private getLegIndexForRace(poolName: string | null, race: number): number { + if (!poolName) return 0; const raceMap: { [key: string]: number[] } = { mjp1: [1, 2, 3, 4], jkp1: [3, 4, 5, 6, 7], trb1: [2, 3, 4], trb2: [5, 6, 7], }; - return raceMap[poolName]?.indexOf(race) ?? 0; + return raceMap[poolName.toLowerCase()]?.indexOf(race) ?? 0; } - private updateEnabledHorseNumbersForMultiLeg(baseRaceIdx: number) { - // Defensive read of races array (structured or legacy) - const racesArr = this.raceCardData?.raceVenueRaces?.races ?? []; - const legCount = this.getLegCountForLabel(); - const key = `${this.currentPool}-${baseRaceIdx}-${legCount}`; + private getRaceForLeg(poolName: string | null, leg: number): number { + if (!poolName) return this.multiLegBaseRaceIdx + leg; + const raceMap: { [key: string]: number[] } = { + mjp1: [1, 2, 3, 4], + jkp1: [3, 4, 5, 6, 7], + trb1: [2, 3, 4], + trb2: [5, 6, 7], + }; + return raceMap[poolName.toLowerCase()]?.[leg] ?? this.multiLegBaseRaceIdx + leg; + } - if (this.prevEnabledKey === key) return; // memoization + private getValidRaceForLeg(poolName: string, leg: number): number { + const races = this.getValidRacesForPool(poolName); + return races[leg] || this.multiLegBaseRaceIdx + leg; + } + + private getValidRacesForPool(poolName: string): number[] { + const raceMap: { [key: string]: number[] } = { + mjp1: [1, 2, 3, 4], + jkp1: [3, 4, 5, 6, 7], + trb1: [2, 3, 4], + trb2: [5, 6, 7], + }; + const defaultRaces = raceMap[poolName.toLowerCase()] || []; + const maxRaces = this.getMaxRaces(); + + let earliestStoppedRace: number | null = null; + this.stopbetStatuses.forEach((status, raceNum) => { + if (status !== 'N' && status !== undefined && (earliestStoppedRace === null || raceNum < earliestStoppedRace)) { + earliestStoppedRace = raceNum; + } + }); + + const validRaces = defaultRaces.filter((race) => + earliestStoppedRace === null + ? this.isOpen(race) && race <= maxRaces + : race > earliestStoppedRace && this.isOpen(race) && race <= maxRaces + ); + console.log('[NAVBAR] Valid races for pool', poolName, ':', validRaces); + return validRaces; + } + + private getLegCountForLabel(): number { + if (!this.currentPool) return 1; + switch (this.currentPool.toLowerCase()) { + case 'mjp1': + return 4; + case 'jkp1': + return 5; + case 'trb1': + case 'trb2': + return 3; + default: + return 1; + } + } + + updateEnabledHorseNumbers() { + const key = `${this.currentPool || 'single'}-${this.selectedRace}-${this.multiLegBaseRaceIdx}`; + if (this.prevEnabledKey === key) return; this.prevEnabledKey = key; - let combinedHorseNumbers: number[] = []; + if (!this.isOpen(this.selectedRace)) { + this.enabledHorseNumbers = []; + this.sharedStateService.updateSharedData({ + type: 'enabledHorseNumbers', + value: this.enabledHorseNumbers, + }); + console.log('[NAVBAR] Race', this.selectedRace, 'is stopped, enabledHorseNumbers:', this.enabledHorseNumbers); + return; + } - const raceIndices = Array.from({ length: legCount }, (_, i) => this.getRaceForLeg(this.currentPool || '', i) - 1); + const racesArr = this.raceCardData?.raceVenueRaces?.races ?? []; + let runners: any[] = []; - for (const raceIdx of raceIndices) { - const rawRace = racesArr[raceIdx] ?? []; - let runners: any[] = []; + if (this.currentPool) { + const legCount = this.getLegCountForLabel(); + const validRaces = this.getValidRacesForPool(this.currentPool); + let combinedHorseNumbers: number[] = []; + + for (let leg = 0; leg < legCount; leg++) { + const raceNum = validRaces[leg]; + if (!raceNum || !this.isOpen(raceNum)) continue; + + const raceIdx = raceNum - 1; + const rawRace = racesArr[raceIdx] ?? []; + + if (Array.isArray(rawRace)) { + runners = rawRace; + } else if (rawRace && Array.isArray(rawRace.runners)) { + runners = rawRace.runners; + } else { + runners = []; + } + + const horses = runners + .map((runner: any) => runner?.horseNumber ?? runner?.number ?? runner?.horse_no) + .filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30); + + combinedHorseNumbers.push(...horses); + } + + this.enabledHorseNumbers = Array.from(new Set(combinedHorseNumbers)); + } else { + const raceIndex = this.selectedRace - 1; + const rawRace = racesArr[raceIndex] ?? []; if (Array.isArray(rawRace)) { runners = rawRace; @@ -382,43 +402,16 @@ export class NavbarComponent implements OnInit, OnDestroy { runners = []; } - const horses = runners - .map((runner: any) => runner?.horseNumber ?? runner?.number ?? runner?.horse_no) - .filter((n: number) => typeof n === 'number' && n >= 1 && n <= 30); - - combinedHorseNumbers.push(...horses); + this.enabledHorseNumbers = runners + .map((r: any) => r?.horseNumber ?? r?.number ?? r?.horse_no) + .filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30); } - this.enabledHorseNumbers = Array.from(new Set(combinedHorseNumbers)); - this.sharedStateService.updateSharedData({ type: 'enabledHorseNumbers', value: this.enabledHorseNumbers, }); - } - - private getLegCountForLabel(): number { - switch (this.currentPool) { - case 'mjp1': - return 4; - case 'jkp1': - return 5; - case 'trb1': - case 'trb2': - return 3; - default: - return 3; - } - } - - private getRaceForLeg(poolName: string, leg: number): number { - const raceMap: { [key: string]: number[] } = { - mjp1: [1, 2, 3, 4], - jkp1: [3, 4, 5, 6, 7], - trb1: [2, 3, 4], - trb2: [5, 6, 7], - }; - return raceMap[poolName]?.[leg] ?? this.multiLegBaseRaceIdx + leg; + console.log('[NAVBAR] Updated enabledHorseNumbers:', this.enabledHorseNumbers); } updateDateTime() { @@ -496,9 +489,8 @@ export class NavbarComponent implements OnInit, OnDestroy { this.currentVenue = newVenue; this.stopbetStatuses.clear(); if (this.currentVenue) { - this.fetchInitialStopbets(); - this.setupSSE(); try { + console.log('[NAVBAR] Initializing stopbetService for venue:', newVenue); this.stopbetService.initialize(this.currentVenue, this.currentDate); } catch {} } @@ -540,35 +532,6 @@ export class NavbarComponent implements OnInit, OnDestroy { this.closeModals(); } - updateEnabledHorseNumbers() { - const raceIndex = this.selectedRace - 1; - const racesArr = this.raceCardData?.raceVenueRaces?.races ?? []; - const rawRace = racesArr?.[raceIndex]; - - let runners: any[] = []; - - if (Array.isArray(rawRace)) { - runners = rawRace; - } else if (rawRace && Array.isArray(rawRace.runners)) { - runners = rawRace.runners; - } else { - runners = []; - } - - if (Array.isArray(runners)) { - this.enabledHorseNumbers = runners - .map((r: any) => r?.horseNumber ?? r?.number ?? r?.horse_no) - .filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30); - } else { - this.enabledHorseNumbers = []; - } - - this.sharedStateService.updateSharedData({ - type: 'enabledHorseNumbers', - value: this.enabledHorseNumbers, - }); - } - selectHorseNumber(number: number) { // Intentionally left no-op (UI hook) } @@ -625,7 +588,6 @@ export class NavbarComponent implements OnInit, OnDestroy { const horsesPartMatch = rawLabel.match(/^\w+\s+(.+?)\s+\*\d+/); if (horsesPartMatch) { horses = horsesPartMatch[1].trim(); - // Special pools split into races if (['MJP', 'JKP', 'TRE'].includes(pool)) { horsesArray = horses.split('/').map((r) => r.trim().split(',').map((h) => h.trim())); @@ -665,6 +627,7 @@ export class NavbarComponent implements OnInit, OnDestroy { logout(): void { const name = localStorage.getItem('userName') || 'Unknown User'; const employeeId = localStorage.getItem('employeeId') || '000000'; + const stopbetData = localStorage.getItem('stopbetStatuses'); // Save stopbetStatuses const printData = { name, @@ -682,11 +645,20 @@ export class NavbarComponent implements OnInit, OnDestroy { if (!res.ok) throw new Error('Logout print failed'); (window as any).electronAPI?.closeSecondScreen?.(); localStorage.clear(); + if (stopbetData) { + localStorage.setItem('stopbetStatuses', stopbetData); + console.log('[NAVBAR] Preserved stopbetStatuses in localStorage:', stopbetData); + } this.router.navigate(['/logout']); }) .catch((err) => { + console.error('[NAVBAR] Logout error:', err); (window as any).electronAPI?.closeSecondScreen?.(); localStorage.clear(); + if (stopbetData) { + localStorage.setItem('stopbetStatuses', stopbetData); + console.log('[NAVBAR] Preserved stopbetStatuses in localStorage:', stopbetData); + } this.router.navigate(['/logout']); }); } @@ -709,4 +681,4 @@ export class NavbarComponent implements OnInit, OnDestroy { trackByHorse(index: number, item: number): number { return item; } -} +} \ No newline at end of file diff --git a/btc-UI/src/app/service/stopbet.service.ts b/btc-UI/src/app/service/stopbet.service.ts index 5bcce7f..749ee52 100644 --- a/btc-UI/src/app/service/stopbet.service.ts +++ b/btc-UI/src/app/service/stopbet.service.ts @@ -1,4 +1,3 @@ -// stopbet.service.ts import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; @@ -11,6 +10,7 @@ export class StopbetService { private currentVenue: string = ''; private currentDate: string = ''; private eventSource?: EventSource; + private readonly STORAGE_KEY = 'stopbetStatuses'; constructor(private http: HttpClient, private zone: NgZone) {} @@ -21,30 +21,130 @@ export class StopbetService { // Initialize stopbet for a venue and date initialize(venue: string, date: string) { - this.currentVenue = venue.toUpperCase(); - this.currentDate = date; + const newVenue = venue ? venue.toUpperCase() : ''; + const newDate = date || this.getTodayDate(); + + // Only clear if venue and date are valid and different + if (newVenue && newDate && (this.currentVenue !== newVenue || this.currentDate !== newDate)) { + console.log( + `[STOPBET] Venue or date changed (from ${this.currentVenue}:${this.currentDate} to ${newVenue}:${newDate}), clearing stopbetStatuses` + ); + this.stopbetStatuses.clear(); + localStorage.removeItem(this.STORAGE_KEY); + this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses)); + } else { + console.log( + `[STOPBET] No clear needed: newVenue=${newVenue}, newDate=${newDate}, currentVenue=${this.currentVenue}, currentDate=${this.currentDate}` + ); + } + + this.currentVenue = newVenue; + this.currentDate = newDate; + + // Load from localStorage first + this.loadFromLocalStorage(); + + // Fetch from server and setup SSE this.fetchInitialStopbets(); this.setupSSE(); } - private fetchInitialStopbets() { - this.http.get(`http://localhost:8080/stopbet/raw?venue=${this.currentVenue}&date=${this.currentDate}`).subscribe({ - next: (res: any) => { - if (res.ok && res.data) { - const key = `${this.currentVenue}:${this.currentDate}`; - const meeting = res.data[key] || {}; - this.stopbetStatuses.clear(); - for (const race in meeting) { - const raceNum = parseInt(race, 10); - this.stopbetStatuses.set(raceNum, meeting[race]); - } - this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses)); + // Public method to set race status + setStatus(raceNum: number, status: string) { + const oldStatus = this.stopbetStatuses.get(raceNum); + if (oldStatus === status) { + console.log(`[STOPBET] No change for race ${raceNum}: status remains ${status}`); + return; + } + + // Set the specified race status + this.stopbetStatuses.set(raceNum, status); + + // If stopping this race, auto-disable previous races + if (status === 'Y' || status === 'S') { + for (let prevRace = 1; prevRace < raceNum; prevRace++) { + const prevStatus = this.stopbetStatuses.get(prevRace); + if (prevStatus === 'N' || prevStatus === undefined) { + console.log(`[STOPBET] Auto-stopping race ${prevRace} due to race ${raceNum} being stopped`); + this.stopbetStatuses.set(prevRace, 'Y'); } - }, - error: (err) => { - console.error('[STOPBET] Failed to fetch initial statuses:', err); } - }); + } + + // Emit updated statuses and save to localStorage + this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses)); + this.saveToLocalStorage(); + } + + private loadFromLocalStorage() { + const cached = localStorage.getItem(this.STORAGE_KEY); + if (cached) { + try { + const parsed = JSON.parse(cached); + if (parsed.venue === this.currentVenue && parsed.date === this.currentDate) { + this.stopbetStatuses.clear(); + for (const [race, status] of Object.entries(parsed.statuses)) { + const raceNum = parseInt(race, 10); + if (!isNaN(raceNum) && ['Y', 'N', 'S'].includes(status as string)) { + this.stopbetStatuses.set(raceNum, status as string); + } + } + console.log('[STOPBET] Loaded statuses from localStorage:', this.stopbetStatuses); + this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses)); + } else { + console.log( + `[STOPBET] localStorage data invalid for venue=${this.currentVenue}, date=${this.currentDate}:`, + parsed + ); + } + } catch (err) { + console.error('[STOPBET] Failed to parse localStorage:', err); + localStorage.removeItem(this.STORAGE_KEY); + } + } else { + console.log('[STOPBET] No stopbetStatuses found in localStorage'); + } + } + + private saveToLocalStorage() { + try { + const data = { + venue: this.currentVenue, + date: this.currentDate, + statuses: Object.fromEntries(this.stopbetStatuses), + }; + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data)); + console.log('[STOPBET] Saved statuses to localStorage:', data); + } catch (err) { + console.error('[STOPBET] Failed to save to localStorage:', err); + } + } + + private fetchInitialStopbets() { + this.http + .get(`http://localhost:8080/stopbet/raw?venue=${this.currentVenue}&date=${this.currentDate}`) + .subscribe({ + next: (res: any) => { + if (res.ok && res.data) { + const key = `${this.currentVenue}:${this.currentDate}`; + const meeting = res.data[key] || {}; + this.stopbetStatuses.clear(); + for (const race in meeting) { + const raceNum = parseInt(race, 10); + const status = meeting[race]; + this.setStatus(raceNum, status); + } + console.log('[STOPBET] Fetched initial statuses:', this.stopbetStatuses); + this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses)); + this.saveToLocalStorage(); + } else { + console.warn('[STOPBET] Invalid response from fetchInitialStopbets:', res); + } + }, + error: (err) => { + console.error('[STOPBET] Failed to fetch initial statuses:', err); + }, + }); } private setupSSE() { @@ -58,8 +158,8 @@ export class StopbetService { const data = JSON.parse(event.data); if (data.type === 'stopbet' && data.venue === this.currentVenue && data.date === this.currentDate) { const raceNum = parseInt(data.race, 10); - this.stopbetStatuses.set(raceNum, data.status); - this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses)); + this.setStatus(raceNum, data.status); + console.log('[STOPBET] SSE updated race', raceNum, 'to', data.status); } } catch (err) { console.error('[STOPBET] SSE parse error:', err); @@ -86,6 +186,14 @@ export class StopbetService { return start; } + private getTodayDate(): string { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + return `${year}/${month}/${day}`; + } + ngOnDestroy() { if (this.eventSource) { this.eventSource.close();