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