fix : stop bet (local storage)
This commit is contained in:
parent
bd4f1e21d0
commit
89009d28c4
@ -1,4 +1,3 @@
|
|||||||
// navbar.component.ts
|
|
||||||
import { Component, OnInit, HostListener, OnDestroy, NgZone, Inject } from '@angular/core';
|
import { Component, OnInit, HostListener, OnDestroy, NgZone, Inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { BtcService } from '../../service/btc.service';
|
import { BtcService } from '../../service/btc.service';
|
||||||
@ -24,7 +23,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
userName: string = '';
|
userName: string = '';
|
||||||
btid: string | null = null;
|
btid: string | null = null;
|
||||||
|
|
||||||
// component properties - add these at top-level in the component class
|
// Component properties
|
||||||
consecTimeouts = 0;
|
consecTimeouts = 0;
|
||||||
maxConsecTimeouts = 2;
|
maxConsecTimeouts = 2;
|
||||||
liveStatusOk = true;
|
liveStatusOk = true;
|
||||||
@ -33,7 +32,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
showRaceModal = false;
|
showRaceModal = false;
|
||||||
selectedVenue = 'Select Venue';
|
selectedVenue = 'Select Venue';
|
||||||
selectedRace: number = 1;
|
selectedRace: number = 1;
|
||||||
currentLegRaceDisplay: string = ''; // Display current leg's race or pool start
|
currentLegRaceDisplay: string = '';
|
||||||
|
|
||||||
showWalletModal = false;
|
showWalletModal = false;
|
||||||
showResultModal = false;
|
showResultModal = false;
|
||||||
@ -43,13 +42,12 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
raceData: any[] = [];
|
raceData: any[] = [];
|
||||||
objectKeys = Object.keys;
|
objectKeys = Object.keys;
|
||||||
|
|
||||||
selectedRaceId: number = 0; // index into races array
|
selectedRaceId: number = 0;
|
||||||
enabledHorseNumbers: number[] = [];
|
enabledHorseNumbers: number[] = [];
|
||||||
multiLegBaseRaceIdx: number = 0;
|
multiLegBaseRaceIdx: number = 0;
|
||||||
currentPool: string | null = null;
|
currentPool: string | null = null;
|
||||||
|
|
||||||
private prevEnabledKey = ''; // For memoization
|
private prevEnabledKey = '';
|
||||||
|
|
||||||
wallet = {
|
wallet = {
|
||||||
withdraw: 0,
|
withdraw: 0,
|
||||||
deposit: 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.'];
|
messages: string[] = ['System ready.', 'Please select a venue.', 'Races updated.', 'Live status stable.'];
|
||||||
|
|
||||||
// Stopbet-related properties (merged approach)
|
// Stopbet-related properties
|
||||||
private stopbetStatuses: Map<number, string> = new Map(); // raceNum => 'Y'|'N'|'S'
|
private stopbetStatuses: Map<number, string> = new Map();
|
||||||
private currentVenue: string = '';
|
private currentVenue: string = '';
|
||||||
private currentDate: string = '';
|
private currentDate: string = '';
|
||||||
private eventSource?: EventSource;
|
private eventSource: EventSource | undefined;
|
||||||
|
|
||||||
formattedTicketLogs: {
|
formattedTicketLogs: {
|
||||||
pool: string;
|
pool: string;
|
||||||
@ -139,51 +137,37 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set current venue and date
|
this.currentVenue = (this.raceCardData?.venue ?? '').toUpperCase() || localStorage.getItem('selectedVenue') || '';
|
||||||
this.currentVenue = (this.raceCardData?.venue ?? '').toUpperCase();
|
|
||||||
this.currentDate = this.getTodayDate();
|
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) {
|
if (this.currentVenue) {
|
||||||
try {
|
try {
|
||||||
this.stopbetService.initialize(this.currentVenue, this.currentDate);
|
this.stopbetService.initialize(this.currentVenue, this.currentDate);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// initialization may not be necessary or supported by the service; ignore if fails
|
console.warn('[STOPBET] stopbetService.initialize failed:', e);
|
||||||
console.warn('[STOPBET] stopbetService.initialize failed or not required:', e);
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[INIT] No venue set, skipping stopbetService.initialize');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) fetch initial stopbets (HTTP fallback) and setup SSE stream
|
|
||||||
this.fetchInitialStopbets();
|
|
||||||
this.setupSSE();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) subscribe to StopbetService (if it provides an observable of statuses)
|
|
||||||
try {
|
try {
|
||||||
const statuses$ = (this.stopbetService as any).getStopbetStatuses?.();
|
const statuses$ = this.stopbetService.getStopbetStatuses();
|
||||||
if (statuses$ && typeof statuses$.subscribe === 'function') {
|
if (statuses$ && typeof statuses$.subscribe === 'function') {
|
||||||
this.stopbetSubscription = statuses$.subscribe((statuses: any) => {
|
this.stopbetSubscription = statuses$.subscribe((statuses: Map<number, string>) => {
|
||||||
// if the service gives you a map/object, merge into local stopbetStatuses
|
this.stopbetStatuses.clear();
|
||||||
if (statuses && typeof statuses === 'object') {
|
statuses.forEach((value, key) => {
|
||||||
try {
|
this.stopbetStatuses.set(key, value);
|
||||||
// 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 {
|
console.log('[NAVBAR] Updated stopbetStatuses:', Object.fromEntries(this.stopbetStatuses));
|
||||||
// ignore malformed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Trigger check/switch if current race is stopped
|
|
||||||
this.checkAndSwitchIfStopped();
|
this.checkAndSwitchIfStopped();
|
||||||
|
this.updateEnabledHorseNumbers();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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)
|
this.subscription = interval(5000)
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
@ -198,15 +182,21 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
.subscribe((res: any) => {
|
.subscribe((res: any) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
this.liveStatusOk = res.success === true;
|
this.liveStatusOk = res.success === true;
|
||||||
|
|
||||||
if (res.raceCardData) {
|
if (res.raceCardData) {
|
||||||
this.raceCardData = res.raceCardData;
|
this.raceCardData = res.raceCardData;
|
||||||
localStorage.setItem('raceCardData', JSON.stringify(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) {
|
if (res.wallet) {
|
||||||
this.wallet = res.wallet;
|
this.wallet = res.wallet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.updateEnabledHorseNumbers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to shared state updates
|
// Subscribe to shared state updates
|
||||||
@ -216,7 +206,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
if (this.currentPool) {
|
if (this.currentPool) {
|
||||||
const leg = this.getLegIndexForRace(this.currentPool, data.value);
|
const leg = this.getLegIndexForRace(this.currentPool, data.value);
|
||||||
this.currentLegRaceDisplay = `Leg ${leg + 1} (Race ${data.value}) for ${this.currentPool}`;
|
this.currentLegRaceDisplay = `Leg ${leg + 1} (Race ${data.value}) for ${this.currentPool}`;
|
||||||
this.updateEnabledHorseNumbersForMultiLeg(this.multiLegBaseRaceIdx);
|
this.updateEnabledHorseNumbers();
|
||||||
} else {
|
} else {
|
||||||
this.currentLegRaceDisplay = '';
|
this.currentLegRaceDisplay = '';
|
||||||
this.updateEnabledHorseNumbers();
|
this.updateEnabledHorseNumbers();
|
||||||
@ -226,8 +216,9 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
const { label, baseRaceIdx } = data.value;
|
const { label, baseRaceIdx } = data.value;
|
||||||
this.currentPool = label;
|
this.currentPool = label;
|
||||||
this.multiLegBaseRaceIdx = baseRaceIdx;
|
this.multiLegBaseRaceIdx = baseRaceIdx;
|
||||||
|
this.selectedRace = this.getValidRaceForLeg(label, 0);
|
||||||
this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${label}`;
|
this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${label}`;
|
||||||
this.updateEnabledHorseNumbersForMultiLeg(baseRaceIdx);
|
this.updateEnabledHorseNumbers();
|
||||||
}
|
}
|
||||||
if (data.type === 'multiLegPoolEnd') {
|
if (data.type === 'multiLegPoolEnd') {
|
||||||
this.currentPool = null;
|
this.currentPool = null;
|
||||||
@ -242,6 +233,15 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
this.selectedRace = data.value;
|
this.selectedRace = data.value;
|
||||||
this.updateEnabledHorseNumbers();
|
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}`;
|
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 {
|
private isOpen(race: number): boolean {
|
||||||
// Prefer explicit map; if unknown assume open
|
return this.stopbetService.isOpen(race);
|
||||||
const status = this.stopbetStatuses.get(race);
|
|
||||||
return status === 'N' || status === undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMaxRaces(): number {
|
private getMaxRaces(): number {
|
||||||
@ -325,54 +263,116 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private getOpenRaceStartingFrom(start: number) {
|
private getOpenRaceStartingFrom(start: number) {
|
||||||
const max = this.getMaxRaces();
|
const max = this.getMaxRaces();
|
||||||
for (let r = start; r <= max; r++) {
|
return this.stopbetService.getOpenRaceStartingFrom(start, max);
|
||||||
if (this.isOpen(r)) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let r = 1; r < start; r++) {
|
|
||||||
if (this.isOpen(r)) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkAndSwitchIfStopped() {
|
private checkAndSwitchIfStopped() {
|
||||||
if (!this.isOpen(this.selectedRace)) {
|
if (!this.isOpen(this.selectedRace)) {
|
||||||
const nextOpen = this.getOpenRaceStartingFrom(this.selectedRace + 1);
|
const nextOpen = this.getOpenRaceStartingFrom(this.selectedRace + 1);
|
||||||
if (nextOpen !== this.selectedRace) {
|
if (nextOpen !== this.selectedRace) {
|
||||||
|
console.log('[NAVBAR] Switching from stopped race', this.selectedRace, 'to', nextOpen);
|
||||||
this.selectRace(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[] } = {
|
const raceMap: { [key: string]: number[] } = {
|
||||||
mjp1: [1, 2, 3, 4],
|
mjp1: [1, 2, 3, 4],
|
||||||
jkp1: [3, 4, 5, 6, 7],
|
jkp1: [3, 4, 5, 6, 7],
|
||||||
trb1: [2, 3, 4],
|
trb1: [2, 3, 4],
|
||||||
trb2: [5, 6, 7],
|
trb2: [5, 6, 7],
|
||||||
};
|
};
|
||||||
return raceMap[poolName]?.indexOf(race) ?? 0;
|
return raceMap[poolName.toLowerCase()]?.indexOf(race) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateEnabledHorseNumbersForMultiLeg(baseRaceIdx: number) {
|
private getRaceForLeg(poolName: string | null, leg: number): number {
|
||||||
// Defensive read of races array (structured or legacy)
|
if (!poolName) return this.multiLegBaseRaceIdx + leg;
|
||||||
const racesArr = this.raceCardData?.raceVenueRaces?.races ?? [];
|
const raceMap: { [key: string]: number[] } = {
|
||||||
const legCount = this.getLegCountForLabel();
|
mjp1: [1, 2, 3, 4],
|
||||||
const key = `${this.currentPool}-${baseRaceIdx}-${legCount}`;
|
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;
|
this.prevEnabledKey = key;
|
||||||
|
|
||||||
|
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 racesArr = this.raceCardData?.raceVenueRaces?.races ?? [];
|
||||||
|
let runners: any[] = [];
|
||||||
|
|
||||||
|
if (this.currentPool) {
|
||||||
|
const legCount = this.getLegCountForLabel();
|
||||||
|
const validRaces = this.getValidRacesForPool(this.currentPool);
|
||||||
let combinedHorseNumbers: number[] = [];
|
let combinedHorseNumbers: number[] = [];
|
||||||
|
|
||||||
const raceIndices = Array.from({ length: legCount }, (_, i) => this.getRaceForLeg(this.currentPool || '', i) - 1);
|
for (let leg = 0; leg < legCount; leg++) {
|
||||||
|
const raceNum = validRaces[leg];
|
||||||
|
if (!raceNum || !this.isOpen(raceNum)) continue;
|
||||||
|
|
||||||
for (const raceIdx of raceIndices) {
|
const raceIdx = raceNum - 1;
|
||||||
const rawRace = racesArr[raceIdx] ?? [];
|
const rawRace = racesArr[raceIdx] ?? [];
|
||||||
let runners: any[] = [];
|
|
||||||
|
|
||||||
if (Array.isArray(rawRace)) {
|
if (Array.isArray(rawRace)) {
|
||||||
runners = rawRace;
|
runners = rawRace;
|
||||||
@ -384,41 +384,34 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const horses = runners
|
const horses = runners
|
||||||
.map((runner: any) => runner?.horseNumber ?? runner?.number ?? runner?.horse_no)
|
.map((runner: any) => runner?.horseNumber ?? runner?.number ?? runner?.horse_no)
|
||||||
.filter((n: number) => typeof n === 'number' && n >= 1 && n <= 30);
|
.filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30);
|
||||||
|
|
||||||
combinedHorseNumbers.push(...horses);
|
combinedHorseNumbers.push(...horses);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.enabledHorseNumbers = Array.from(new Set(combinedHorseNumbers));
|
this.enabledHorseNumbers = Array.from(new Set(combinedHorseNumbers));
|
||||||
|
} else {
|
||||||
|
const raceIndex = this.selectedRace - 1;
|
||||||
|
const rawRace = racesArr[raceIndex] ?? [];
|
||||||
|
|
||||||
|
if (Array.isArray(rawRace)) {
|
||||||
|
runners = rawRace;
|
||||||
|
} else if (rawRace && Array.isArray(rawRace.runners)) {
|
||||||
|
runners = rawRace.runners;
|
||||||
|
} else {
|
||||||
|
runners = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.enabledHorseNumbers = runners
|
||||||
|
.map((r: any) => r?.horseNumber ?? r?.number ?? r?.horse_no)
|
||||||
|
.filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30);
|
||||||
|
}
|
||||||
|
|
||||||
this.sharedStateService.updateSharedData({
|
this.sharedStateService.updateSharedData({
|
||||||
type: 'enabledHorseNumbers',
|
type: 'enabledHorseNumbers',
|
||||||
value: this.enabledHorseNumbers,
|
value: this.enabledHorseNumbers,
|
||||||
});
|
});
|
||||||
}
|
console.log('[NAVBAR] Updated enabledHorseNumbers:', 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDateTime() {
|
updateDateTime() {
|
||||||
@ -496,9 +489,8 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
this.currentVenue = newVenue;
|
this.currentVenue = newVenue;
|
||||||
this.stopbetStatuses.clear();
|
this.stopbetStatuses.clear();
|
||||||
if (this.currentVenue) {
|
if (this.currentVenue) {
|
||||||
this.fetchInitialStopbets();
|
|
||||||
this.setupSSE();
|
|
||||||
try {
|
try {
|
||||||
|
console.log('[NAVBAR] Initializing stopbetService for venue:', newVenue);
|
||||||
this.stopbetService.initialize(this.currentVenue, this.currentDate);
|
this.stopbetService.initialize(this.currentVenue, this.currentDate);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
@ -540,35 +532,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
this.closeModals();
|
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) {
|
selectHorseNumber(number: number) {
|
||||||
// Intentionally left no-op (UI hook)
|
// Intentionally left no-op (UI hook)
|
||||||
}
|
}
|
||||||
@ -625,7 +588,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
const horsesPartMatch = rawLabel.match(/^\w+\s+(.+?)\s+\*\d+/);
|
const horsesPartMatch = rawLabel.match(/^\w+\s+(.+?)\s+\*\d+/);
|
||||||
if (horsesPartMatch) {
|
if (horsesPartMatch) {
|
||||||
horses = horsesPartMatch[1].trim();
|
horses = horsesPartMatch[1].trim();
|
||||||
|
|
||||||
// Special pools split into races
|
// Special pools split into races
|
||||||
if (['MJP', 'JKP', 'TRE'].includes(pool)) {
|
if (['MJP', 'JKP', 'TRE'].includes(pool)) {
|
||||||
horsesArray = horses.split('/').map((r) => r.trim().split(',').map((h) => h.trim()));
|
horsesArray = horses.split('/').map((r) => r.trim().split(',').map((h) => h.trim()));
|
||||||
@ -665,6 +627,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
logout(): void {
|
logout(): void {
|
||||||
const name = localStorage.getItem('userName') || 'Unknown User';
|
const name = localStorage.getItem('userName') || 'Unknown User';
|
||||||
const employeeId = localStorage.getItem('employeeId') || '000000';
|
const employeeId = localStorage.getItem('employeeId') || '000000';
|
||||||
|
const stopbetData = localStorage.getItem('stopbetStatuses'); // Save stopbetStatuses
|
||||||
|
|
||||||
const printData = {
|
const printData = {
|
||||||
name,
|
name,
|
||||||
@ -682,11 +645,20 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
if (!res.ok) throw new Error('Logout print failed');
|
if (!res.ok) throw new Error('Logout print failed');
|
||||||
(window as any).electronAPI?.closeSecondScreen?.();
|
(window as any).electronAPI?.closeSecondScreen?.();
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
if (stopbetData) {
|
||||||
|
localStorage.setItem('stopbetStatuses', stopbetData);
|
||||||
|
console.log('[NAVBAR] Preserved stopbetStatuses in localStorage:', stopbetData);
|
||||||
|
}
|
||||||
this.router.navigate(['/logout']);
|
this.router.navigate(['/logout']);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
console.error('[NAVBAR] Logout error:', err);
|
||||||
(window as any).electronAPI?.closeSecondScreen?.();
|
(window as any).electronAPI?.closeSecondScreen?.();
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
if (stopbetData) {
|
||||||
|
localStorage.setItem('stopbetStatuses', stopbetData);
|
||||||
|
console.log('[NAVBAR] Preserved stopbetStatuses in localStorage:', stopbetData);
|
||||||
|
}
|
||||||
this.router.navigate(['/logout']);
|
this.router.navigate(['/logout']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// stopbet.service.ts
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
@ -11,6 +10,7 @@ export class StopbetService {
|
|||||||
private currentVenue: string = '';
|
private currentVenue: string = '';
|
||||||
private currentDate: string = '';
|
private currentDate: string = '';
|
||||||
private eventSource?: EventSource;
|
private eventSource?: EventSource;
|
||||||
|
private readonly STORAGE_KEY = 'stopbetStatuses';
|
||||||
|
|
||||||
constructor(private http: HttpClient, private zone: NgZone) {}
|
constructor(private http: HttpClient, private zone: NgZone) {}
|
||||||
|
|
||||||
@ -21,14 +21,109 @@ export class StopbetService {
|
|||||||
|
|
||||||
// Initialize stopbet for a venue and date
|
// Initialize stopbet for a venue and date
|
||||||
initialize(venue: string, date: string) {
|
initialize(venue: string, date: string) {
|
||||||
this.currentVenue = venue.toUpperCase();
|
const newVenue = venue ? venue.toUpperCase() : '';
|
||||||
this.currentDate = date;
|
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.fetchInitialStopbets();
|
||||||
this.setupSSE();
|
this.setupSSE();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
private fetchInitialStopbets() {
|
||||||
this.http.get(`http://localhost:8080/stopbet/raw?venue=${this.currentVenue}&date=${this.currentDate}`).subscribe({
|
this.http
|
||||||
|
.get(`http://localhost:8080/stopbet/raw?venue=${this.currentVenue}&date=${this.currentDate}`)
|
||||||
|
.subscribe({
|
||||||
next: (res: any) => {
|
next: (res: any) => {
|
||||||
if (res.ok && res.data) {
|
if (res.ok && res.data) {
|
||||||
const key = `${this.currentVenue}:${this.currentDate}`;
|
const key = `${this.currentVenue}:${this.currentDate}`;
|
||||||
@ -36,14 +131,19 @@ export class StopbetService {
|
|||||||
this.stopbetStatuses.clear();
|
this.stopbetStatuses.clear();
|
||||||
for (const race in meeting) {
|
for (const race in meeting) {
|
||||||
const raceNum = parseInt(race, 10);
|
const raceNum = parseInt(race, 10);
|
||||||
this.stopbetStatuses.set(raceNum, meeting[race]);
|
const status = meeting[race];
|
||||||
|
this.setStatus(raceNum, status);
|
||||||
}
|
}
|
||||||
|
console.log('[STOPBET] Fetched initial statuses:', this.stopbetStatuses);
|
||||||
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
|
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
|
||||||
|
this.saveToLocalStorage();
|
||||||
|
} else {
|
||||||
|
console.warn('[STOPBET] Invalid response from fetchInitialStopbets:', res);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
console.error('[STOPBET] Failed to fetch initial statuses:', err);
|
console.error('[STOPBET] Failed to fetch initial statuses:', err);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,8 +158,8 @@ export class StopbetService {
|
|||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.type === 'stopbet' && data.venue === this.currentVenue && data.date === this.currentDate) {
|
if (data.type === 'stopbet' && data.venue === this.currentVenue && data.date === this.currentDate) {
|
||||||
const raceNum = parseInt(data.race, 10);
|
const raceNum = parseInt(data.race, 10);
|
||||||
this.stopbetStatuses.set(raceNum, data.status);
|
this.setStatus(raceNum, data.status);
|
||||||
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
|
console.log('[STOPBET] SSE updated race', raceNum, 'to', data.status);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[STOPBET] SSE parse error:', err);
|
console.error('[STOPBET] SSE parse error:', err);
|
||||||
@ -86,6 +186,14 @@ export class StopbetService {
|
|||||||
return start;
|
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() {
|
ngOnDestroy() {
|
||||||
if (this.eventSource) {
|
if (this.eventSource) {
|
||||||
this.eventSource.close();
|
this.eventSource.close();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user