feat : issue tickets to bakend from GUI
This commit is contained in:
parent
b680150a88
commit
6d752fcfb2
@ -1,6 +1,7 @@
|
|||||||
import { Component, OnInit, HostListener, OnDestroy, NgZone } from '@angular/core';
|
import { Component, OnInit, HostListener, OnDestroy, NgZone } 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 { 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';
|
||||||
@ -10,7 +11,7 @@ import { SharedStateService } from '../../service/shared-state.service';
|
|||||||
templateUrl: './navbar.component.html',
|
templateUrl: './navbar.component.html',
|
||||||
styleUrls: ['./navbar.component.css'],
|
styleUrls: ['./navbar.component.css'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule],
|
imports: [CommonModule, HttpClientModule],
|
||||||
})
|
})
|
||||||
export class NavbarComponent implements OnInit, OnDestroy {
|
export class NavbarComponent implements OnInit, OnDestroy {
|
||||||
dateTime: string = '';
|
dateTime: string = '';
|
||||||
@ -19,32 +20,52 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
private subscription!: Subscription;
|
private subscription!: Subscription;
|
||||||
userName: string = '';
|
userName: string = '';
|
||||||
btid: string | null = null;
|
btid: string | null = null;
|
||||||
liveStatusOk: boolean = true;
|
|
||||||
|
// component properties - add these at top-level in the component class
|
||||||
|
consecTimeouts = 0;
|
||||||
|
maxConsecTimeouts = 2;
|
||||||
|
liveStatusOk = true;
|
||||||
|
|
||||||
|
|
||||||
showVenueModal = false;
|
showVenueModal = false;
|
||||||
showRaceModal = false;
|
showRaceModal = false;
|
||||||
selectedVenue = 'Select Venue';
|
selectedVenue = 'Select Venue';
|
||||||
selectedRace: number = 1;
|
selectedRace: number = 1;
|
||||||
currentLegRaceDisplay: string = '';
|
currentLegRaceDisplay: string = ''; // Display current leg's race or pool start
|
||||||
|
|
||||||
showWalletModal = false;
|
showWalletModal = false;
|
||||||
showResultModal = false;
|
showResultModal = false;
|
||||||
showMessagesModal = false;
|
showMessagesModal = false;
|
||||||
showLogModal = false;
|
showLogModal = false;
|
||||||
raceCardData: any = null;
|
raceCardData: any = {};
|
||||||
raceData: any[] = [];
|
raceData: any[] = [];
|
||||||
objectKeys = Object.keys;
|
objectKeys = Object.keys;
|
||||||
|
|
||||||
selectedRaceId: number = 0;
|
selectedRaceId: number = 0; // index into races array
|
||||||
enabledHorseNumbers: number[] = [];
|
enabledHorseNumbers: number[] = [];
|
||||||
multiLegBaseRaceIdx: number = 0;
|
multiLegBaseRaceIdx: number = 0;
|
||||||
currentPool: string | null = null;
|
currentPool: string | null = null;
|
||||||
|
|
||||||
private prevEnabledKey = '';
|
private prevEnabledKey = ''; // For memoization
|
||||||
|
|
||||||
wallet = { withdraw: 0, deposit: 0, payout: 0, cancel: 0, ticketing: 0, balance: 0 };
|
wallet = {
|
||||||
|
withdraw: 0,
|
||||||
|
deposit: 0,
|
||||||
|
payout: 0,
|
||||||
|
cancel: 0,
|
||||||
|
ticketing: 0,
|
||||||
|
balance: 0,
|
||||||
|
};
|
||||||
|
|
||||||
logs = [{ description: '', venue: '', ticketNumber: '', poolName: '', totalAmount: '' }];
|
logs = [
|
||||||
|
{
|
||||||
|
description: '',
|
||||||
|
venue: '',
|
||||||
|
ticketNumber: '',
|
||||||
|
poolName: '',
|
||||||
|
totalAmount: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
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.'];
|
||||||
|
|
||||||
@ -52,28 +73,83 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
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
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.userName = localStorage.getItem('userName') || '';
|
this.userName = localStorage.getItem('userName') || '';
|
||||||
this.btid = localStorage.getItem('btid');
|
this.btid = localStorage.getItem('btid');
|
||||||
|
|
||||||
// Use NgZone to run setInterval outside Angular's change detection
|
// Use NgZone to run setInterval outside Angular's change detection
|
||||||
this.zone.runOutsideAngular(() => {
|
this.zone.runOutsideAngular(() => {
|
||||||
setInterval(() => { this.zone.run(() => this.updateDateTime()); }, 1000);
|
setInterval(() => {
|
||||||
|
this.zone.run(() => this.updateDateTime());
|
||||||
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// === Load structuredRaceCard from localdb (rpinfo) if available ===
|
||||||
|
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 = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periodic ABS/latest polling
|
||||||
this.subscription = interval(5000)
|
this.subscription = interval(5000)
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() =>
|
switchMap(() => {
|
||||||
this.btcService.pingLiveStatus().pipe(
|
console.log(`[ANGULAR] Fetching latest ABS status at ${new Date().toISOString()}`);
|
||||||
catchError((error) => { console.error('[LIVE STATUS] Ping failed:', error); this.liveStatusOk = false; return of(null); })
|
return this.http.get('http://localhost:8080/abs/latest').pipe(
|
||||||
))
|
catchError((err) => {
|
||||||
).subscribe((response) => {
|
console.error('[ANGULAR] ABS latest fetch failed ❌', err);
|
||||||
if (response !== null) { this.liveStatusOk = true; console.log('[LIVE STATUS] OK'); }
|
this.liveStatusOk = false;
|
||||||
|
return of(null);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sharedStateService.sharedData$.subscribe(data => {
|
// Subscribe to shared state updates
|
||||||
|
this.sharedStateService.sharedData$.subscribe((data) => {
|
||||||
if (data.type === 'currentLegRace') {
|
if (data.type === 'currentLegRace') {
|
||||||
this.selectedRace = data.value;
|
this.selectedRace = data.value;
|
||||||
if (this.currentPool) {
|
if (this.currentPool) {
|
||||||
@ -87,11 +163,11 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
if (data.type === 'multiLegPoolStart') {
|
if (data.type === 'multiLegPoolStart') {
|
||||||
const { label, baseRaceIdx } = data.value;
|
const { label, baseRaceIdx } = data.value;
|
||||||
this.currentPool = this.normalizePoolName(label);
|
this.currentPool = label;
|
||||||
this.multiLegBaseRaceIdx = baseRaceIdx;
|
this.multiLegBaseRaceIdx = baseRaceIdx;
|
||||||
this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${this.currentPool}`;
|
this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${label}`;
|
||||||
this.updateEnabledHorseNumbersForMultiLeg(baseRaceIdx);
|
this.updateEnabledHorseNumbersForMultiLeg(baseRaceIdx);
|
||||||
console.log(`[Multi-leg Pool] Selected: ${this.currentPool}, Base Race: ${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;
|
||||||
@ -107,115 +183,135 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
this.updateEnabledHorseNumbers();
|
this.updateEnabledHorseNumbers();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.loadRaceCardData();
|
|
||||||
}
|
|
||||||
|
|
||||||
private safeGetJSON(key: string): any | null {
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem(key);
|
|
||||||
if (!raw) return null;
|
|
||||||
return JSON.parse(raw);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(`[safeGetJSON] failed parse ${key}`, err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadRaceCardData() {
|
|
||||||
const cached = this.safeGetJSON('rpinfo');
|
|
||||||
if (cached && cached.structuredRaceCard) {
|
|
||||||
this.raceCardData = cached.structuredRaceCard;
|
|
||||||
this.selectedVenue = this.raceCardData?.venue || 'Select Venue';
|
|
||||||
this.updateEnabledHorseNumbers();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rc = this.safeGetJSON('raceCardData');
|
|
||||||
if (rc) {
|
|
||||||
this.raceCardData = rc;
|
|
||||||
this.selectedVenue = this.raceCardData?.venue || 'Select Venue';
|
|
||||||
this.updateEnabledHorseNumbers();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.raceCardData = { error: 'Race card not available locally' };
|
|
||||||
this.selectedVenue = 'Select Venue';
|
|
||||||
}
|
|
||||||
|
|
||||||
private normalizePoolName(name: string | null | undefined): string | null {
|
|
||||||
if (!name) return null;
|
|
||||||
const n = String(name).toLowerCase();
|
|
||||||
if (n.startsWith('trb') || n.startsWith('tbp')) return n.includes('2') ? 'trb2' : 'trb1';
|
|
||||||
if (n.startsWith('mjp')) return n.includes('2') ? 'mjp2' : 'mjp1';
|
|
||||||
if (n.startsWith('jkp') || n.startsWith('jpp') || n.startsWith('jk')) return n.includes('2') ? 'jkp2' : 'jkp1';
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLegIndexForRace(poolName: string, race: number): number {
|
private getLegIndexForRace(poolName: string, race: number): number {
|
||||||
if (!this.raceCardData) return 0;
|
const raceMap: { [key: string]: number[] } = {
|
||||||
const normalized = this.normalizePoolName(poolName) || poolName;
|
mjp1: [1, 2, 3, 4],
|
||||||
const poolMap: { [key: string]: number[] } = this.raceCardData?.pools || {};
|
jkp1: [3, 4, 5, 6, 7],
|
||||||
const arr = poolMap[normalized] || poolMap[poolName] || [];
|
trb1: [2, 3, 4],
|
||||||
return arr.indexOf(race) >= 0 ? arr.indexOf(race) : 0;
|
trb2: [5, 6, 7],
|
||||||
|
};
|
||||||
|
return raceMap[poolName]?.indexOf(race) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateEnabledHorseNumbersForMultiLeg(baseRaceIdx: number) {
|
private updateEnabledHorseNumbersForMultiLeg(baseRaceIdx: number) {
|
||||||
const racesArr = this.raceCardData?.raceVenueRaces?.races || [];
|
// Defensive read of races array (structured or legacy)
|
||||||
|
const racesArr = this.raceCardData?.raceVenueRaces?.races ?? [];
|
||||||
const legCount = this.getLegCountForLabel();
|
const legCount = this.getLegCountForLabel();
|
||||||
const poolKey = this.currentPool || '';
|
const key = `${this.currentPool}-${baseRaceIdx}-${legCount}`;
|
||||||
const key = `${poolKey}-${baseRaceIdx}-${legCount}`;
|
|
||||||
if (this.prevEnabledKey === key) return;
|
if (this.prevEnabledKey === key) return; // memoization
|
||||||
this.prevEnabledKey = key;
|
this.prevEnabledKey = key;
|
||||||
|
|
||||||
let combinedHorseNumbers: number[] = [];
|
let combinedHorseNumbers: number[] = [];
|
||||||
const raceIndices = Array.from({ length: legCount }, (_, i) => this.getRaceForLeg(poolKey, i) - 1);
|
|
||||||
|
const raceIndices = Array.from({ length: legCount }, (_, i) => this.getRaceForLeg(this.currentPool || '', i) - 1);
|
||||||
|
|
||||||
for (const raceIdx of raceIndices) {
|
for (const raceIdx of raceIndices) {
|
||||||
const race = racesArr[raceIdx] || {};
|
const rawRace = racesArr[raceIdx] ?? [];
|
||||||
if (Array.isArray(race.horses)) {
|
let runners: any[] = [];
|
||||||
const horses = race.horses.filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30);
|
|
||||||
combinedHorseNumbers.push(...horses);
|
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: number) => typeof n === 'number' && n >= 1 && n <= 30);
|
||||||
|
|
||||||
|
combinedHorseNumbers.push(...horses);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.enabledHorseNumbers = Array.from(new Set(combinedHorseNumbers));
|
this.enabledHorseNumbers = Array.from(new Set(combinedHorseNumbers));
|
||||||
this.sharedStateService.updateSharedData({ type: 'enabledHorseNumbers', value: this.enabledHorseNumbers });
|
|
||||||
|
this.sharedStateService.updateSharedData({
|
||||||
|
type: 'enabledHorseNumbers',
|
||||||
|
value: this.enabledHorseNumbers,
|
||||||
|
});
|
||||||
|
|
||||||
console.log('[Multi-leg Pool] Updated enabled horse numbers:', this.enabledHorseNumbers);
|
console.log('[Multi-leg Pool] Updated enabled horse numbers:', this.enabledHorseNumbers);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLegCountForLabel(): number {
|
private getLegCountForLabel(): number {
|
||||||
const p = (this.currentPool || '').toLowerCase();
|
switch (this.currentPool) {
|
||||||
switch (p) {
|
case 'mjp1':
|
||||||
case 'mjp1': case 'mjp2': return 4;
|
return 4;
|
||||||
case 'jkp1': case 'jkp2': return 5;
|
case 'jkp1':
|
||||||
case 'trb1': case 'trb2': return 3;
|
return 5;
|
||||||
default: return 3;
|
case 'trb1':
|
||||||
|
case 'trb2':
|
||||||
|
return 3;
|
||||||
|
default:
|
||||||
|
return 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRaceForLeg(poolName: string, leg: number): number {
|
private getRaceForLeg(poolName: string, leg: number): number {
|
||||||
const normalized = this.normalizePoolName(poolName) || poolName;
|
const raceMap: { [key: string]: number[] } = {
|
||||||
const poolMap: { [key: string]: number[] } = this.raceCardData?.pools || {};
|
mjp1: [1, 2, 3, 4],
|
||||||
const arr = poolMap[normalized] || poolMap[poolName] || [];
|
jkp1: [3, 4, 5, 6, 7],
|
||||||
if (arr.length > leg) return arr[leg];
|
trb1: [2, 3, 4],
|
||||||
return (this.multiLegBaseRaceIdx || 1) + leg;
|
trb2: [5, 6, 7],
|
||||||
|
};
|
||||||
|
return raceMap[poolName]?.[leg] ?? this.multiLegBaseRaceIdx + leg;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDateTime() { this.dateTime = new Date().toLocaleString(); }
|
updateDateTime() {
|
||||||
|
const now = new Date();
|
||||||
|
this.dateTime = now.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize(event: any) {
|
onResize(event: any) {
|
||||||
this.screenWidth = event.target.innerWidth;
|
this.screenWidth = event.target.innerWidth;
|
||||||
if (this.screenWidth > 800) this.isMenuOpen = false;
|
if (this.screenWidth > 800) {
|
||||||
|
this.isMenuOpen = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleMenu() { this.isMenuOpen = !this.isMenuOpen; }
|
toggleMenu() {
|
||||||
|
this.isMenuOpen = !this.isMenuOpen;
|
||||||
|
}
|
||||||
|
|
||||||
openVenueModal() { this.loadRaceCardData(); this.showVenueModal = true; }
|
openVenueModal() {
|
||||||
|
// Prefer structuredRaceCard loaded at init (rpinfo). If empty, try legacy key.
|
||||||
|
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: [] }, pools: {} };
|
||||||
|
} catch {
|
||||||
|
this.raceCardData = { raceVenueRaces: { races: [] }, pools: {} };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.raceCardData = { raceVenueRaces: { races: [] }, pools: {} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
openRaceModal() {
|
||||||
|
console.log('[MODAL] Opening race modal');
|
||||||
this.showRaceModal = true;
|
this.showRaceModal = true;
|
||||||
const race = this.raceCardData?.raceVenueRaces?.races?.[this.selectedRaceId];
|
|
||||||
this.raceData = race ? (race.horses || []) : [];
|
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() {
|
closeModals() {
|
||||||
@ -229,9 +325,17 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectVenue(index: number) {
|
selectVenue(index: number) {
|
||||||
this.selectedVenue = this.raceCardData?.venue || 'Unknown Venue';
|
// 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;
|
this.selectedRaceId = index;
|
||||||
this.sharedStateService.updateSharedData({ type: 'selectedVenue', value: this.selectedVenue });
|
|
||||||
|
this.sharedStateService.updateSharedData({
|
||||||
|
type: 'selectedVenue',
|
||||||
|
value: this.selectedVenue,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[VENUE] Venue resolved to (structured):', this.selectedVenue, '| index:', index);
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,84 +344,196 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
this.currentPool = null;
|
this.currentPool = null;
|
||||||
this.multiLegBaseRaceIdx = 0;
|
this.multiLegBaseRaceIdx = 0;
|
||||||
this.currentLegRaceDisplay = '';
|
this.currentLegRaceDisplay = '';
|
||||||
this.sharedStateService.updateSharedData({ type: 'selectedRace', value: this.selectedRace });
|
|
||||||
|
|
||||||
const raceData = this.raceCardData?.raceVenueRaces?.races?.[race - 1] || {};
|
this.sharedStateService.updateSharedData({
|
||||||
const runnerCount = Array.isArray(raceData.horses) ? raceData.horses.length : (raceData.runners?.length || 12);
|
type: 'selectedRace',
|
||||||
|
value: this.selectedRace,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use this.raceCardData (structured) rather than re-parsing localStorage
|
||||||
|
const raceList = this.raceCardData?.raceVenueRaces?.races ?? [];
|
||||||
|
const selectedRaceEntry = raceList[race - 1] ?? [];
|
||||||
|
|
||||||
|
// Determine runnerCount defensively based on possible shapes
|
||||||
|
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.sharedStateService.setRunnerCount(runnerCount);
|
||||||
this.updateEnabledHorseNumbers();
|
this.updateEnabledHorseNumbers();
|
||||||
|
|
||||||
|
console.log('[RACE] Race selected (structured):', this.selectedRace, '| Runner count:', runnerCount);
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEnabledHorseNumbers() {
|
updateEnabledHorseNumbers() {
|
||||||
const raceIndex = this.selectedRace - 1;
|
const raceIndex = this.selectedRace - 1;
|
||||||
const race = this.raceCardData?.raceVenueRaces?.races?.[raceIndex] || {};
|
const racesArr = this.raceCardData?.raceVenueRaces?.races ?? [];
|
||||||
let horses: any[] = [];
|
const rawRace = racesArr?.[raceIndex];
|
||||||
|
|
||||||
if (Array.isArray(race?.horses)) horses = race.horses;
|
let runners: any[] = [];
|
||||||
else if (Array.isArray(race?.runners)) horses = race.runners.map((r: any) => r.number ?? r);
|
|
||||||
else if (Array.isArray(race)) horses = race;
|
|
||||||
|
|
||||||
this.enabledHorseNumbers = horses.map((n: any) => Number(n)).filter((n: number) => !Number.isNaN(n) && n >= 1 && n <= 30);
|
if (Array.isArray(rawRace)) {
|
||||||
this.sharedStateService.updateSharedData({ type: 'enabledHorseNumbers', value: this.enabledHorseNumbers });
|
runners = rawRace;
|
||||||
console.log('[HORSE NUMBERS] Enabled horse numbers:', this.enabledHorseNumbers);
|
} else if (rawRace && Array.isArray(rawRace.runners)) {
|
||||||
|
runners = rawRace.runners;
|
||||||
|
} else {
|
||||||
|
runners = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(runners)) {
|
||||||
|
this.enabledHorseNumbers = runners
|
||||||
|
.map((r: any) => r?.horseNumber ?? r?.number ?? r?.horse_no)
|
||||||
|
.filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30);
|
||||||
|
} else {
|
||||||
|
this.enabledHorseNumbers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sharedStateService.updateSharedData({
|
||||||
|
type: 'enabledHorseNumbers',
|
||||||
|
value: this.enabledHorseNumbers,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[HORSE NUMBERS] Enabled horse numbers (structured):', this.enabledHorseNumbers);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectHorseNumber(number: number) { console.log('[HORSE] Selected horse number:', number); }
|
selectHorseNumber(number: number) {
|
||||||
|
console.log('[HORSE] Selected horse number:', number);
|
||||||
|
}
|
||||||
|
|
||||||
openWalletModal() { this.showWalletModal = true; }
|
openWalletModal() {
|
||||||
openResultModal() { this.showResultModal = true; }
|
console.log('[MODAL] Opening wallet modal');
|
||||||
openMessagesModal() { this.showMessagesModal = true; }
|
this.showWalletModal = true;
|
||||||
openLogModal() { this.showLogModal = true; }
|
}
|
||||||
|
|
||||||
formattedTicketLogs: any[] = [];
|
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() {
|
openViewLog() {
|
||||||
const storedTickets = localStorage.getItem('localTicketsViewlog');
|
const storedTickets = localStorage.getItem('localTicketsViewlog');
|
||||||
if (!storedTickets) { this.formattedTicketLogs = []; this.showLogModal = true; return; }
|
|
||||||
const tickets = JSON.parse(storedTickets);
|
|
||||||
this.formattedTicketLogs = tickets.map((ticket: any) => {
|
|
||||||
const rawLabel = ticket.winLabels?.trim() || '';
|
|
||||||
const barcodeId = ticket.barcodeId || '';
|
|
||||||
let pool = '', horses = '', horsesArray: string[][] = [], ticketCountLabel = '', price = '', maskedBarcode = '', displayBarcode = '';
|
|
||||||
|
|
||||||
if (rawLabel) {
|
if (storedTickets) {
|
||||||
const parts = rawLabel.split(/\s+/);
|
const tickets = JSON.parse(storedTickets);
|
||||||
pool = parts[0];
|
|
||||||
const countMatch = rawLabel.match(/\*(\d+)(?:\s+(Rs\s*\d+))?/);
|
this.formattedTicketLogs = tickets.map((ticket: any, index: number) => {
|
||||||
if (countMatch) { ticketCountLabel = `*${countMatch[1]}`; price = countMatch[2] || ''; }
|
const rawLabel = ticket.winLabels?.trim() || '';
|
||||||
const horsesPartMatch = rawLabel.match(/^\w+\s+(.+?)\s+\*\d+/);
|
const numbers = ticket.numbers || [];
|
||||||
if (horsesPartMatch) {
|
const count = ticket.ticketCount || 0;
|
||||||
horses = horsesPartMatch[1].trim();
|
const amount = ticket.totalAmount || 0;
|
||||||
if (['MJP', 'JKP', 'TRE', 'trb1', 'trb2', 'mjp1', 'jkp1'].includes(pool)) {
|
const barcodeId = ticket.barcodeId || '';
|
||||||
horsesArray = horses.split('/').map((r: string) => r.trim().split(',').map(h => h.trim()));
|
let pool = '',
|
||||||
} else {
|
horses = '',
|
||||||
horsesArray = [horses.split(',').map(h => h.trim())];
|
horsesArray: string[][] = [],
|
||||||
|
ticketCountLabel = '',
|
||||||
|
price = '',
|
||||||
|
maskedBarcode = '',
|
||||||
|
displayBarcode = '';
|
||||||
|
|
||||||
|
if (rawLabel) {
|
||||||
|
// 1️⃣ Extract pool (first word)
|
||||||
|
const parts = rawLabel.split(/\s+/);
|
||||||
|
pool = parts[0];
|
||||||
|
|
||||||
|
// 2️⃣ Extract ticket count (*n) & price if exists
|
||||||
|
const countMatch = rawLabel.match(/\*(\d+)(?:\s+(Rs\s*\d+))?/);
|
||||||
|
if (countMatch) {
|
||||||
|
ticketCountLabel = `*${countMatch[1]}`;
|
||||||
|
price = countMatch[2] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Extract horses part (between pool name & ticket count)
|
||||||
|
const horsesPartMatch = rawLabel.match(/^\w+\s+(.+?)\s+\*\d+/);
|
||||||
|
if (horsesPartMatch) {
|
||||||
|
horses = horsesPartMatch[1].trim();
|
||||||
|
|
||||||
|
// Special pools split into races
|
||||||
|
if (['MJP', 'JKP', 'TRE'].includes(pool)) {
|
||||||
|
horsesArray = horses.split('/').map((r) => r.trim().split(',').map((h) => h.trim()));
|
||||||
|
} else {
|
||||||
|
horsesArray = [horses.split(',').map((h) => h.trim())];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (barcodeId) {
|
if (barcodeId) {
|
||||||
const last4 = barcodeId.slice(-4);
|
const last4 = barcodeId.slice(-4);
|
||||||
maskedBarcode = btoa(barcodeId.slice(0, -4));
|
const encryptedPart = btoa(barcodeId.slice(0, -4));
|
||||||
displayBarcode = '********' + last4;
|
maskedBarcode = encryptedPart;
|
||||||
}
|
displayBarcode = '********' + last4;
|
||||||
|
}
|
||||||
|
|
||||||
return { pool, horses, horsesArray, ticketCountLabel, price, numbers: ticket.numbers || [], count: ticket.ticketCount || 0, amount: ticket.totalAmount || 0, maskedBarcode, displayBarcode };
|
console.log(maskedBarcode);
|
||||||
});
|
console.log('Decoded:', atob(maskedBarcode));
|
||||||
|
|
||||||
|
return {
|
||||||
|
pool,
|
||||||
|
horses,
|
||||||
|
horsesArray,
|
||||||
|
ticketCountLabel,
|
||||||
|
price,
|
||||||
|
numbers,
|
||||||
|
count,
|
||||||
|
amount,
|
||||||
|
maskedBarcode,
|
||||||
|
displayBarcode,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('No tickets found in localStorage.');
|
||||||
|
this.formattedTicketLogs = [];
|
||||||
|
}
|
||||||
|
|
||||||
this.showLogModal = true;
|
this.showLogModal = true;
|
||||||
|
console.log('Log modal opened. Final formattedTicketLogs:', this.formattedTicketLogs);
|
||||||
}
|
}
|
||||||
|
|
||||||
logout(): void {
|
logout(): void {
|
||||||
const name = localStorage.getItem('userName') || 'Unknown User';
|
const name = localStorage.getItem('userName') || 'Unknown User';
|
||||||
const employeeId = localStorage.getItem('employeeId') || '000000';
|
const employeeId = localStorage.getItem('employeeId') || '000000';
|
||||||
const printData = { name, employeeId, action: 'logout', type: 'logout' };
|
|
||||||
console.log('[LOGOUT] Initiating logout with printData:', printData);
|
|
||||||
|
|
||||||
|
const printData = {
|
||||||
|
name,
|
||||||
|
employeeId,
|
||||||
|
action: 'logout',
|
||||||
|
type: 'logout', // This is the missing piece
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[LOGOUT] Initiating logout with printData:', printData);
|
||||||
|
|
||||||
fetch('http://localhost:9100/print', {
|
fetch('http://localhost:9100/print', {
|
||||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(printData),
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(printData),
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (!res.ok) throw new Error('Logout print failed');
|
if (!res.ok) throw new Error('Logout print failed');
|
||||||
@ -335,8 +551,13 @@ export class NavbarComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (this.subscription) this.subscription.unsubscribe();
|
if (this.subscription) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByHorse(index: number, item: number): number { return item; }
|
// Add trackByHorse for use in *ngFor
|
||||||
|
trackByHorse(index: number, item: number): number {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,10 +59,10 @@ export class SelectionService {
|
|||||||
|
|
||||||
if ((numbers.length > 0 || numbers.includes('F')) && value > 0 && label) {
|
if ((numbers.length > 0 || numbers.includes('F')) && value > 0 && label) {
|
||||||
switch (label) {
|
switch (label) {
|
||||||
case 'WIN':
|
case 'WNP':
|
||||||
case 'SHP':
|
case 'SHP':
|
||||||
case 'THP':
|
case 'THP':
|
||||||
case 'PLC':
|
case 'PLP':
|
||||||
case 'SHW':
|
case 'SHW':
|
||||||
case 'WSP': {
|
case 'WSP': {
|
||||||
updated.total = numbers.includes('F')
|
updated.total = numbers.includes('F')
|
||||||
@ -71,7 +71,7 @@ export class SelectionService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'FOR': {
|
case 'FRP': {
|
||||||
const dashIndex = numbers.indexOf('-');
|
const dashIndex = numbers.indexOf('-');
|
||||||
const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex);
|
const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex);
|
||||||
const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1);
|
const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1);
|
||||||
@ -106,7 +106,7 @@ export class SelectionService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'QUI': {
|
case 'QNP': {
|
||||||
const dashIndex = numbers.indexOf('-');
|
const dashIndex = numbers.indexOf('-');
|
||||||
const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex);
|
const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex);
|
||||||
const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1);
|
const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1);
|
||||||
@ -149,7 +149,7 @@ export class SelectionService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'TAN': {
|
case 'TNP': {
|
||||||
const dashIndices = numbers
|
const dashIndices = numbers
|
||||||
.map((n, idx) => (n === '-' ? idx : -1))
|
.map((n, idx) => (n === '-' ? idx : -1))
|
||||||
.filter(idx => idx !== -1);
|
.filter(idx => idx !== -1);
|
||||||
@ -347,7 +347,7 @@ export class SelectionService {
|
|||||||
|
|
||||||
if (!base.numbers.length || base.value <= 0) return [];
|
if (!base.numbers.length || base.value <= 0) return [];
|
||||||
|
|
||||||
return ['WIN', 'SHP', 'PLC'].map(label => {
|
return ['WNP', 'SHP', 'PLP'].map(label => {
|
||||||
const newRow: SelectionData = {
|
const newRow: SelectionData = {
|
||||||
...base,
|
...base,
|
||||||
label,
|
label,
|
||||||
|
|||||||
@ -24,14 +24,14 @@ import _ from 'lodash';
|
|||||||
export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||||
@Input() ticketingActive: boolean = false;
|
@Input() ticketingActive: boolean = false;
|
||||||
|
|
||||||
public twoGroupLabels = ['FOR', 'QUI'];
|
public twoGroupLabels = ['FRP', 'QNP'];
|
||||||
public multiLegLabels = ['TRE', 'MJP', 'JKP'];
|
public multiLegLabels = ['TRE', 'MJP', 'JKP'];
|
||||||
public threeGroupLabels = ['TAN'];
|
public threeGroupLabels = ['TNP'];
|
||||||
public allowedFieldLabels = ['WIN', 'SHP', 'THP', 'PLC', 'SHW'];
|
public allowedFieldLabels = ['WNP', 'SHP', 'THP', 'PLP', 'SHW'];
|
||||||
|
|
||||||
labels: string[] = [
|
labels: string[] = [
|
||||||
'WIN', 'SHP', 'THP', 'PLC', 'SHW', 'FOR',
|
'WNP', 'SHP', 'THP', 'PLP', 'SHW', 'FRP',
|
||||||
'QUI', 'TAN', 'EXA', 'WSP', 'TRE', 'MJP',
|
'QNP', 'TNP', 'EXA', 'WSP', 'TRE', 'MJP',
|
||||||
'JKP', 'SJP', '.'
|
'JKP', 'SJP', '.'
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -53,11 +53,11 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
showLimitPopup: boolean = false;
|
showLimitPopup: boolean = false;
|
||||||
disabledLabels: string[] = ['SHW', 'SJP', '.'];
|
disabledLabels: string[] = ['SHW', 'SJP', '.'];
|
||||||
|
|
||||||
// TAN logic
|
// TNP logic
|
||||||
tanGroupStage = 0;
|
tanGroupStage = 0;
|
||||||
tanGroups: (number | string)[][] = [[], [], []];
|
tanGroups: (number | string)[][] = [[], [], []];
|
||||||
|
|
||||||
// FOR/QUI logic
|
// FRP/QNP logic
|
||||||
isFirstGroupComplete = false;
|
isFirstGroupComplete = false;
|
||||||
firstGroup: (number | string)[] = [];
|
firstGroup: (number | string)[] = [];
|
||||||
secondGroup: (number | string)[] = [];
|
secondGroup: (number | string)[] = [];
|
||||||
@ -257,10 +257,10 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
get showShashEnter(): boolean {
|
get showShashEnter(): boolean {
|
||||||
const label = this.selectedLabel || '';
|
const label = this.selectedLabel || '';
|
||||||
if (['FOR', 'QUI', 'TAN'].includes(label) && this.isBoxed) {
|
if (['FRP', 'QNP', 'TNP'].includes(label) && this.isBoxed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const specialLabels = ['FOR', 'QUI', 'TAN', 'EXA', 'TRE', 'MJP', 'JKP', '.'];
|
const specialLabels = ['FRP', 'QNP', 'TNP', 'EXA', 'TRE', 'MJP', 'JKP', '.'];
|
||||||
if (this.multiLegLabels.includes(label)) {
|
if (this.multiLegLabels.includes(label)) {
|
||||||
const maxLegs = this.getMaxLegs(this.currentPool || '');
|
const maxLegs = this.getMaxLegs(this.currentPool || '');
|
||||||
// Hide Shash Enter if on the final leg
|
// Hide Shash Enter if on the final leg
|
||||||
@ -270,7 +270,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isShashEnterDisabled(): boolean {
|
get isShashEnterDisabled(): boolean {
|
||||||
if (this.selectedLabel === 'TAN') {
|
if (this.selectedLabel === 'TNP') {
|
||||||
if (this.isBoxed) {
|
if (this.isBoxed) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -292,15 +292,15 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
get isBoxToggleDisabled(): boolean {
|
get isBoxToggleDisabled(): boolean {
|
||||||
if (!this.selectedLabel) return true;
|
if (!this.selectedLabel) return true;
|
||||||
const disallowedBoxLabels = ['WIN', 'SHP', 'THP', 'PLC', 'WSP', 'SHW', 'TRE', 'MJP', 'JKP'];
|
const disallowedBoxLabels = ['WNP', 'SHP', 'THP', 'PLP', 'WSP', 'SHW', 'TRE', 'MJP', 'JKP'];
|
||||||
if (disallowedBoxLabels.includes(this.selectedLabel)) {
|
if (disallowedBoxLabels.includes(this.selectedLabel)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.selectedNumbers.includes('F')) {
|
if (this.selectedNumbers.includes('F')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Disable Box toggle for TAN, QUI, FOR after any number is selected
|
// Disable Box toggle for TNP, QNP, FRP after any number is selected
|
||||||
if (['TAN', 'QUI', 'FOR'].includes(this.selectedLabel) && this.selectedNumbers.length > 0) {
|
if (['TNP', 'QNP', 'FRP'].includes(this.selectedLabel) && this.selectedNumbers.length > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -308,8 +308,8 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
get isWSPSelection(): boolean {
|
get isWSPSelection(): boolean {
|
||||||
const currentRow = this.selectionService.getCurrentRow();
|
const currentRow = this.selectionService.getCurrentRow();
|
||||||
return ['WIN', 'SHP', 'PLC'].includes(currentRow.label) ||
|
return ['WNP', 'SHP', 'PLP'].includes(currentRow.label) ||
|
||||||
this.selectionService.getSelections().some(sel => ['WIN', 'SHP', 'PLC'].includes(sel.label));
|
this.selectionService.getSelections().some(sel => ['WNP', 'SHP', 'PLP'].includes(sel.label));
|
||||||
}
|
}
|
||||||
|
|
||||||
private chunk<T>(array: T[], size: number): T[][] {
|
private chunk<T>(array: T[], size: number): T[][] {
|
||||||
@ -330,19 +330,19 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
if (!this.actualRunners.has(number)) return true;
|
if (!this.actualRunners.has(number)) return true;
|
||||||
// Disable if total amount limit reached
|
// Disable if total amount limit reached
|
||||||
if (this.totalAmountLimitReached) return true;
|
if (this.totalAmountLimitReached) return true;
|
||||||
// Allow all numbers for TAN when boxed, but disable selected numbers
|
// Allow all numbers for TNP when boxed, but disable selected numbers
|
||||||
if (this.selectedLabel === 'TAN' && this.isBoxed) {
|
if (this.selectedLabel === 'TNP' && this.isBoxed) {
|
||||||
return this.selectedNumbers.includes(number);
|
return this.selectedNumbers.includes(number);
|
||||||
}
|
}
|
||||||
// TAN (unboxed): Disable numbers already selected in the current group
|
// TNP (unboxed): Disable numbers already selected in the current group
|
||||||
if (this.selectedLabel === 'TAN') {
|
if (this.selectedLabel === 'TNP') {
|
||||||
return this.tanGroups[this.tanGroupStage].includes(number);
|
return this.tanGroups[this.tanGroupStage].includes(number);
|
||||||
}
|
}
|
||||||
// Multi-leg pools (TRE, MJP, JKP): Disable numbers already selected in the current leg
|
// Multi-leg pools (TRE, MJP, JKP): Disable numbers already selected in the current leg
|
||||||
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
|
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
|
||||||
return this.multiLegGroups[this.multiLegStage].includes(number);
|
return this.multiLegGroups[this.multiLegStage].includes(number);
|
||||||
}
|
}
|
||||||
// Two-group pools (FOR, QUI): Disable numbers already selected in the current group
|
// Two-group pools (FRP, QNP): Disable numbers already selected in the current group
|
||||||
if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
||||||
if (!this.isFirstGroupComplete) {
|
if (!this.isFirstGroupComplete) {
|
||||||
return this.firstGroup.includes(number);
|
return this.firstGroup.includes(number);
|
||||||
@ -350,7 +350,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
return this.secondGroup.includes(number);
|
return this.secondGroup.includes(number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Default case for WIN, SHP, THP, PLC, etc.: Disable if already selected
|
// Default case for WNP, SHP, THP, PLP, etc.: Disable if already selected
|
||||||
return this.selectedNumbers.includes(number);
|
return this.selectedNumbers.includes(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,7 +402,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
//----------------------------------ADDED THIS -----------------------------------------------------
|
//----------------------------------ADDED THIS -----------------------------------------------------
|
||||||
|
|
||||||
if (label === 'WSP') {
|
if (label === 'WSP') {
|
||||||
const wspLabels = ['WIN', 'SHP', 'PLC'];
|
const wspLabels = ['WNP', 'SHP', 'PLP'];
|
||||||
this.wspTicketStage = 0;
|
this.wspTicketStage = 0;
|
||||||
this.selectionService.finalizeCurrentRow();
|
this.selectionService.finalizeCurrentRow();
|
||||||
const currentSelections = this.selectionService.getSelections();
|
const currentSelections = this.selectionService.getSelections();
|
||||||
@ -429,7 +429,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
// Reset group states
|
// Reset group states
|
||||||
this.tanGroupStage = 0;
|
this.tanGroupStage = 0;
|
||||||
this.tanGroups = [[], [], []];
|
this.tanGroups = [[], [], []];
|
||||||
// Reset FOR/QUI
|
// Reset FRP/QNP
|
||||||
this.isFirstGroupComplete = false;
|
this.isFirstGroupComplete = false;
|
||||||
this.firstGroup = [];
|
this.firstGroup = [];
|
||||||
this.secondGroup = [];
|
this.secondGroup = [];
|
||||||
@ -466,8 +466,8 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
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;
|
||||||
|
|
||||||
// TAN boxed
|
// TNP boxed
|
||||||
if (this.selectedLabel === 'TAN' && this.isBoxed) {
|
if (this.selectedLabel === 'TNP' && this.isBoxed) {
|
||||||
if (!this.selectedNumbers.includes(number)) {
|
if (!this.selectedNumbers.includes(number)) {
|
||||||
const currentNumbers = this.selectedNumbers.filter(n => typeof n === 'number') as number[];
|
const currentNumbers = this.selectedNumbers.filter(n => typeof n === 'number') as number[];
|
||||||
const allBoxed = [...currentNumbers, number];
|
const allBoxed = [...currentNumbers, number];
|
||||||
@ -482,14 +482,14 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
this.selectionService.updatePartial({
|
this.selectionService.updatePartial({
|
||||||
numbers: [...this.selectedNumbers],
|
numbers: [...this.selectedNumbers],
|
||||||
isBoxed: true,
|
isBoxed: true,
|
||||||
label: 'TAN'
|
label: 'TNP'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TAN unboxed
|
// TNP unboxed
|
||||||
if (this.selectedLabel === 'TAN') {
|
if (this.selectedLabel === 'TNP') {
|
||||||
if (!this.tanGroups[this.tanGroupStage].includes(number)) {
|
if (!this.tanGroups[this.tanGroupStage].includes(number)) {
|
||||||
this.tanGroups[this.tanGroupStage].push(number);
|
this.tanGroups[this.tanGroupStage].push(number);
|
||||||
const combined: (number | string)[] = [...this.tanGroups[0]];
|
const combined: (number | string)[] = [...this.tanGroups[0]];
|
||||||
@ -511,7 +511,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// FOR/QUI logic
|
// FRP/QNP logic
|
||||||
if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
||||||
console.log('Selected label:', this.selectedLabel);
|
console.log('Selected label:', this.selectedLabel);
|
||||||
console.log('Current number clicked:', number);
|
console.log('Current number clicked:', number);
|
||||||
@ -548,10 +548,10 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
if (!this.selectedNumbers.includes(number)) {
|
if (!this.selectedNumbers.includes(number)) {
|
||||||
this.selectedNumbers.push(number);
|
this.selectedNumbers.push(number);
|
||||||
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
|
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
|
||||||
// ✅ Special logic: If WSP, mirror number to WIN, SHP, and THP
|
// ✅ Special logic: If WSP, mirror number to WNP, SHP, and THP
|
||||||
|
|
||||||
if (this.selectedLabel === 'WSP') {
|
if (this.selectedLabel === 'WSP') {
|
||||||
const labelsToUpdate = ['WIN', 'SHP', 'PLC'];
|
const labelsToUpdate = ['WNP', 'SHP', 'PLP'];
|
||||||
const selections = this.selectionService.getSelections();
|
const selections = this.selectionService.getSelections();
|
||||||
const updated = selections.map(sel => {
|
const updated = selections.map(sel => {
|
||||||
if (labelsToUpdate.includes(sel.label)) {
|
if (labelsToUpdate.includes(sel.label)) {
|
||||||
@ -583,10 +583,10 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
|
|
||||||
createVirtualRowsFromWSP(): SelectionData[] {
|
createVirtualRowsFromWSP(): SelectionData[] {
|
||||||
const base = this.selectionService.getCurrentRow();
|
const base = this.selectionService.getCurrentRow();
|
||||||
if (!base.numbers.length || base.value <= 0 || ['WIN', 'SHP', 'PLC'].includes(base.label)) {
|
if (!base.numbers.length || base.value <= 0 || ['WNP', 'SHP', 'PLP'].includes(base.label)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return ['WIN', 'SHP', 'PLC'].map(label => {
|
return ['WNP', 'SHP', 'PLP'].map(label => {
|
||||||
const newRow: SelectionData = {
|
const newRow: SelectionData = {
|
||||||
...base,
|
...base,
|
||||||
label,
|
label,
|
||||||
@ -602,7 +602,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let combinations = 1;
|
let combinations = 1;
|
||||||
if (['TAN', 'FOR', 'QUI'].includes(row.label)) {
|
if (['TNP', 'FRP', 'QNP'].includes(row.label)) {
|
||||||
combinations = row.numbers.length * (row.numbers.length - 1);
|
combinations = row.numbers.length * (row.numbers.length - 1);
|
||||||
} else if (['TRE', 'JKP', 'MJP'].includes(row.label)) {
|
} else if (['TRE', 'JKP', 'MJP'].includes(row.label)) {
|
||||||
const legs = row.numbers.join('').split('/').map(leg => leg.split(',').length);
|
const legs = row.numbers.join('').split('/').map(leg => leg.split(',').length);
|
||||||
@ -617,7 +617,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
clearWSPSelection() {
|
clearWSPSelection() {
|
||||||
if (this.selectedLabel !== 'WSP') return;
|
if (this.selectedLabel !== 'WSP') return;
|
||||||
|
|
||||||
const labels = ['WIN', 'SHP', 'PLC'];
|
const labels = ['WNP', 'SHP', 'PLP'];
|
||||||
const targetLabel = labels[this.wspTicketStage];
|
const targetLabel = labels[this.wspTicketStage];
|
||||||
// Update only the current WSP stage's value to 0
|
// Update only the current WSP stage's value to 0
|
||||||
const updatedSelections = this.selectionService.getSelections().map(sel => {
|
const updatedSelections = this.selectionService.getSelections().map(sel => {
|
||||||
@ -641,7 +641,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
|
|
||||||
const value = parseFloat(this.padValue) || 0;
|
const value = parseFloat(this.padValue) || 0;
|
||||||
if (this.selectedLabel === 'WSP') {
|
if (this.selectedLabel === 'WSP') {
|
||||||
const labels = ['WIN', 'SHP', 'PLC'];
|
const labels = ['WNP', 'SHP', 'PLP'];
|
||||||
const targetLabel = labels[this.wspTicketStage];
|
const targetLabel = labels[this.wspTicketStage];
|
||||||
const selections = this.selectionService.getSelections();
|
const selections = this.selectionService.getSelections();
|
||||||
// Find the current WSP row to ensure numbers are synchronized
|
// Find the current WSP row to ensure numbers are synchronized
|
||||||
@ -660,7 +660,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.selectionService.setSelections(updatedSelections);
|
this.selectionService.setSelections(updatedSelections);
|
||||||
// Only increment stage if not at the last stage (PLC)
|
// Only increment stage if not at the last stage (PLP)
|
||||||
|
|
||||||
if (this.wspTicketStage < 2) {
|
if (this.wspTicketStage < 2) {
|
||||||
this.wspTicketStage++;
|
this.wspTicketStage++;
|
||||||
@ -678,9 +678,9 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onShashEnter() {
|
onShashEnter() {
|
||||||
if (this.selectedLabel === 'TAN' && this.isBoxed) return;
|
if (this.selectedLabel === 'TNP' && this.isBoxed) return;
|
||||||
|
|
||||||
if (this.selectedLabel === 'TAN') {
|
if (this.selectedLabel === 'TNP') {
|
||||||
if (this.tanGroupStage < 2 && this.tanGroups[this.tanGroupStage].length > 0) {
|
if (this.tanGroupStage < 2 && this.tanGroups[this.tanGroupStage].length > 0) {
|
||||||
this.tanGroupStage++;
|
this.tanGroupStage++;
|
||||||
const combined: (number | string)[] = [...this.tanGroups[0]];
|
const combined: (number | string)[] = [...this.tanGroups[0]];
|
||||||
@ -746,7 +746,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
const value = parseFloat(this.padValue) || 0;
|
const value = parseFloat(this.padValue) || 0;
|
||||||
|
|
||||||
if (this.selectedLabel === 'WSP') {
|
if (this.selectedLabel === 'WSP') {
|
||||||
const labels = ['WIN', 'SHP', 'PLC'];
|
const labels = ['WNP', 'SHP', 'PLP'];
|
||||||
const targetLabel = labels[this.wspTicketStage];
|
const targetLabel = labels[this.wspTicketStage];
|
||||||
const updatedSelections = this.selectionService.getSelections().map(sel => {
|
const updatedSelections = this.selectionService.getSelections().map(sel => {
|
||||||
if (sel.label === targetLabel && JSON.stringify(sel.numbers) === JSON.stringify(this.selectedNumbers)) {
|
if (sel.label === targetLabel && JSON.stringify(sel.numbers) === JSON.stringify(this.selectedNumbers)) {
|
||||||
@ -884,8 +884,8 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
const selections = this.selectionService.getSelections();
|
const selections = this.selectionService.getSelections();
|
||||||
const currentRow = this.selectionService.getCurrentRow();
|
const currentRow = this.selectionService.getCurrentRow();
|
||||||
if (this.selectedLabel === 'WSP') {
|
if (this.selectedLabel === 'WSP') {
|
||||||
// For WSP, require all three rows (WIN, SHP, PLC) to have valid numbers and values >= 1
|
// For WSP, require all three rows (WNP, SHP, PLP) to have valid numbers and values >= 1
|
||||||
const wspLabels = ['WIN', 'SHP', 'PLC'];
|
const wspLabels = ['WNP', 'SHP', 'PLP'];
|
||||||
const wspSelections = selections.filter(sel => wspLabels.includes(sel.label));
|
const wspSelections = selections.filter(sel => wspLabels.includes(sel.label));
|
||||||
const allWSPRowsValid = wspSelections.length === 3 && wspSelections.every(row =>
|
const allWSPRowsValid = wspSelections.length === 3 && wspSelections.every(row =>
|
||||||
row.label && row.numbers && row.numbers.length > 0 && row.value >= 1 && row.total > 0
|
row.label && row.numbers && row.numbers.length > 0 && row.value >= 1 && row.total > 0
|
||||||
@ -933,7 +933,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
if (this.selectedLabel === 'WSP') {
|
if (this.selectedLabel === 'WSP') {
|
||||||
const virtualRows = this.createVirtualRowsFromWSP();
|
const virtualRows = this.createVirtualRowsFromWSP();
|
||||||
const currentSelections = this.selectionService.getSelections();
|
const currentSelections = this.selectionService.getSelections();
|
||||||
const nonWSPSelections = currentSelections.filter(sel => !['WIN', 'SHP', 'PLC'].includes(sel.label));
|
const nonWSPSelections = currentSelections.filter(sel => !['WNP', 'SHP', 'PLP'].includes(sel.label));
|
||||||
this.selectionService.setSelections([...nonWSPSelections, ...virtualRows]);
|
this.selectionService.setSelections([...nonWSPSelections, ...virtualRows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -972,7 +972,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|||||||
|
|
||||||
|
|
||||||
//-------------------PRINT LOGIC----------------------------------------
|
//-------------------PRINT LOGIC----------------------------------------
|
||||||
printTicket() {
|
async printTicket() {
|
||||||
const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + sel.total, 0);
|
const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + sel.total, 0);
|
||||||
if (selectionsTotal + this.currentTotal > 5000) {
|
if (selectionsTotal + this.currentTotal > 5000) {
|
||||||
this.showLimitPopup = true;
|
this.showLimitPopup = true;
|
||||||
@ -987,7 +987,7 @@ printTicket() {
|
|||||||
const currentRow = this.selectionService.getCurrentRow();
|
const currentRow = this.selectionService.getCurrentRow();
|
||||||
|
|
||||||
if (this.selectedLabel === 'WSP') {
|
if (this.selectedLabel === 'WSP') {
|
||||||
selections = selections.filter(sel => ['WIN', 'SHP', 'PLC'].includes(sel.label));
|
selections = selections.filter(sel => ['WNP', 'SHP', 'PLP'].includes(sel.label));
|
||||||
}
|
}
|
||||||
|
|
||||||
let allRows = [...selections];
|
let allRows = [...selections];
|
||||||
@ -1003,7 +1003,7 @@ printTicket() {
|
|||||||
if (!currentRow.total) {
|
if (!currentRow.total) {
|
||||||
let combinations = 1;
|
let combinations = 1;
|
||||||
|
|
||||||
if (['TAN', 'FOR', 'QUI'].includes(currentRow.label)) {
|
if (['TNP', 'FRP', 'QNP'].includes(currentRow.label)) {
|
||||||
combinations = currentRow.numbers.length * (currentRow.numbers.length - 1);
|
combinations = currentRow.numbers.length * (currentRow.numbers.length - 1);
|
||||||
} else if (['TRE', 'JKP', 'MJP'].includes(currentRow.label)) {
|
} else if (['TRE', 'JKP', 'MJP'].includes(currentRow.label)) {
|
||||||
combinations = 1; // or your specific logic
|
combinations = 1; // or your specific logic
|
||||||
@ -1026,7 +1026,7 @@ printTicket() {
|
|||||||
const totalAmount = allRows.reduce((sum, row) => sum + (row.total || 0), 0);
|
const totalAmount = allRows.reduce((sum, row) => sum + (row.total || 0), 0);
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const venue = 'MYS';
|
const venue = this.structuredRaceCard?.venue || '';
|
||||||
const day = String(now.getDate()).padStart(2, '0');
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
const year = String(now.getFullYear()).slice(-2);
|
const year = String(now.getFullYear()).slice(-2);
|
||||||
@ -1156,8 +1156,8 @@ displayNumbers = displayNumbers.flatMap((n) => {
|
|||||||
|
|
||||||
let numbersStr = '';
|
let numbersStr = '';
|
||||||
|
|
||||||
// 🎯 FOR, QUI, TAN logic with box check
|
// 🎯 FRP, QNP, TNP logic with box check
|
||||||
if (['FOR', 'QUI'].includes(row.label)) {
|
if (['FRP', 'QNP'].includes(row.label)) {
|
||||||
// const actualNumbers = displayNumbers.filter(n => n !== '#' && n !== '-').join(',');
|
// const actualNumbers = displayNumbers.filter(n => n !== '#' && n !== '-').join(',');
|
||||||
const actualNumbers = displayNumbers.filter(n => n !== '#').join(',');
|
const actualNumbers = displayNumbers.filter(n => n !== '#').join(',');
|
||||||
if (row.numbers.includes('#') || row.isBoxed) { // ✅ box condition
|
if (row.numbers.includes('#') || row.isBoxed) { // ✅ box condition
|
||||||
@ -1179,7 +1179,7 @@ displayNumbers = displayNumbers.flatMap((n) => {
|
|||||||
return `${label}${numbers} ${value} ${total}`;
|
return `${label}${numbers} ${value} ${total}`;
|
||||||
}).join('\n');
|
}).join('\n');
|
||||||
|
|
||||||
//------------------------------------WIN LABELS ENDS HERE --------------------------------
|
//------------------------------------WNP LABELS ENDS HERE --------------------------------
|
||||||
|
|
||||||
// ✅ Print preview
|
// ✅ Print preview
|
||||||
const printData = {
|
const printData = {
|
||||||
@ -1205,6 +1205,213 @@ displayNumbers = displayNumbers.flatMap((n) => {
|
|||||||
console.log(`Date/Time : ${now.toLocaleString()}`);
|
console.log(`Date/Time : ${now.toLocaleString()}`);
|
||||||
console.log('-----------------------------');
|
console.log('-----------------------------');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------CONSOLE LOGS EDITTED FORMATTED ----------------------------------
|
||||||
|
// ------------------- Simulated console-ticket printing (REPLACE THIS BLOCK) -------------------
|
||||||
|
console.log('--- Simulated Ticket Print ---');
|
||||||
|
console.log(`Ticket ID : ${printData.ticketId}`);
|
||||||
|
console.log(`Barcode ID : ${printData.barcodeId}`);
|
||||||
|
console.log(`|||||| ||| | ||||| |||| |`); // Dummy barcode
|
||||||
|
console.log(`WIN Labels :`);
|
||||||
|
|
||||||
|
// helper to pad each horse number in a leg (handles " / " multi-leg separators)
|
||||||
|
function formatNumbers(numStr: string) {
|
||||||
|
const legs = numStr.split(/\s*\/\s*/).map(leg => {
|
||||||
|
const parts = leg.split(',').map(p => p.trim()).filter(Boolean);
|
||||||
|
return parts.map(p => {
|
||||||
|
const numeric = p.match(/^\d+$/);
|
||||||
|
return numeric ? p.padStart(2, '0') : p;
|
||||||
|
}).join(',');
|
||||||
|
});
|
||||||
|
return legs.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper to pad ticket count to 3 digits (001..999)
|
||||||
|
function padTicketCountToThree(n: number) {
|
||||||
|
const num = Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0;
|
||||||
|
return String(num).padStart(3, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build compact entries "LABEL numbers*NNN"
|
||||||
|
const formattedEntries: string[] = [];
|
||||||
|
printData.winLabels.split('\n').forEach(line => {
|
||||||
|
const m = line.match(/^(\S+)\s+(.+?)\s+\*(\d+)\b/);
|
||||||
|
if (m) {
|
||||||
|
const label = m[1];
|
||||||
|
const rawNums = m[2].trim();
|
||||||
|
const value = Number(m[3]) || 0;
|
||||||
|
const formattedNums = formatNumbers(rawNums);
|
||||||
|
const valueStr = `*${padTicketCountToThree(value)}`;
|
||||||
|
formattedEntries.push(`${label} ${formattedNums}${valueStr}`);
|
||||||
|
} else {
|
||||||
|
if (line.trim()) formattedEntries.push(line.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function compactifyEntry(entry: string) {
|
||||||
|
const m = entry.match(/^(\S+)\s+(.+?)(\*\d{1,})?$/);
|
||||||
|
if (!m) return entry;
|
||||||
|
|
||||||
|
const label = m[1];
|
||||||
|
let numsPart = (m[2] || '').trim();
|
||||||
|
const starPart = m[3] || '';
|
||||||
|
|
||||||
|
// Normalize whitespace
|
||||||
|
numsPart = numsPart.replace(/\s+/g, ' ').trim();
|
||||||
|
|
||||||
|
// Build groups by treating '-' token as a group separator
|
||||||
|
const rawTokens = numsPart.split(',').map(s => s.trim());
|
||||||
|
|
||||||
|
const groups: string[][] = [];
|
||||||
|
let currentGroup: string[] = [];
|
||||||
|
|
||||||
|
for (const tok of rawTokens) {
|
||||||
|
if (!tok || tok === '#') {
|
||||||
|
// skip stray empty / '#' tokens
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (tok === '-') {
|
||||||
|
// push current group (even if empty) and start new group
|
||||||
|
groups.push(currentGroup);
|
||||||
|
currentGroup = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
currentGroup.push(tok);
|
||||||
|
}
|
||||||
|
// push last group
|
||||||
|
groups.push(currentGroup);
|
||||||
|
|
||||||
|
// helper to normalize token (zero-pad numeric)
|
||||||
|
const normalizeToken = (tok: string) => {
|
||||||
|
const num = tok.match(/^\d+$/);
|
||||||
|
return num ? tok.padStart(2, '0') : tok;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter out groups that became empty due to stray tokens, but preserve intentional empties if user had them.
|
||||||
|
const filteredGroups = groups
|
||||||
|
.map(g => g.filter(t => t && t !== '-' && t !== '#'))
|
||||||
|
.filter((g, idx) => !(g.length === 0 && groups.length === 1)); // keep single empty group only if it was the only group
|
||||||
|
|
||||||
|
// If there are multiple groups (from '-' markers), format them joined by hyphen
|
||||||
|
if (filteredGroups.length > 1) {
|
||||||
|
const groupStrs = filteredGroups.map(g => g.map(normalizeToken).join(','));
|
||||||
|
return `${label} ${groupStrs.join('-')}${starPart}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No explicit '-' groups (single group only). fallback to duplicated-halves detection
|
||||||
|
const singleTokens = filteredGroups[0] || [];
|
||||||
|
if (singleTokens.length >= 2 && singleTokens.length % 2 === 0) {
|
||||||
|
const half = singleTokens.length / 2;
|
||||||
|
const firstHalf = singleTokens.slice(0, half).map(normalizeToken).join(',');
|
||||||
|
const secondHalf = singleTokens.slice(half).map(normalizeToken).join(',');
|
||||||
|
if (firstHalf === secondHalf) {
|
||||||
|
return `${label} ${firstHalf}-${secondHalf}${starPart}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final fallback: normalized comma-joined tokens
|
||||||
|
const normalized = singleTokens.map(normalizeToken).join(',');
|
||||||
|
return `${label} ${normalized}${starPart}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Apply compactify to each entry and join into one string
|
||||||
|
let formattedWinLabels = formattedEntries.map(compactifyEntry).join('');
|
||||||
|
|
||||||
|
// Fallback if nothing parsed
|
||||||
|
if (!formattedWinLabels) {
|
||||||
|
formattedWinLabels = printData.winLabels || '(no win labels)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show formatted result and totals
|
||||||
|
const formattedTotal = `₹${printData.totalAmount}`;
|
||||||
|
console.log(formattedWinLabels);
|
||||||
|
console.log(formattedTotal);
|
||||||
|
console.log(`GST Number : ${printData.gstNumber}`);
|
||||||
|
console.log(`Date/Time : ${now.toLocaleString()}`);
|
||||||
|
console.log('-----------------------------');
|
||||||
|
|
||||||
|
// ---------------------------------- CONSOLE LOG ENDS HERE ---------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//----------------------------------------SENDIND DATA TO BACKEND ----------------------------------------------------
|
||||||
|
// Build the commit payload as before (btId, raceVenue, raceDt, raceNum, betInfo, nHorseBits, tktVal)
|
||||||
|
const ticketParts = (printData.ticketId || '').split('/');
|
||||||
|
const raceVenue = ticketParts[0] || 'MYS';
|
||||||
|
const dateStr = ticketParts[1] || ''; // "20250907"
|
||||||
|
const raceDt = dateStr && dateStr.length === 8
|
||||||
|
? `${dateStr.slice(0,4)}/${dateStr.slice(4,6)}/${dateStr.slice(6,8)}`
|
||||||
|
: (new Date()).toISOString().slice(0,10).replace(/-/g,'/');
|
||||||
|
const raceNumRaw = ticketParts[2] || '1';
|
||||||
|
const raceNum = String(Number(raceNumRaw)).padStart(2,'0');
|
||||||
|
const btId_bc = (printData.barcodeId || '0000').toString().slice(0,4);
|
||||||
|
const betInfo = formattedWinLabels || printData.winLabels || '';
|
||||||
|
const nHorseBits = [0,0,0,0,0,0,0,0];
|
||||||
|
const tktVal = Number(printData.totalAmount) || 0;
|
||||||
|
|
||||||
|
const commitPayload = {
|
||||||
|
btMake: "I",
|
||||||
|
btId: btId_bc,
|
||||||
|
raceVenue: raceVenue,
|
||||||
|
raceDt: raceDt,
|
||||||
|
raceNum: raceNum,
|
||||||
|
betInfo: betInfo,
|
||||||
|
nHorseBits: nHorseBits,
|
||||||
|
moneyTyp: "C",
|
||||||
|
tktVal: tktVal,
|
||||||
|
repeatFlag: "N"
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('➡️ Sending commit to /isr/commit:', commitPayload);
|
||||||
|
|
||||||
|
let commitJson = null;
|
||||||
|
try {
|
||||||
|
const commitResp = await fetch('http://localhost:8084/isr/commit', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(commitPayload),
|
||||||
|
// optionally set a timeout by using AbortController if you need it
|
||||||
|
});
|
||||||
|
|
||||||
|
// attempt to parse JSON safely
|
||||||
|
const txt = await commitResp.text();
|
||||||
|
try {
|
||||||
|
commitJson = txt ? JSON.parse(txt) : null;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ /isr/commit returned non-JSON response:', txt);
|
||||||
|
commitJson = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If HTTP status not OK, treat as failure
|
||||||
|
if (!commitResp.ok) {
|
||||||
|
console.error('❌ /isr/commit HTTP error', commitResp.status, commitJson ?? txt);
|
||||||
|
return; // stop — don't print
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ Network/error while calling /isr/commit:', err);
|
||||||
|
return; // stop — don't print
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check API-level success flag before proceeding to print
|
||||||
|
// Adjust this line if your API uses a different success indicator.
|
||||||
|
if (!(commitJson && commitJson.success === true)) {
|
||||||
|
console.error('❌ /isr/commit failed or returned success=false:', commitJson);
|
||||||
|
// Optionally surface error message from server
|
||||||
|
if (commitJson && commitJson.message) {
|
||||||
|
console.error('Server message:', commitJson.message);
|
||||||
|
}
|
||||||
|
return; // do not proceed to printing
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reach here, commit succeeded
|
||||||
|
console.log('✅ /isr/commit success:', commitJson);
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------- BACK END ENDS HERE -----------------------------------------------------
|
||||||
// 🖨️ Send to printer API
|
// 🖨️ Send to printer API
|
||||||
const payload = {
|
const payload = {
|
||||||
type: 'ticket',
|
type: 'ticket',
|
||||||
@ -1337,7 +1544,7 @@ try {
|
|||||||
if (this.totalAmountLimitReached) return;
|
if (this.totalAmountLimitReached) return;
|
||||||
this.isBoxed = !this.isBoxed;
|
this.isBoxed = !this.isBoxed;
|
||||||
const value = parseFloat(this.padValue) || 0;
|
const value = parseFloat(this.padValue) || 0;
|
||||||
if (this.selectedLabel === 'TAN' && this.isBoxed) {
|
if (this.selectedLabel === 'TNP' && this.isBoxed) {
|
||||||
this.tanGroupStage = 0;
|
this.tanGroupStage = 0;
|
||||||
this.tanGroups = [[], [], []];
|
this.tanGroups = [[], [], []];
|
||||||
this.selectedNumbers = [];
|
this.selectedNumbers = [];
|
||||||
@ -1360,7 +1567,7 @@ try {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedLabel === 'TAN' && this.isBoxed) {
|
if (this.selectedLabel === 'TNP' && this.isBoxed) {
|
||||||
const currentNumbers = this.selectedNumbers.filter(n => typeof n === 'number') as number[];
|
const currentNumbers = this.selectedNumbers.filter(n => typeof n === 'number') as number[];
|
||||||
if (currentNumbers.length > 0) {
|
if (currentNumbers.length > 0) {
|
||||||
currentNumbers.pop();
|
currentNumbers.pop();
|
||||||
@ -1372,12 +1579,12 @@ try {
|
|||||||
if (group2.length) combined.push('-', ...group2);
|
if (group2.length) combined.push('-', ...group2);
|
||||||
if (group3.length) combined.push('-', ...group3);
|
if (group3.length) combined.push('-', ...group3);
|
||||||
this.selectedNumbers = combined;
|
this.selectedNumbers = combined;
|
||||||
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers], isBoxed: true, label: 'TAN' });
|
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers], isBoxed: true, label: 'TNP' });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedLabel === 'TAN') {
|
if (this.selectedLabel === 'TNP') {
|
||||||
const currentGroup = this.tanGroups[this.tanGroupStage];
|
const currentGroup = this.tanGroups[this.tanGroupStage];
|
||||||
if (currentGroup.length > 0) {
|
if (currentGroup.length > 0) {
|
||||||
currentGroup.pop();
|
currentGroup.pop();
|
||||||
@ -1459,7 +1666,7 @@ try {
|
|||||||
private handleFieldForSpecialLabels() {
|
private handleFieldForSpecialLabels() {
|
||||||
if (!this.selectedLabel) return;
|
if (!this.selectedLabel) return;
|
||||||
|
|
||||||
if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') {
|
if (this.selectedLabel === 'FRP' || this.selectedLabel === 'QNP') {
|
||||||
if (!this.isFirstGroupComplete) {
|
if (!this.isFirstGroupComplete) {
|
||||||
this.firstGroup = ['F'];
|
this.firstGroup = ['F'];
|
||||||
this.selectedNumbers = ['F'];
|
this.selectedNumbers = ['F'];
|
||||||
@ -1469,7 +1676,7 @@ try {
|
|||||||
this.selectedNumbers = [...this.firstGroup, '-', 'F'];
|
this.selectedNumbers = [...this.firstGroup, '-', 'F'];
|
||||||
this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
|
this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
|
||||||
}
|
}
|
||||||
} else if (this.selectedLabel === 'TAN') {
|
} else if (this.selectedLabel === 'TNP') {
|
||||||
if (this.isBoxed) {
|
if (this.isBoxed) {
|
||||||
this.selectedNumbers = ['F'];
|
this.selectedNumbers = ['F'];
|
||||||
this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: true });
|
this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: true });
|
||||||
@ -1500,13 +1707,13 @@ try {
|
|||||||
|
|
||||||
canUseField(): boolean {
|
canUseField(): boolean {
|
||||||
if (this.totalAmountLimitReached) return false;
|
if (this.totalAmountLimitReached) return false;
|
||||||
if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') {
|
if (this.selectedLabel === 'FRP' || this.selectedLabel === 'QNP') {
|
||||||
if (!this.isFirstGroupComplete && this.firstGroup.length === 0) return true;
|
if (!this.isFirstGroupComplete && this.firstGroup.length === 0) return true;
|
||||||
if (this.isFirstGroupComplete && this.secondGroup.length === 0) return true;
|
if (this.isFirstGroupComplete && this.secondGroup.length === 0) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.selectedLabel === 'TAN' || this.multiLegLabels.includes(this.selectedLabel || '')) {
|
if (this.selectedLabel === 'TNP' || this.multiLegLabels.includes(this.selectedLabel || '')) {
|
||||||
if (this.selectedLabel === 'TAN' && this.tanGroups[this.tanGroupStage].length === 0) return true;
|
if (this.selectedLabel === 'TNP' && this.tanGroups[this.tanGroupStage].length === 0) return true;
|
||||||
if (this.multiLegLabels.includes(this.selectedLabel || '') && this.multiLegGroups[this.multiLegStage].length === 0) return true;
|
if (this.multiLegLabels.includes(this.selectedLabel || '') && this.multiLegGroups[this.multiLegStage].length === 0) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1515,7 +1722,7 @@ try {
|
|||||||
|
|
||||||
openFieldModal() {
|
openFieldModal() {
|
||||||
if (this.totalAmountLimitReached) return;
|
if (this.totalAmountLimitReached) return;
|
||||||
if (['FOR', 'QUI', 'TAN'].includes(this.selectedLabel || '') || this.multiLegLabels.includes(this.selectedLabel || '')) {
|
if (['FRP', 'QNP', 'TNP'].includes(this.selectedLabel || '') || this.multiLegLabels.includes(this.selectedLabel || '')) {
|
||||||
this.handleFieldForSpecialLabels();
|
this.handleFieldForSpecialLabels();
|
||||||
} else {
|
} else {
|
||||||
this.fieldModalOpen = true;
|
this.fieldModalOpen = true;
|
||||||
@ -1545,7 +1752,7 @@ try {
|
|||||||
confirmFieldEntry() {
|
confirmFieldEntry() {
|
||||||
if (!this.fieldFEntered || !this.selectedLabel) return;
|
if (!this.fieldFEntered || !this.selectedLabel) return;
|
||||||
|
|
||||||
if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') {
|
if (this.selectedLabel === 'FRP' || this.selectedLabel === 'QNP') {
|
||||||
if (!this.isFirstGroupComplete) {
|
if (!this.isFirstGroupComplete) {
|
||||||
this.firstGroup = ['F'];
|
this.firstGroup = ['F'];
|
||||||
this.selectedNumbers = ['F'];
|
this.selectedNumbers = ['F'];
|
||||||
@ -1554,7 +1761,7 @@ try {
|
|||||||
this.selectedNumbers = [...this.firstGroup, '-', 'F'];
|
this.selectedNumbers = [...this.firstGroup, '-', 'F'];
|
||||||
}
|
}
|
||||||
this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
|
this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
|
||||||
} else if (this.selectedLabel === 'TAN') {
|
} else if (this.selectedLabel === 'TNP') {
|
||||||
if (this.isBoxed) {
|
if (this.isBoxed) {
|
||||||
this.selectedNumbers = ['F'];
|
this.selectedNumbers = ['F'];
|
||||||
this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: true });
|
this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: true });
|
||||||
@ -1579,8 +1786,8 @@ try {
|
|||||||
|
|
||||||
openPoolReplaceModal() {
|
openPoolReplaceModal() {
|
||||||
if (this.totalAmountLimitReached) return;
|
if (this.totalAmountLimitReached) return;
|
||||||
const groupA = ['WIN', 'SHP', 'THP', 'PLC', 'SHW'];
|
const groupA = ['WNP', 'SHP', 'THP', 'PLP', 'SHW'];
|
||||||
const groupB = ['FOR', 'QUI', 'TAN'];
|
const groupB = ['FRP', 'QNP', 'TNP'];
|
||||||
if (groupA.includes(this.selectedLabel || '')) this.poolReplaceOptions = groupA;
|
if (groupA.includes(this.selectedLabel || '')) this.poolReplaceOptions = groupA;
|
||||||
else if (groupB.includes(this.selectedLabel || '')) this.poolReplaceOptions = groupB;
|
else if (groupB.includes(this.selectedLabel || '')) this.poolReplaceOptions = groupB;
|
||||||
else this.poolReplaceOptions = [];
|
else this.poolReplaceOptions = [];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user