772 lines
25 KiB
TypeScript
Executable File
772 lines
25 KiB
TypeScript
Executable File
import { Component, OnInit, HostListener, OnDestroy, NgZone, Inject } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { BtcService } from '../../service/btc.service';
|
|
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
|
import { Router } from '@angular/router';
|
|
import { catchError, interval, of, Subscription, switchMap } from 'rxjs';
|
|
import { SharedStateService } from '../../service/shared-state.service';
|
|
import { StopbetService } from '../../service/stopbet.service';
|
|
|
|
@Component({
|
|
selector: 'app-navbar',
|
|
templateUrl: './navbar.component.html',
|
|
styleUrls: ['./navbar.component.css'],
|
|
standalone: true,
|
|
imports: [CommonModule, HttpClientModule],
|
|
})
|
|
export class NavbarComponent implements OnInit, OnDestroy {
|
|
dateTime: string = '';
|
|
isMenuOpen: boolean = false;
|
|
screenWidth: number = window.innerWidth;
|
|
private subscription!: Subscription;
|
|
private stopbetSubscription: Subscription | null = null;
|
|
userName: string = '';
|
|
btid: string | null = null;
|
|
|
|
// Component properties
|
|
consecTimeouts = 0;
|
|
maxConsecTimeouts = 2;
|
|
liveStatusOk = true;
|
|
|
|
showVenueModal = false;
|
|
showRaceModal = false;
|
|
selectedVenue = 'Select Venue';
|
|
selectedRace: number = 1;
|
|
currentLegRaceDisplay: string = '';
|
|
|
|
showWalletModal = false;
|
|
showResultModal = false;
|
|
showMessagesModal = false;
|
|
showLogModal = false;
|
|
raceCardData: any = {};
|
|
raceData: any[] = [];
|
|
objectKeys = Object.keys;
|
|
|
|
selectedRaceId: number = 0;
|
|
enabledHorseNumbers: number[] = [];
|
|
multiLegBaseRaceIdx = 0;
|
|
currentPool: string | null = null;
|
|
|
|
private prevEnabledKey = '';
|
|
wallet = {
|
|
withdraw: 0,
|
|
deposit: 0,
|
|
payout: 0,
|
|
cancel: 0,
|
|
ticketing: 0,
|
|
balance: 0,
|
|
};
|
|
|
|
logs = [
|
|
{
|
|
description: '',
|
|
venue: '',
|
|
ticketNumber: '',
|
|
poolName: '',
|
|
totalAmount: '',
|
|
},
|
|
];
|
|
|
|
messages: string[] = ['System ready.', 'Please select a venue.', 'Races updated.', 'Live status stable.'];
|
|
|
|
// Stopbet-related properties
|
|
private stopbetStatuses: Map<number, string> = new Map();
|
|
private currentVenue: string = '';
|
|
private currentDate: string = '';
|
|
private eventSource: EventSource | undefined;
|
|
|
|
stopMessage: string = '';
|
|
private stopMessageTimeout: number | null = null;
|
|
|
|
formattedTicketLogs: {
|
|
pool: string;
|
|
horses: string;
|
|
horsesArray: string[][];
|
|
ticketCountLabel: string;
|
|
price: string;
|
|
numbers: number[];
|
|
count: number;
|
|
amount: number;
|
|
maskedBarcode: string;
|
|
displayBarcode: string;
|
|
}[] = [];
|
|
|
|
constructor(
|
|
private btcService: BtcService,
|
|
private router: Router,
|
|
private sharedStateService: SharedStateService,
|
|
private zone: NgZone,
|
|
private http: HttpClient,
|
|
@Inject(StopbetService) private stopbetService: StopbetService
|
|
) {}
|
|
|
|
ngOnInit() {
|
|
this.userName = localStorage.getItem('userName') || '';
|
|
this.btid = localStorage.getItem('btid');
|
|
|
|
// Use NgZone to run setInterval outside Angular's change detection
|
|
this.zone.runOutsideAngular(() => {
|
|
setInterval(() => {
|
|
this.zone.run(() => this.updateDateTime());
|
|
}, 1000);
|
|
});
|
|
|
|
// Load structuredRaceCard from rpinfo if available (prefer structured)
|
|
const rpCached = localStorage.getItem('rpinfo');
|
|
if (rpCached) {
|
|
try {
|
|
const parsed = JSON.parse(rpCached);
|
|
this.raceCardData = parsed?.structuredRaceCard ?? {};
|
|
console.log('[INIT] Loaded structuredRaceCard from rpinfo:', this.raceCardData);
|
|
} catch (err) {
|
|
console.error('[INIT] Failed to parse rpinfo:', err);
|
|
this.raceCardData = {};
|
|
}
|
|
} else {
|
|
// Fallback to older raceCardData key if present (non-structured)
|
|
const fallback = localStorage.getItem('raceCardData');
|
|
if (fallback) {
|
|
try {
|
|
const parsed = JSON.parse(fallback);
|
|
// prefer structuredRaceCard inside fallback if present
|
|
this.raceCardData = parsed?.structuredRaceCard ?? parsed ?? {};
|
|
console.warn('[INIT] Loaded fallback raceCardData (prefer rpinfo/structuredRaceCard):', this.raceCardData);
|
|
} catch (err) {
|
|
console.error('[INIT] Failed to parse fallback raceCardData:', err);
|
|
this.raceCardData = {};
|
|
}
|
|
} else {
|
|
this.raceCardData = {};
|
|
}
|
|
}
|
|
|
|
this.currentVenue = (this.raceCardData?.venue ?? '').toUpperCase() || localStorage.getItem('selectedVenue') || '';
|
|
this.currentDate = this.getTodayDate();
|
|
console.log('[INIT] Current venue:', this.currentVenue, 'date:', this.currentDate);
|
|
|
|
this.selectedVenue = localStorage.getItem('selectedVenue') || this.raceCardData?.venue || 'Select Venue';
|
|
|
|
if (this.currentVenue) {
|
|
try {
|
|
this.stopbetService.initialize(this.currentVenue, this.currentDate);
|
|
} catch (e) {
|
|
console.warn('[STOPBET] stopbetService.initialize failed:', e);
|
|
}
|
|
} else {
|
|
console.warn('[INIT] No venue set, skipping stopbetService.initialize');
|
|
}
|
|
|
|
try {
|
|
const statuses$ = this.stopbetService.getStopbetStatuses();
|
|
if (statuses$ && typeof statuses$.subscribe === 'function') {
|
|
this.stopbetSubscription = statuses$.subscribe((statuses: Map<number, string>) => {
|
|
const newStatuses = new Map(statuses);
|
|
const newStops: number[] = [];
|
|
|
|
// Detect newly stopped races (same logic as before)
|
|
newStatuses.forEach((value, key) => {
|
|
const prev = this.stopbetStatuses.get(key);
|
|
if (prev === 'N' || prev === undefined) {
|
|
if (value === 'Y' || value === 'S') {
|
|
newStops.push(key);
|
|
}
|
|
}
|
|
});
|
|
|
|
this.stopbetStatuses = newStatuses;
|
|
|
|
// If there are new stops, display only the latest one (most recent)
|
|
if (newStops.length > 0) {
|
|
newStops.sort((a, b) => a - b); // sort ascending
|
|
const latestStop = newStops[newStops.length - 1]; // pick the highest (latest) race number detected
|
|
this.stopMessage = `Race ${latestStop} Stopped`;
|
|
|
|
// Sync new single stop message to electron main if available
|
|
if ((window as any).electronAPI) {
|
|
try {
|
|
(window as any).electronAPI.syncStopMessage(this.stopMessage);
|
|
} catch (e) {
|
|
console.warn('[NAVBAR] electronAPI.syncStopMessage failed:', e);
|
|
}
|
|
}
|
|
|
|
// Clear any existing timeout so message stays visible full duration
|
|
if (this.stopMessageTimeout) {
|
|
clearTimeout(this.stopMessageTimeout);
|
|
}
|
|
// Keep message visible for 50 seconds (50000 ms) then clear
|
|
this.stopMessageTimeout = window.setTimeout(() => {
|
|
this.stopMessage = '';
|
|
this.stopMessageTimeout = null;
|
|
// Sync cleared message to electron main if available
|
|
if ((window as any).electronAPI) {
|
|
try {
|
|
(window as any).electronAPI.syncStopMessage(this.stopMessage);
|
|
} catch (e) {
|
|
console.warn('[NAVBAR] electronAPI.syncStopMessage (clear) failed:', e);
|
|
}
|
|
}
|
|
}, 50000);
|
|
}
|
|
|
|
console.log('[NAVBAR] Updated stopbetStatuses:', Object.fromEntries(this.stopbetStatuses));
|
|
this.checkAndSwitchIfStopped();
|
|
this.updateEnabledHorseNumbers();
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.warn('[STOPBET] Subscribe to service failed:', e);
|
|
}
|
|
|
|
this.subscription = interval(5000)
|
|
.pipe(
|
|
switchMap(() => {
|
|
return this.http.get('http://localhost:8087/abs/latest').pipe(
|
|
catchError((err) => {
|
|
this.liveStatusOk = false;
|
|
return of(null);
|
|
})
|
|
);
|
|
})
|
|
)
|
|
.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
|
|
this.sharedStateService.sharedData$.subscribe((data) => {
|
|
if (data.type === 'currentLegRace') {
|
|
this.selectedRace = data.value;
|
|
const electronAPI = (window as any).electronAPI;
|
|
if (electronAPI) {
|
|
electronAPI.syncSelectedRace(this.selectedRace);
|
|
}
|
|
if (this.currentPool) {
|
|
const leg = this.getLegIndexForRace(this.currentPool, data.value);
|
|
this.currentLegRaceDisplay = `Leg ${leg + 1} (Race ${data.value}) for ${this.currentPool}`;
|
|
this.updateEnabledHorseNumbers();
|
|
} else {
|
|
this.currentLegRaceDisplay = '';
|
|
this.updateEnabledHorseNumbers();
|
|
}
|
|
}
|
|
if (data.type === 'multiLegPoolStart') {
|
|
const { label, baseRaceIdx } = data.value;
|
|
this.currentPool = label;
|
|
this.multiLegBaseRaceIdx = baseRaceIdx;
|
|
this.selectedRace = this.getValidRaceForLeg(label, 0);
|
|
const electronAPI = (window as any).electronAPI;
|
|
if (electronAPI) {
|
|
electronAPI.syncSelectedRace(this.selectedRace);
|
|
}
|
|
this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${label}`;
|
|
this.updateEnabledHorseNumbers();
|
|
}
|
|
if (data.type === 'multiLegPoolEnd') {
|
|
this.currentPool = null;
|
|
this.multiLegBaseRaceIdx = 0;
|
|
this.currentLegRaceDisplay = '';
|
|
|
|
// Reset selectedRace to the first open race (1 if no stops, or the running/open one if stops exist)
|
|
this.selectedRace = this.getOpenRaceStartingFrom(1);
|
|
this.sharedStateService.updateSharedData({
|
|
type: 'selectedRace',
|
|
value: this.selectedRace,
|
|
});
|
|
const electronAPI = (window as any).electronAPI;
|
|
if (electronAPI) {
|
|
electronAPI.syncSelectedRace(this.selectedRace);
|
|
}
|
|
this.updateEnabledHorseNumbers();
|
|
}
|
|
if (data.type === 'selectedRace') {
|
|
this.currentPool = null;
|
|
this.multiLegBaseRaceIdx = 0;
|
|
this.currentLegRaceDisplay = '';
|
|
this.selectedRace = data.value;
|
|
const electronAPI = (window as any).electronAPI;
|
|
if (electronAPI) {
|
|
electronAPI.syncSelectedRace(this.selectedRace);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initial sync for venue and race
|
|
const electronAPI = (window as any).electronAPI;
|
|
if (electronAPI) {
|
|
electronAPI.syncSelectedVenue(this.selectedVenue);
|
|
electronAPI.syncSelectedRace(this.selectedRace);
|
|
}
|
|
}
|
|
|
|
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}`;
|
|
}
|
|
|
|
private isOpen(race: number): boolean {
|
|
return this.stopbetService.isOpen(race);
|
|
}
|
|
|
|
private getMaxRaces(): number {
|
|
return this.raceCardData?.raceVenueRaces?.races?.length || 10;
|
|
}
|
|
|
|
private getOpenRaceStartingFrom(start: number) {
|
|
const max = this.getMaxRaces();
|
|
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 | 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.toLowerCase()]?.indexOf(race) ?? 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
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[] = [];
|
|
|
|
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;
|
|
} 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({
|
|
type: 'enabledHorseNumbers',
|
|
value: this.enabledHorseNumbers,
|
|
});
|
|
console.log('[NAVBAR] Updated enabledHorseNumbers:', this.enabledHorseNumbers);
|
|
}
|
|
|
|
updateDateTime() {
|
|
const now = new Date();
|
|
this.dateTime = now.toLocaleString();
|
|
}
|
|
|
|
@HostListener('window:resize', ['$event'])
|
|
onResize(event: any) {
|
|
this.screenWidth = event.target.innerWidth;
|
|
if (this.screenWidth > 800) {
|
|
this.isMenuOpen = false;
|
|
}
|
|
}
|
|
|
|
toggleMenu() {
|
|
this.isMenuOpen = !this.isMenuOpen;
|
|
}
|
|
|
|
openVenueModal() {
|
|
if (!this.raceCardData || Object.keys(this.raceCardData).length === 0) {
|
|
const cachedData = localStorage.getItem('raceCardData');
|
|
if (cachedData) {
|
|
try {
|
|
const parsed = JSON.parse(cachedData);
|
|
this.raceCardData = parsed?.structuredRaceCard ?? parsed ?? { raceVenueRaces: { races: [] }, venue: 'Unknown Venue' };
|
|
console.log('[VENUE MODAL] Loaded cached raceCardData:', this.raceCardData);
|
|
} catch (e) {
|
|
console.error('[VENUE MODAL] Failed to parse cached raceCardData:', e);
|
|
this.raceCardData = { raceVenueRaces: { races: [] }, venue: 'Unknown Venue' };
|
|
}
|
|
} else {
|
|
this.raceCardData = { raceVenueRaces: { races: [] }, venue: 'Unknown Venue' };
|
|
console.log('[VENUE MODAL] No cached data, using default raceCardData:', this.raceCardData);
|
|
}
|
|
}
|
|
|
|
// Set selectedVenue based on raceCardData
|
|
this.selectedVenue = this.raceCardData?.venue ?? this.raceCardData?.Venue ?? 'Select Venue';
|
|
console.log('[VENUE MODAL] Setting selectedVenue:', this.selectedVenue);
|
|
this.updateEnabledHorseNumbers(); // Ensure this is called if needed
|
|
this.showVenueModal = true;
|
|
}
|
|
|
|
openRaceModal() {
|
|
this.showRaceModal = true;
|
|
|
|
const racesArr = this.raceCardData?.raceVenueRaces?.races ?? [];
|
|
if (typeof this.selectedRaceId === 'number' && racesArr.length > this.selectedRaceId) {
|
|
const maybeRace = racesArr[this.selectedRaceId];
|
|
this.raceData = Array.isArray(maybeRace) ? maybeRace : maybeRace?.runners ?? [];
|
|
} else {
|
|
this.raceData = [];
|
|
}
|
|
}
|
|
|
|
closeModals() {
|
|
this.showVenueModal = false;
|
|
this.showRaceModal = false;
|
|
this.showWalletModal = false;
|
|
this.showResultModal = false;
|
|
this.showMessagesModal = false;
|
|
this.showLogModal = false;
|
|
}
|
|
|
|
selectVenue(index: number) {
|
|
const venue = this.raceCardData?.venue ?? this.raceCardData?.Venue ?? 'Unknown Venue';
|
|
this.selectedVenue = venue;
|
|
this.selectedRaceId = index;
|
|
|
|
this.sharedStateService.updateSharedData({
|
|
type: 'selectedVenue',
|
|
value: this.selectedVenue,
|
|
});
|
|
|
|
const electronAPI = (window as any).electronAPI;
|
|
if (electronAPI) {
|
|
electronAPI.syncSelectedVenue(this.selectedVenue);
|
|
}
|
|
|
|
const newVenue = venue.toUpperCase();
|
|
if (newVenue !== this.currentVenue) {
|
|
this.currentVenue = newVenue;
|
|
this.stopbetStatuses.clear();
|
|
if (this.currentVenue) {
|
|
try {
|
|
console.log('[NAVBAR] Initializing stopbetService for venue:', newVenue);
|
|
this.stopbetService.initialize(this.currentVenue, this.currentDate);
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
this.closeModals();
|
|
}
|
|
|
|
selectRace(race: number) {
|
|
const adjustedRace = this.isOpen(race) ? race : this.getOpenRaceStartingFrom(race);
|
|
|
|
this.selectedRace = adjustedRace;
|
|
this.currentPool = null;
|
|
this.multiLegBaseRaceIdx = 0;
|
|
this.currentLegRaceDisplay = '';
|
|
|
|
this.sharedStateService.updateSharedData({
|
|
type: 'selectedRace',
|
|
value: this.selectedRace,
|
|
});
|
|
|
|
const electronAPI = (window as any).electronAPI;
|
|
if (electronAPI) {
|
|
electronAPI.syncSelectedRace(this.selectedRace);
|
|
}
|
|
|
|
const raceList = this.raceCardData?.raceVenueRaces?.races ?? [];
|
|
const selectedRaceEntry = raceList[this.selectedRace - 1] ?? [];
|
|
|
|
let runnerCount = 12;
|
|
if (Array.isArray(selectedRaceEntry)) {
|
|
runnerCount = selectedRaceEntry.length || 12;
|
|
} else if (selectedRaceEntry && Array.isArray(selectedRaceEntry.runners)) {
|
|
runnerCount = selectedRaceEntry.runners.length || 12;
|
|
} else if (selectedRaceEntry && typeof selectedRaceEntry.runnerCount === 'number') {
|
|
runnerCount = selectedRaceEntry.runnerCount;
|
|
}
|
|
|
|
this.sharedStateService.setRunnerCount(runnerCount);
|
|
this.updateEnabledHorseNumbers();
|
|
this.closeModals();
|
|
}
|
|
|
|
selectHorseNumber(number: number) {
|
|
// Intentionally left no-op (UI hook)
|
|
}
|
|
|
|
openWalletModal() {
|
|
this.showWalletModal = true;
|
|
}
|
|
|
|
openResultModal() {
|
|
this.showResultModal = true;
|
|
}
|
|
|
|
openMessagesModal() {
|
|
this.showMessagesModal = true;
|
|
}
|
|
|
|
openLogModal() {
|
|
this.showLogModal = true;
|
|
}
|
|
|
|
openViewLog() {
|
|
const storedTickets = localStorage.getItem('localTicketsViewlog');
|
|
|
|
if (storedTickets) {
|
|
const tickets = JSON.parse(storedTickets);
|
|
|
|
this.formattedTicketLogs = tickets.map((ticket: any, index: number) => {
|
|
const rawLabel = ticket.winLabels?.trim() || '';
|
|
const numbers = ticket.numbers || [];
|
|
const count = ticket.ticketCount || 0;
|
|
const amount = ticket.totalAmount || 0;
|
|
const barcodeId = ticket.barcodeId || '';
|
|
let pool = '',
|
|
horses = '',
|
|
horsesArray: string[][] = [],
|
|
ticketCountLabel = '',
|
|
price = '',
|
|
maskedBarcode = '',
|
|
displayBarcode = '';
|
|
|
|
if (rawLabel) {
|
|
const parts = rawLabel.split(/\s+/);
|
|
pool = parts[0];
|
|
|
|
const countMatch = rawLabel.match(/\*(\d+)(?:\s+(Rs\s*\d+))?/);
|
|
if (countMatch) {
|
|
ticketCountLabel = `*${countMatch[1]}`;
|
|
price = countMatch[2] || '';
|
|
}
|
|
|
|
const horsesPartMatch = rawLabel.match(/^\w+\s+(.+?)\s+\*\d+/);
|
|
if (horsesPartMatch) {
|
|
horses = horsesPartMatch[1].trim();
|
|
if (['MJP', 'JKP', 'TRE'].includes(pool)) {
|
|
horsesArray = horses.split('/').map((r) => r.trim().split(',').map((h) => h.trim()));
|
|
} else {
|
|
horsesArray = [horses.split(',').map((h) => h.trim())];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (barcodeId) {
|
|
const last4 = barcodeId.slice(-4);
|
|
const encryptedPart = btoa(barcodeId.slice(0, -4));
|
|
maskedBarcode = encryptedPart;
|
|
displayBarcode = '********' + last4;
|
|
}
|
|
|
|
return {
|
|
pool,
|
|
horses,
|
|
horsesArray,
|
|
ticketCountLabel,
|
|
price,
|
|
numbers,
|
|
count,
|
|
amount,
|
|
maskedBarcode,
|
|
displayBarcode,
|
|
};
|
|
});
|
|
} else {
|
|
this.formattedTicketLogs = [];
|
|
}
|
|
|
|
this.showLogModal = true;
|
|
}
|
|
|
|
logout(): void {
|
|
const name = localStorage.getItem('userName') || 'Unknown User';
|
|
const employeeId = localStorage.getItem('employeeId') || '000000';
|
|
const stopbetData = localStorage.getItem('stopbetStatuses');
|
|
|
|
const printData = {
|
|
name,
|
|
employeeId,
|
|
action: 'logout',
|
|
type: 'logout',
|
|
};
|
|
|
|
fetch('http://localhost:9100/print', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(printData),
|
|
})
|
|
.then((res) => {
|
|
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']);
|
|
});
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
if (this.subscription) {
|
|
this.subscription.unsubscribe();
|
|
}
|
|
if (this.stopbetSubscription) {
|
|
this.stopbetSubscription.unsubscribe();
|
|
}
|
|
if (this.eventSource) {
|
|
try {
|
|
this.eventSource.close();
|
|
} catch {}
|
|
}
|
|
if (this.stopMessageTimeout) {
|
|
clearTimeout(this.stopMessageTimeout);
|
|
}
|
|
}
|
|
|
|
trackByHorse(index: number, item: number): number {
|
|
return item;
|
|
}
|
|
} |