fix : added service for the stop bet
This commit is contained in:
parent
d851b0f949
commit
5984f9d036
@ -1,10 +1,12 @@
|
||||
import { Component, OnInit, HostListener, OnDestroy, NgZone } from '@angular/core';
|
||||
// navbar.component.ts
|
||||
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',
|
||||
@ -18,6 +20,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
isMenuOpen: boolean = false;
|
||||
screenWidth: number = window.innerWidth;
|
||||
private subscription!: Subscription;
|
||||
private stopbetSubscription: Subscription | null = null;
|
||||
userName: string = '';
|
||||
btid: string | null = null;
|
||||
|
||||
@ -68,20 +71,34 @@ 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<number, string> = new Map(); // raceNum => 'Y'|'N'|'S'
|
||||
private currentVenue: string = '';
|
||||
private currentDate: string = '';
|
||||
private eventSource?: EventSource;
|
||||
|
||||
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
|
||||
private http: HttpClient,
|
||||
@Inject(StopbetService) private stopbetService: StopbetService
|
||||
) {}
|
||||
|
||||
// Stopbet-related properties
|
||||
private stopbetStatuses: Map<number, string> = new Map(); // raceNum => 'Y'|'N'|'S'
|
||||
private currentVenue: string = '';
|
||||
private currentDate: string = '';
|
||||
private eventSource?: EventSource;
|
||||
|
||||
ngOnInit() {
|
||||
this.userName = localStorage.getItem('userName') || '';
|
||||
this.btid = localStorage.getItem('btid');
|
||||
@ -93,7 +110,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// === Load structuredRaceCard from localdb (rpinfo) if available ===
|
||||
// === Load structuredRaceCard from rpinfo if available (prefer structured) ===
|
||||
const rpCached = localStorage.getItem('rpinfo');
|
||||
if (rpCached) {
|
||||
try {
|
||||
@ -126,20 +143,52 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
this.currentVenue = (this.raceCardData?.venue ?? '').toUpperCase();
|
||||
this.currentDate = this.getTodayDate();
|
||||
|
||||
// Setup stopbet if venue is available
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 2) fetch initial stopbets (HTTP fallback) and setup SSE stream
|
||||
this.fetchInitialStopbets();
|
||||
this.setupSSE();
|
||||
}
|
||||
|
||||
// Periodic ABS/latest polling
|
||||
// 3) subscribe to StopbetService (if it provides an observable of statuses)
|
||||
try {
|
||||
const statuses$ = (this.stopbetService as any).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.checkAndSwitchIfStopped();
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[STOPBET] subscribe to service failed:', e);
|
||||
}
|
||||
|
||||
// Periodic ABS/latest polling (unchanged)
|
||||
this.subscription = interval(5000)
|
||||
.pipe(
|
||||
switchMap(() => {
|
||||
// console.log(`[ANGULAR] Fetching latest ABS status at ${new Date().toISOString()}`);
|
||||
return this.http.get('http://localhost:8080/abs/latest').pipe(
|
||||
catchError((err) => {
|
||||
// console.error('[ANGULAR] ABS latest fetch failed ❌', err);
|
||||
this.liveStatusOk = false;
|
||||
return of(null);
|
||||
})
|
||||
@ -148,14 +197,11 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
.subscribe((res: any) => {
|
||||
if (res) {
|
||||
// console.log('[ANGULAR] ABS latest response ✅', res);
|
||||
this.liveStatusOk = res.success === true;
|
||||
|
||||
// If backend eventually returns structured data:
|
||||
if (res.raceCardData) {
|
||||
this.raceCardData = res.raceCardData;
|
||||
localStorage.setItem('raceCardData', JSON.stringify(res.raceCardData));
|
||||
// keep rpinfo consistent if desired (do not overwrite rpinfo unless you want to)
|
||||
}
|
||||
if (res.wallet) {
|
||||
this.wallet = res.wallet;
|
||||
@ -182,7 +228,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
this.multiLegBaseRaceIdx = baseRaceIdx;
|
||||
this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${label}`;
|
||||
this.updateEnabledHorseNumbersForMultiLeg(baseRaceIdx);
|
||||
//console.log(`[Multi-leg Pool] Selected: ${label}, Base Race: ${baseRaceIdx}`);
|
||||
}
|
||||
if (data.type === 'multiLegPoolEnd') {
|
||||
this.currentPool = null;
|
||||
@ -208,17 +253,22 @@ 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.ok && res.data) {
|
||||
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]);
|
||||
}
|
||||
// Check and switch if current race is stopped
|
||||
// After loading, ensure current selected race is valid
|
||||
this.checkAndSwitchIfStopped();
|
||||
}
|
||||
},
|
||||
@ -228,7 +278,11 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SSE stream setup for real-time stopbet updates.
|
||||
*/
|
||||
private setupSSE() {
|
||||
try {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
}
|
||||
@ -252,33 +306,35 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
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; // Assume open if unknown
|
||||
return status === 'N' || status === undefined;
|
||||
}
|
||||
|
||||
private getMaxRaces(): number {
|
||||
return this.raceCardData?.raceVenueRaces?.races?.length || 10;
|
||||
}
|
||||
|
||||
private getOpenRaceStartingFrom(start: number): number {
|
||||
private getOpenRaceStartingFrom(start: number) {
|
||||
const max = this.getMaxRaces();
|
||||
// Try from start to max
|
||||
for (let r = start; r <= max; r++) {
|
||||
if (this.isOpen(r)) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
// If none after, try from 1 to start-1
|
||||
for (let r = 1; r < start; r++) {
|
||||
if (this.isOpen(r)) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
// All stopped, return original start
|
||||
return start;
|
||||
}
|
||||
|
||||
@ -339,8 +395,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
type: 'enabledHorseNumbers',
|
||||
value: this.enabledHorseNumbers,
|
||||
});
|
||||
|
||||
// console.log('[Multi-leg Pool] Updated enabled horse numbers:', this.enabledHorseNumbers);
|
||||
}
|
||||
|
||||
private getLegCountForLabel(): number {
|
||||
@ -403,12 +457,10 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
// Use lowercase 'venue' as structuredRaceCard uses .venue
|
||||
this.selectedVenue = this.raceCardData?.venue ?? this.raceCardData?.Venue ?? 'Select Venue';
|
||||
this.updateEnabledHorseNumbers();
|
||||
// console.log('[MODAL] Opening venue modal (structured):', this.selectedVenue);
|
||||
this.showVenueModal = true;
|
||||
}
|
||||
|
||||
openRaceModal() {
|
||||
// console.log('[MODAL] Opening race modal');
|
||||
this.showRaceModal = true;
|
||||
|
||||
const racesArr = this.raceCardData?.raceVenueRaces?.races ?? [];
|
||||
@ -421,7 +473,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
closeModals() {
|
||||
// console.log('[MODAL] Closing all modals');
|
||||
this.showVenueModal = false;
|
||||
this.showRaceModal = false;
|
||||
this.showWalletModal = false;
|
||||
@ -431,7 +482,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
selectVenue(index: number) {
|
||||
// We expect this.raceCardData to be structuredRaceCard (venue field lowercase)
|
||||
const venue = this.raceCardData?.venue ?? this.raceCardData?.Venue ?? 'Unknown Venue';
|
||||
this.selectedVenue = venue;
|
||||
this.selectedRaceId = index;
|
||||
@ -441,7 +491,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
value: this.selectedVenue,
|
||||
});
|
||||
|
||||
// Update currentVenue if changed
|
||||
const newVenue = venue.toUpperCase();
|
||||
if (newVenue !== this.currentVenue) {
|
||||
this.currentVenue = newVenue;
|
||||
@ -449,10 +498,12 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
if (this.currentVenue) {
|
||||
this.fetchInitialStopbets();
|
||||
this.setupSSE();
|
||||
try {
|
||||
this.stopbetService.initialize(this.currentVenue, this.currentDate);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('[VENUE] Venue resolved to (structured):', this.selectedVenue, '| index:', index);
|
||||
this.closeModals();
|
||||
}
|
||||
|
||||
@ -486,8 +537,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.sharedStateService.setRunnerCount(runnerCount);
|
||||
this.updateEnabledHorseNumbers();
|
||||
|
||||
// console.log('[RACE] Race selected (structured):', this.selectedRace, '| Runner count:', runnerCount);
|
||||
this.closeModals();
|
||||
}
|
||||
|
||||
@ -518,47 +567,28 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
type: 'enabledHorseNumbers',
|
||||
value: this.enabledHorseNumbers,
|
||||
});
|
||||
|
||||
// console.log('[HORSE NUMBERS] Enabled horse numbers (structured):', this.enabledHorseNumbers);
|
||||
}
|
||||
|
||||
selectHorseNumber(number: number) {
|
||||
// console.log('[HORSE] Selected horse number:', number);
|
||||
// Intentionally left no-op (UI hook)
|
||||
}
|
||||
|
||||
openWalletModal() {
|
||||
// console.log('[MODAL] Opening wallet modal');
|
||||
this.showWalletModal = true;
|
||||
}
|
||||
|
||||
openResultModal() {
|
||||
// console.log('[MODAL] Opening result modal');
|
||||
this.showResultModal = true;
|
||||
}
|
||||
|
||||
openMessagesModal() {
|
||||
// console.log('[MODAL] Opening messages modal');
|
||||
this.showMessagesModal = true;
|
||||
}
|
||||
|
||||
openLogModal() {
|
||||
// console.log('[MODAL] Opening log modal');
|
||||
this.showLogModal = true;
|
||||
}
|
||||
|
||||
formattedTicketLogs: {
|
||||
pool: string;
|
||||
horses: string;
|
||||
horsesArray: string[][];
|
||||
ticketCountLabel: string;
|
||||
price: string;
|
||||
numbers: number[];
|
||||
count: number;
|
||||
amount: number;
|
||||
maskedBarcode: string;
|
||||
displayBarcode: string;
|
||||
}[] = [];
|
||||
|
||||
openViewLog() {
|
||||
const storedTickets = localStorage.getItem('localTicketsViewlog');
|
||||
|
||||
@ -612,9 +642,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
displayBarcode = '********' + last4;
|
||||
}
|
||||
|
||||
// console.log(maskedBarcode);
|
||||
// console.log('Decoded:', atob(maskedBarcode));
|
||||
|
||||
return {
|
||||
pool,
|
||||
horses,
|
||||
@ -629,12 +656,10 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// console.log('No tickets found in localStorage.');
|
||||
this.formattedTicketLogs = [];
|
||||
}
|
||||
|
||||
this.showLogModal = true;
|
||||
// console.log('Log modal opened. Final formattedTicketLogs:', this.formattedTicketLogs);
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
@ -645,11 +670,9 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
name,
|
||||
employeeId,
|
||||
action: 'logout',
|
||||
type: 'logout', // This is the missing piece
|
||||
type: 'logout',
|
||||
};
|
||||
|
||||
// console.log('[LOGOUT] Initiating logout with printData:', printData);
|
||||
|
||||
fetch('http://localhost:9100/print', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@ -657,13 +680,11 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Logout print failed');
|
||||
// console.log('[LOGOUT] Print successful');
|
||||
(window as any).electronAPI?.closeSecondScreen?.();
|
||||
localStorage.clear();
|
||||
this.router.navigate(['/logout']);
|
||||
})
|
||||
.catch((err) => {
|
||||
// console.error('[LOGOUT] Error printing:', err);
|
||||
(window as any).electronAPI?.closeSecondScreen?.();
|
||||
localStorage.clear();
|
||||
this.router.navigate(['/logout']);
|
||||
@ -674,8 +695,13 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
if (this.stopbetSubscription) {
|
||||
this.stopbetSubscription.unsubscribe();
|
||||
}
|
||||
if (this.eventSource) {
|
||||
try {
|
||||
this.eventSource.close();
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// touch-pad-menu.component.ts
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
@ -5,14 +6,15 @@ import {
|
||||
OnDestroy,
|
||||
NgZone,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef // <-- Add this import
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { SelectionService, SelectionData } from '../selection.service/selection.service';
|
||||
import { SharedStateService } from '../../service/shared-state.service';
|
||||
import { LabelRestrictionService } from '../selection.service/label-restriction.service';
|
||||
import _, { join } from 'lodash';
|
||||
import { StopbetService } from '../../service/stopbet.service'; // Import StopbetService
|
||||
import _ from 'lodash';
|
||||
|
||||
@Component({
|
||||
selector: 'app-touch-pad-menu',
|
||||
@ -87,6 +89,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
private currentRowSubscription: Subscription | null = null;
|
||||
private selectionsSubscription: Subscription | null = null;
|
||||
private runnerCountSubscription: Subscription | null = null;
|
||||
private stopbetSubscription: Subscription | null = null; // Subscription for stopbet statuses
|
||||
private currentTotal: number = 0;
|
||||
private currentSelections: SelectionData[] = [];
|
||||
|
||||
@ -98,21 +101,17 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
structuredRaceCard: any = {};
|
||||
|
||||
selectedRaceNumber: string = '1';
|
||||
selectionService: SelectionService;
|
||||
sharedStateService: SharedStateService;
|
||||
labelRestrictionService: LabelRestrictionService;
|
||||
|
||||
private stopbetStatuses: Map<number, string> = new Map(); // Local copy of stopbet statuses
|
||||
|
||||
constructor(
|
||||
selectionService: SelectionService,
|
||||
sharedStateService: SharedStateService,
|
||||
labelRestrictionService: LabelRestrictionService,
|
||||
private selectionService: SelectionService,
|
||||
private sharedStateService: SharedStateService,
|
||||
private labelRestrictionService: LabelRestrictionService,
|
||||
private stopbetService: StopbetService, // Inject StopbetService
|
||||
private ngZone: NgZone,
|
||||
private cdr: ChangeDetectorRef // <-- Inject ChangeDetectorRef
|
||||
) {
|
||||
this.selectionService = selectionService;
|
||||
this.sharedStateService = sharedStateService;
|
||||
this.labelRestrictionService = labelRestrictionService;
|
||||
}
|
||||
private cdr: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
// Prefer rpinfo.structuredRaceCard
|
||||
@ -126,6 +125,14 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.raceCardData = this.structuredRaceCard;
|
||||
|
||||
// Subscribe to stopbet statuses
|
||||
this.stopbetSubscription = this.stopbetService.getStopbetStatuses().subscribe((statuses) => {
|
||||
this.stopbetStatuses = new Map(statuses);
|
||||
this.refreshBlockedLabels(this.selectedLabel);
|
||||
this.setActualRunners();
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
|
||||
this.runnerCountSubscription = this.sharedStateService.runnerCount$.subscribe((count: number) => {
|
||||
this.runnerCount = count || 12;
|
||||
this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
|
||||
@ -134,6 +141,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
this.btid = localStorage.getItem('btid');
|
||||
// --- NEW: Update actualRunners when runner count changes ---
|
||||
this.setActualRunners();
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
|
||||
this.labelRowsFlat = this.labelRows.flat();
|
||||
@ -150,10 +158,12 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
if (!this.totalAmountLimitReached) {
|
||||
this.showLimitPopup = false;
|
||||
}
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
|
||||
this.currentRowSubscription = this.selectionService.currentRow$.subscribe((row: SelectionData) => {
|
||||
this.currentTotal = row.total || 0;
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
|
||||
// Subscribe to selectedRace (from navbar)
|
||||
@ -170,6 +180,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
this.setActualRunners();
|
||||
}
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
|
||||
// legacy storage - keep for compatibility if rpinfo absent
|
||||
@ -183,6 +194,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
this.currentRowSubscription?.unsubscribe();
|
||||
this.selectionsSubscription?.unsubscribe();
|
||||
this.runnerCountSubscription?.unsubscribe();
|
||||
this.stopbetSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private safeGetJSON(key: string): any | null {
|
||||
@ -221,10 +233,13 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
const raceIdx = poolRaces.length > this.multiLegStage
|
||||
? poolRaces[this.multiLegStage] - 1
|
||||
: (this.multiLegBaseRaceIdx - 1) + this.multiLegStage;
|
||||
// Check if the race is open
|
||||
if (!this.stopbetStatuses.has(raceIdx + 1) || this.stopbetStatuses.get(raceIdx + 1) === 'N') {
|
||||
const race = races[raceIdx];
|
||||
if (race?.horses && Array.isArray(race.horses)) {
|
||||
return new Set(race.horses.map((num: any) => Number(num)));
|
||||
}
|
||||
}
|
||||
return new Set();
|
||||
}
|
||||
|
||||
@ -241,10 +256,13 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
getActualRunnersForCurrentRace(): Set<number> {
|
||||
const races = this.structuredRaceCard?.raceVenueRaces?.races || [];
|
||||
const selectedRaceIdx = parseInt(this.selectedRaceNumber, 10) - 1;
|
||||
// Check if the race is open
|
||||
if (!this.stopbetStatuses.has(selectedRaceIdx + 1) || this.stopbetStatuses.get(selectedRaceIdx + 1) === 'N') {
|
||||
const race = races[selectedRaceIdx];
|
||||
if (race?.horses && Array.isArray(race.horses)) {
|
||||
return new Set(race.horses.map((num: any) => Number(num)));
|
||||
}
|
||||
}
|
||||
return new Set();
|
||||
}
|
||||
|
||||
@ -324,9 +342,30 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
isLabelDisabled(label: string): boolean {
|
||||
return this.disabledLabels.includes(label) ||
|
||||
this.totalAmountLimitReached ||
|
||||
this.blockedLabels.has(label);
|
||||
if (this.disabledLabels.includes(label) || this.totalAmountLimitReached || this.blockedLabels.has(label)) return true;
|
||||
|
||||
// Additional check for multi-leg pools: disable if any race in the pool is stopped
|
||||
if (this.multiLegLabels.includes(label)) {
|
||||
const poolName = label === 'MJP' ? 'mjp1' : label === 'JKP' ? 'jkp1' : 'trb1';
|
||||
const raceIndices = this.getRaceIndicesForPool(poolName);
|
||||
if (raceIndices.some(race => !this.isOpen(race))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private getRaceIndicesForPool(poolName: string): number[] {
|
||||
const poolKey = this.normalizePoolName(poolName) || poolName;
|
||||
const poolRaces: number[] = this.structuredRaceCard?.pools?.[poolKey] || [];
|
||||
if (poolRaces.length > 0) return poolRaces;
|
||||
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[poolKey] || [];
|
||||
}
|
||||
|
||||
// --- MODIFIED METHOD ---
|
||||
@ -462,6 +501,10 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
const poolKey = this.normalizePoolName(poolName) || poolName;
|
||||
const poolRaces: number[] = this.structuredRaceCard?.pools?.[poolKey] || [];
|
||||
let baseRaceIdx = poolRaces.length > 0 ? poolRaces[0] : this.getDefaultBaseRace(poolKey);
|
||||
// Ensure baseRaceIdx is open
|
||||
if (!this.isOpen(baseRaceIdx)) {
|
||||
baseRaceIdx = this.getOpenRaceStartingFrom(baseRaceIdx);
|
||||
}
|
||||
if (baseRaceIdx + maxLegs - 1 > totalRaces) {
|
||||
baseRaceIdx = Math.max(1, totalRaces - maxLegs + 1);
|
||||
}
|
||||
@ -478,6 +521,26 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
return poolRaceMap[poolName.toLowerCase()] || parseInt(this.selectedRaceNumber, 10);
|
||||
}
|
||||
|
||||
private isOpen(race: number): boolean {
|
||||
const status = this.stopbetStatuses.get(race);
|
||||
return status === 'N' || status === undefined; // Assume open if unknown
|
||||
}
|
||||
|
||||
private getOpenRaceStartingFrom(start: number): number {
|
||||
const max = this.structuredRaceCard?.raceVenueRaces?.races?.length || 10;
|
||||
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;
|
||||
}
|
||||
|
||||
selectNumber(number: number) {
|
||||
if (!this.selectedLabel || this.totalAmountLimitReached || !this.actualRunners.has(number)) return;
|
||||
|
||||
@ -1555,7 +1618,7 @@ try {
|
||||
|
||||
this.erase(); // ✅ Clear selections after successful print
|
||||
|
||||
//--------------------Ended Print here -----------------------------
|
||||
//--------------------Ended Print here ----------------------------
|
||||
|
||||
this.selectionService.finalizeCurrentRow();
|
||||
// Call after finalizeCurrentRow
|
||||
|
||||
94
btc-UI/src/app/service/stopbet.service.ts
Normal file
94
btc-UI/src/app/service/stopbet.service.ts
Normal file
@ -0,0 +1,94 @@
|
||||
// stopbet.service.ts
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { NgZone } from '@angular/core';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class StopbetService {
|
||||
private stopbetStatuses: Map<number, string> = new Map(); // raceNum => 'Y'|'N'|'S'
|
||||
private stopbetStatusesSubject = new BehaviorSubject<Map<number, string>>(this.stopbetStatuses);
|
||||
private currentVenue: string = '';
|
||||
private currentDate: string = '';
|
||||
private eventSource?: EventSource;
|
||||
|
||||
constructor(private http: HttpClient, private zone: NgZone) {}
|
||||
|
||||
// Expose stopbet statuses as observable
|
||||
getStopbetStatuses(): Observable<Map<number, string>> {
|
||||
return this.stopbetStatusesSubject.asObservable();
|
||||
}
|
||||
|
||||
// Initialize stopbet for a venue and date
|
||||
initialize(venue: string, date: string) {
|
||||
this.currentVenue = venue.toUpperCase();
|
||||
this.currentDate = date;
|
||||
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));
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('[STOPBET] Failed to fetch initial statuses:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setupSSE() {
|
||||
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);
|
||||
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[STOPBET] SSE parse error:', err);
|
||||
}
|
||||
});
|
||||
};
|
||||
this.eventSource.onerror = (err) => {
|
||||
console.error('[STOPBET] SSE error:', err);
|
||||
};
|
||||
}
|
||||
|
||||
isOpen(race: number): boolean {
|
||||
const status = this.stopbetStatuses.get(race);
|
||||
return status === 'N' || status === undefined;
|
||||
}
|
||||
|
||||
getOpenRaceStartingFrom(start: number, maxRaces: number): number {
|
||||
for (let r = start; r <= maxRaces; r++) {
|
||||
if (this.isOpen(r)) return r;
|
||||
}
|
||||
for (let r = 1; r < start; r++) {
|
||||
if (this.isOpen(r)) return r;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user