From 6d752fcfb21d8b7220e95290732075796921fb7e Mon Sep 17 00:00:00 2001 From: Sibin Sabu Date: Sun, 14 Sep 2025 13:51:18 +0530 Subject: [PATCH] feat : issue tickets to bakend from GUI --- .../app/components/navbar/navbar.component.ts | 507 +++++++++++++----- .../selection.service/selection.service.ts | 12 +- .../touch-pad-menu.component.ts | 335 +++++++++--- 3 files changed, 641 insertions(+), 213 deletions(-) diff --git a/btc-UI/src/app/components/navbar/navbar.component.ts b/btc-UI/src/app/components/navbar/navbar.component.ts index 397a6d7..7a24a88 100755 --- a/btc-UI/src/app/components/navbar/navbar.component.ts +++ b/btc-UI/src/app/components/navbar/navbar.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, HostListener, OnDestroy, NgZone } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BtcService } from '../../service/btc.service'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; import { Router } from '@angular/router'; import { catchError, interval, of, Subscription, switchMap } from 'rxjs'; import { SharedStateService } from '../../service/shared-state.service'; @@ -10,7 +11,7 @@ import { SharedStateService } from '../../service/shared-state.service'; templateUrl: './navbar.component.html', styleUrls: ['./navbar.component.css'], standalone: true, - imports: [CommonModule], + imports: [CommonModule, HttpClientModule], }) export class NavbarComponent implements OnInit, OnDestroy { dateTime: string = ''; @@ -19,32 +20,52 @@ export class NavbarComponent implements OnInit, OnDestroy { private subscription!: Subscription; userName: string = ''; 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; showRaceModal = false; selectedVenue = 'Select Venue'; selectedRace: number = 1; - currentLegRaceDisplay: string = ''; + currentLegRaceDisplay: string = ''; // Display current leg's race or pool start showWalletModal = false; showResultModal = false; showMessagesModal = false; showLogModal = false; - raceCardData: any = null; + raceCardData: any = {}; raceData: any[] = []; objectKeys = Object.keys; - selectedRaceId: number = 0; + selectedRaceId: number = 0; // index into races array enabledHorseNumbers: number[] = []; multiLegBaseRaceIdx: number = 0; 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.']; @@ -52,28 +73,83 @@ export class NavbarComponent implements OnInit, OnDestroy { private btcService: BtcService, private router: Router, private sharedStateService: SharedStateService, - private zone: NgZone + private zone: NgZone, + private http: HttpClient ) {} ngOnInit() { this.userName = localStorage.getItem('userName') || ''; this.btid = localStorage.getItem('btid'); + // Use NgZone to run setInterval outside Angular's change detection this.zone.runOutsideAngular(() => { - setInterval(() => { this.zone.run(() => this.updateDateTime()); }, 1000); + 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) .pipe( - switchMap(() => - this.btcService.pingLiveStatus().pipe( - catchError((error) => { console.error('[LIVE STATUS] Ping failed:', error); this.liveStatusOk = false; return of(null); }) - )) - ).subscribe((response) => { - if (response !== null) { this.liveStatusOk = true; console.log('[LIVE STATUS] OK'); } + switchMap(() => { + console.log(`[ANGULAR] Fetching latest ABS status at ${new Date().toISOString()}`); + return this.http.get('http://localhost:8080/abs/latest').pipe( + catchError((err) => { + console.error('[ANGULAR] ABS latest fetch failed ❌', err); + this.liveStatusOk = false; + return of(null); + }) + ); + }) + ) + .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') { this.selectedRace = data.value; if (this.currentPool) { @@ -87,11 +163,11 @@ export class NavbarComponent implements OnInit, OnDestroy { } if (data.type === 'multiLegPoolStart') { const { label, baseRaceIdx } = data.value; - this.currentPool = this.normalizePoolName(label); + this.currentPool = label; this.multiLegBaseRaceIdx = baseRaceIdx; - this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${this.currentPool}`; + this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${label}`; 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') { this.currentPool = null; @@ -107,115 +183,135 @@ export class NavbarComponent implements OnInit, OnDestroy { 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 { - if (!this.raceCardData) return 0; - const normalized = this.normalizePoolName(poolName) || poolName; - const poolMap: { [key: string]: number[] } = this.raceCardData?.pools || {}; - const arr = poolMap[normalized] || poolMap[poolName] || []; - return arr.indexOf(race) >= 0 ? arr.indexOf(race) : 0; + const raceMap: { [key: string]: number[] } = { + mjp1: [1, 2, 3, 4], + jkp1: [3, 4, 5, 6, 7], + trb1: [2, 3, 4], + trb2: [5, 6, 7], + }; + return raceMap[poolName]?.indexOf(race) ?? 0; } 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 poolKey = this.currentPool || ''; - const key = `${poolKey}-${baseRaceIdx}-${legCount}`; - if (this.prevEnabledKey === key) return; + const key = `${this.currentPool}-${baseRaceIdx}-${legCount}`; + + if (this.prevEnabledKey === key) return; // memoization this.prevEnabledKey = key; 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) { - const race = racesArr[raceIdx] || {}; - if (Array.isArray(race.horses)) { - const horses = race.horses.filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30); - combinedHorseNumbers.push(...horses); + const rawRace = racesArr[raceIdx] ?? []; + let runners: any[] = []; + + 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.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); } private getLegCountForLabel(): number { - const p = (this.currentPool || '').toLowerCase(); - switch (p) { - case 'mjp1': case 'mjp2': return 4; - case 'jkp1': case 'jkp2': return 5; - case 'trb1': case 'trb2': return 3; - default: return 3; + switch (this.currentPool) { + case 'mjp1': + return 4; + case 'jkp1': + return 5; + case 'trb1': + case 'trb2': + return 3; + default: + return 3; } } private getRaceForLeg(poolName: string, leg: number): number { - const normalized = this.normalizePoolName(poolName) || poolName; - const poolMap: { [key: string]: number[] } = this.raceCardData?.pools || {}; - const arr = poolMap[normalized] || poolMap[poolName] || []; - if (arr.length > leg) return arr[leg]; - return (this.multiLegBaseRaceIdx || 1) + leg; + const raceMap: { [key: string]: number[] } = { + mjp1: [1, 2, 3, 4], + jkp1: [3, 4, 5, 6, 7], + trb1: [2, 3, 4], + trb2: [5, 6, 7], + }; + return raceMap[poolName]?.[leg] ?? this.multiLegBaseRaceIdx + leg; } - updateDateTime() { this.dateTime = new Date().toLocaleString(); } + updateDateTime() { + const now = new Date(); + this.dateTime = now.toLocaleString(); + } @HostListener('window:resize', ['$event']) onResize(event: any) { this.screenWidth = event.target.innerWidth; - if (this.screenWidth > 800) this.isMenuOpen = false; + 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() { + console.log('[MODAL] Opening race modal'); 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() { @@ -229,9 +325,17 @@ export class NavbarComponent implements OnInit, OnDestroy { } 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.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(); } @@ -240,84 +344,196 @@ export class NavbarComponent implements OnInit, OnDestroy { this.currentPool = null; this.multiLegBaseRaceIdx = 0; this.currentLegRaceDisplay = ''; - this.sharedStateService.updateSharedData({ type: 'selectedRace', value: this.selectedRace }); - const raceData = this.raceCardData?.raceVenueRaces?.races?.[race - 1] || {}; - const runnerCount = Array.isArray(raceData.horses) ? raceData.horses.length : (raceData.runners?.length || 12); + this.sharedStateService.updateSharedData({ + 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.updateEnabledHorseNumbers(); + + console.log('[RACE] Race selected (structured):', this.selectedRace, '| Runner count:', runnerCount); this.closeModals(); } updateEnabledHorseNumbers() { const raceIndex = this.selectedRace - 1; - const race = this.raceCardData?.raceVenueRaces?.races?.[raceIndex] || {}; - let horses: any[] = []; + const racesArr = this.raceCardData?.raceVenueRaces?.races ?? []; + const rawRace = racesArr?.[raceIndex]; - if (Array.isArray(race?.horses)) horses = race.horses; - else if (Array.isArray(race?.runners)) horses = race.runners.map((r: any) => r.number ?? r); - else if (Array.isArray(race)) horses = race; + let runners: any[] = []; - this.enabledHorseNumbers = horses.map((n: any) => Number(n)).filter((n: number) => !Number.isNaN(n) && n >= 1 && n <= 30); - this.sharedStateService.updateSharedData({ type: 'enabledHorseNumbers', value: this.enabledHorseNumbers }); - console.log('[HORSE NUMBERS] Enabled horse numbers:', this.enabledHorseNumbers); + if (Array.isArray(rawRace)) { + runners = rawRace; + } else if (rawRace && Array.isArray(rawRace.runners)) { + runners = rawRace.runners; + } else { + runners = []; + } + + if (Array.isArray(runners)) { + this.enabledHorseNumbers = runners + .map((r: any) => r?.horseNumber ?? r?.number ?? r?.horse_no) + .filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30); + } else { + this.enabledHorseNumbers = []; + } + + this.sharedStateService.updateSharedData({ + type: 'enabledHorseNumbers', + value: this.enabledHorseNumbers, + }); + + 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; } - openResultModal() { this.showResultModal = true; } - openMessagesModal() { this.showMessagesModal = true; } - openLogModal() { this.showLogModal = true; } + openWalletModal() { + console.log('[MODAL] Opening wallet modal'); + this.showWalletModal = 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() { 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) { - const parts = rawLabel.split(/\s+/); - pool = parts[0]; - const countMatch = rawLabel.match(/\*(\d+)(?:\s+(Rs\s*\d+))?/); - if (countMatch) { ticketCountLabel = `*${countMatch[1]}`; price = countMatch[2] || ''; } - const horsesPartMatch = rawLabel.match(/^\w+\s+(.+?)\s+\*\d+/); - if (horsesPartMatch) { - horses = horsesPartMatch[1].trim(); - if (['MJP', 'JKP', 'TRE', 'trb1', 'trb2', 'mjp1', 'jkp1'].includes(pool)) { - horsesArray = horses.split('/').map((r: string) => r.trim().split(',').map(h => h.trim())); - } else { - horsesArray = [horses.split(',').map(h => h.trim())]; + if (storedTickets) { + const tickets = JSON.parse(storedTickets); + + this.formattedTicketLogs = tickets.map((ticket: any, index: number) => { + const rawLabel = ticket.winLabels?.trim() || ''; + const numbers = ticket.numbers || []; + const count = ticket.ticketCount || 0; + const amount = ticket.totalAmount || 0; + const barcodeId = ticket.barcodeId || ''; + let pool = '', + horses = '', + horsesArray: string[][] = [], + ticketCountLabel = '', + price = '', + maskedBarcode = '', + displayBarcode = ''; + + if (rawLabel) { + // 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) { - const last4 = barcodeId.slice(-4); - maskedBarcode = btoa(barcodeId.slice(0, -4)); - displayBarcode = '********' + last4; - } + if (barcodeId) { + const last4 = barcodeId.slice(-4); + const encryptedPart = btoa(barcodeId.slice(0, -4)); + maskedBarcode = encryptedPart; + displayBarcode = '********' + last4; + } - return { pool, horses, horsesArray, ticketCountLabel, price, numbers: 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; + console.log('Log modal opened. Final formattedTicketLogs:', this.formattedTicketLogs); } logout(): void { const name = localStorage.getItem('userName') || 'Unknown User'; 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', { - method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(printData), + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(printData), }) .then((res) => { if (!res.ok) throw new Error('Logout print failed'); @@ -335,8 +551,13 @@ export class NavbarComponent implements OnInit, OnDestroy { } 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; + } } diff --git a/btc-UI/src/app/components/selection.service/selection.service.ts b/btc-UI/src/app/components/selection.service/selection.service.ts index c945b8f..2cbe380 100644 --- a/btc-UI/src/app/components/selection.service/selection.service.ts +++ b/btc-UI/src/app/components/selection.service/selection.service.ts @@ -59,10 +59,10 @@ export class SelectionService { if ((numbers.length > 0 || numbers.includes('F')) && value > 0 && label) { switch (label) { - case 'WIN': + case 'WNP': case 'SHP': case 'THP': - case 'PLC': + case 'PLP': case 'SHW': case 'WSP': { updated.total = numbers.includes('F') @@ -71,7 +71,7 @@ export class SelectionService { break; } - case 'FOR': { + case 'FRP': { const dashIndex = numbers.indexOf('-'); const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex); const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1); @@ -106,7 +106,7 @@ export class SelectionService { break; } - case 'QUI': { + case 'QNP': { const dashIndex = numbers.indexOf('-'); const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex); const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1); @@ -149,7 +149,7 @@ export class SelectionService { break; } - case 'TAN': { + case 'TNP': { const dashIndices = numbers .map((n, idx) => (n === '-' ? idx : -1)) .filter(idx => idx !== -1); @@ -347,7 +347,7 @@ export class SelectionService { if (!base.numbers.length || base.value <= 0) return []; - return ['WIN', 'SHP', 'PLC'].map(label => { + return ['WNP', 'SHP', 'PLP'].map(label => { const newRow: SelectionData = { ...base, label, diff --git a/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts b/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts index 7a105cf..f270bae 100755 --- a/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts +++ b/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts @@ -24,14 +24,14 @@ import _ from 'lodash'; export class TouchPadMenuComponent implements OnInit, OnDestroy { @Input() ticketingActive: boolean = false; - public twoGroupLabels = ['FOR', 'QUI']; + public twoGroupLabels = ['FRP', 'QNP']; public multiLegLabels = ['TRE', 'MJP', 'JKP']; - public threeGroupLabels = ['TAN']; - public allowedFieldLabels = ['WIN', 'SHP', 'THP', 'PLC', 'SHW']; + public threeGroupLabels = ['TNP']; + public allowedFieldLabels = ['WNP', 'SHP', 'THP', 'PLP', 'SHW']; labels: string[] = [ - 'WIN', 'SHP', 'THP', 'PLC', 'SHW', 'FOR', - 'QUI', 'TAN', 'EXA', 'WSP', 'TRE', 'MJP', + 'WNP', 'SHP', 'THP', 'PLP', 'SHW', 'FRP', + 'QNP', 'TNP', 'EXA', 'WSP', 'TRE', 'MJP', 'JKP', 'SJP', '.' ]; @@ -53,11 +53,11 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { showLimitPopup: boolean = false; disabledLabels: string[] = ['SHW', 'SJP', '.']; - // TAN logic + // TNP logic tanGroupStage = 0; tanGroups: (number | string)[][] = [[], [], []]; - // FOR/QUI logic + // FRP/QNP logic isFirstGroupComplete = false; firstGroup: (number | string)[] = []; secondGroup: (number | string)[] = []; @@ -257,10 +257,10 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { get showShashEnter(): boolean { const label = this.selectedLabel || ''; - if (['FOR', 'QUI', 'TAN'].includes(label) && this.isBoxed) { + if (['FRP', 'QNP', 'TNP'].includes(label) && this.isBoxed) { 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)) { const maxLegs = this.getMaxLegs(this.currentPool || ''); // Hide Shash Enter if on the final leg @@ -270,7 +270,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { } get isShashEnterDisabled(): boolean { - if (this.selectedLabel === 'TAN') { + if (this.selectedLabel === 'TNP') { if (this.isBoxed) { return true; } @@ -292,15 +292,15 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { get isBoxToggleDisabled(): boolean { 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)) { return true; } if (this.selectedNumbers.includes('F')) { return true; } - // Disable Box toggle for TAN, QUI, FOR after any number is selected - if (['TAN', 'QUI', 'FOR'].includes(this.selectedLabel) && this.selectedNumbers.length > 0) { + // Disable Box toggle for TNP, QNP, FRP after any number is selected + if (['TNP', 'QNP', 'FRP'].includes(this.selectedLabel) && this.selectedNumbers.length > 0) { return true; } return false; @@ -308,8 +308,8 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { get isWSPSelection(): boolean { const currentRow = this.selectionService.getCurrentRow(); - return ['WIN', 'SHP', 'PLC'].includes(currentRow.label) || - this.selectionService.getSelections().some(sel => ['WIN', 'SHP', 'PLC'].includes(sel.label)); + return ['WNP', 'SHP', 'PLP'].includes(currentRow.label) || + this.selectionService.getSelections().some(sel => ['WNP', 'SHP', 'PLP'].includes(sel.label)); } private chunk(array: T[], size: number): T[][] { @@ -330,19 +330,19 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { if (!this.actualRunners.has(number)) return true; // Disable if total amount limit reached if (this.totalAmountLimitReached) return true; - // Allow all numbers for TAN when boxed, but disable selected numbers - if (this.selectedLabel === 'TAN' && this.isBoxed) { + // Allow all numbers for TNP when boxed, but disable selected numbers + if (this.selectedLabel === 'TNP' && this.isBoxed) { return this.selectedNumbers.includes(number); } - // TAN (unboxed): Disable numbers already selected in the current group - if (this.selectedLabel === 'TAN') { + // TNP (unboxed): Disable numbers already selected in the current group + if (this.selectedLabel === 'TNP') { return this.tanGroups[this.tanGroupStage].includes(number); } // Multi-leg pools (TRE, MJP, JKP): Disable numbers already selected in the current leg if (this.multiLegLabels.includes(this.selectedLabel || '')) { 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.isFirstGroupComplete) { return this.firstGroup.includes(number); @@ -350,7 +350,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { 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); } @@ -402,7 +402,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { //----------------------------------ADDED THIS ----------------------------------------------------- if (label === 'WSP') { - const wspLabels = ['WIN', 'SHP', 'PLC']; + const wspLabels = ['WNP', 'SHP', 'PLP']; this.wspTicketStage = 0; this.selectionService.finalizeCurrentRow(); const currentSelections = this.selectionService.getSelections(); @@ -429,7 +429,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { // Reset group states this.tanGroupStage = 0; this.tanGroups = [[], [], []]; - // Reset FOR/QUI + // Reset FRP/QNP this.isFirstGroupComplete = false; this.firstGroup = []; this.secondGroup = []; @@ -466,8 +466,8 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { selectNumber(number: number) { if (!this.selectedLabel || this.totalAmountLimitReached || !this.actualRunners.has(number)) return; - // TAN boxed - if (this.selectedLabel === 'TAN' && this.isBoxed) { + // TNP boxed + if (this.selectedLabel === 'TNP' && this.isBoxed) { if (!this.selectedNumbers.includes(number)) { const currentNumbers = this.selectedNumbers.filter(n => typeof n === 'number') as number[]; const allBoxed = [...currentNumbers, number]; @@ -482,14 +482,14 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { this.selectionService.updatePartial({ numbers: [...this.selectedNumbers], isBoxed: true, - label: 'TAN' + label: 'TNP' }); } return; } - // TAN unboxed - if (this.selectedLabel === 'TAN') { + // TNP unboxed + if (this.selectedLabel === 'TNP') { if (!this.tanGroups[this.tanGroupStage].includes(number)) { this.tanGroups[this.tanGroupStage].push(number); 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 || '')) { console.log('Selected label:', this.selectedLabel); console.log('Current number clicked:', number); @@ -548,10 +548,10 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { if (!this.selectedNumbers.includes(number)) { this.selectedNumbers.push(number); 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') { - const labelsToUpdate = ['WIN', 'SHP', 'PLC']; + const labelsToUpdate = ['WNP', 'SHP', 'PLP']; const selections = this.selectionService.getSelections(); const updated = selections.map(sel => { if (labelsToUpdate.includes(sel.label)) { @@ -583,10 +583,10 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { createVirtualRowsFromWSP(): SelectionData[] { 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 ['WIN', 'SHP', 'PLC'].map(label => { + return ['WNP', 'SHP', 'PLP'].map(label => { const newRow: SelectionData = { ...base, label, @@ -602,7 +602,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { return 0; } 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); } else if (['TRE', 'JKP', 'MJP'].includes(row.label)) { const legs = row.numbers.join('').split('/').map(leg => leg.split(',').length); @@ -617,7 +617,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { clearWSPSelection() { if (this.selectedLabel !== 'WSP') return; - const labels = ['WIN', 'SHP', 'PLC']; + const labels = ['WNP', 'SHP', 'PLP']; const targetLabel = labels[this.wspTicketStage]; // Update only the current WSP stage's value to 0 const updatedSelections = this.selectionService.getSelections().map(sel => { @@ -641,7 +641,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { const value = parseFloat(this.padValue) || 0; if (this.selectedLabel === 'WSP') { - const labels = ['WIN', 'SHP', 'PLC']; + const labels = ['WNP', 'SHP', 'PLP']; const targetLabel = labels[this.wspTicketStage]; const selections = this.selectionService.getSelections(); // Find the current WSP row to ensure numbers are synchronized @@ -660,7 +660,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { }); 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) { this.wspTicketStage++; @@ -678,9 +678,9 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { } 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) { this.tanGroupStage++; const combined: (number | string)[] = [...this.tanGroups[0]]; @@ -746,7 +746,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { const value = parseFloat(this.padValue) || 0; if (this.selectedLabel === 'WSP') { - const labels = ['WIN', 'SHP', 'PLC']; + const labels = ['WNP', 'SHP', 'PLP']; const targetLabel = labels[this.wspTicketStage]; const updatedSelections = this.selectionService.getSelections().map(sel => { 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 currentRow = this.selectionService.getCurrentRow(); if (this.selectedLabel === 'WSP') { - // For WSP, require all three rows (WIN, SHP, PLC) to have valid numbers and values >= 1 - const wspLabels = ['WIN', 'SHP', 'PLC']; + // For WSP, require all three rows (WNP, SHP, PLP) to have valid numbers and values >= 1 + const wspLabels = ['WNP', 'SHP', 'PLP']; const wspSelections = selections.filter(sel => wspLabels.includes(sel.label)); const allWSPRowsValid = wspSelections.length === 3 && wspSelections.every(row => 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') { const virtualRows = this.createVirtualRowsFromWSP(); 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]); } @@ -972,7 +972,7 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { //-------------------PRINT LOGIC---------------------------------------- -printTicket() { +async printTicket() { const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + sel.total, 0); if (selectionsTotal + this.currentTotal > 5000) { this.showLimitPopup = true; @@ -987,7 +987,7 @@ printTicket() { const currentRow = this.selectionService.getCurrentRow(); 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]; @@ -1003,7 +1003,7 @@ printTicket() { if (!currentRow.total) { 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); } else if (['TRE', 'JKP', 'MJP'].includes(currentRow.label)) { combinations = 1; // or your specific logic @@ -1026,7 +1026,7 @@ printTicket() { const totalAmount = allRows.reduce((sum, row) => sum + (row.total || 0), 0); const now = new Date(); - const venue = 'MYS'; + const venue = this.structuredRaceCard?.venue || ''; const day = String(now.getDate()).padStart(2, '0'); const month = String(now.getMonth() + 1).padStart(2, '0'); const year = String(now.getFullYear()).slice(-2); @@ -1156,8 +1156,8 @@ displayNumbers = displayNumbers.flatMap((n) => { let numbersStr = ''; - // 🎯 FOR, QUI, TAN logic with box check - if (['FOR', 'QUI'].includes(row.label)) { + // 🎯 FRP, QNP, TNP logic with box check + if (['FRP', 'QNP'].includes(row.label)) { // const actualNumbers = displayNumbers.filter(n => n !== '#' && n !== '-').join(','); const actualNumbers = displayNumbers.filter(n => n !== '#').join(','); if (row.numbers.includes('#') || row.isBoxed) { // ✅ box condition @@ -1179,7 +1179,7 @@ displayNumbers = displayNumbers.flatMap((n) => { return `${label}${numbers} ${value} ${total}`; }).join('\n'); -//------------------------------------WIN LABELS ENDS HERE -------------------------------- +//------------------------------------WNP LABELS ENDS HERE -------------------------------- // ✅ Print preview const printData = { @@ -1205,6 +1205,213 @@ displayNumbers = displayNumbers.flatMap((n) => { console.log(`Date/Time : ${now.toLocaleString()}`); 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 const payload = { type: 'ticket', @@ -1337,7 +1544,7 @@ try { if (this.totalAmountLimitReached) return; this.isBoxed = !this.isBoxed; const value = parseFloat(this.padValue) || 0; - if (this.selectedLabel === 'TAN' && this.isBoxed) { + if (this.selectedLabel === 'TNP' && this.isBoxed) { this.tanGroupStage = 0; this.tanGroups = [[], [], []]; this.selectedNumbers = []; @@ -1360,7 +1567,7 @@ try { return; } - if (this.selectedLabel === 'TAN' && this.isBoxed) { + if (this.selectedLabel === 'TNP' && this.isBoxed) { const currentNumbers = this.selectedNumbers.filter(n => typeof n === 'number') as number[]; if (currentNumbers.length > 0) { currentNumbers.pop(); @@ -1372,12 +1579,12 @@ try { if (group2.length) combined.push('-', ...group2); if (group3.length) combined.push('-', ...group3); this.selectedNumbers = combined; - this.selectionService.updatePartial({ numbers: [...this.selectedNumbers], isBoxed: true, label: 'TAN' }); + this.selectionService.updatePartial({ numbers: [...this.selectedNumbers], isBoxed: true, label: 'TNP' }); } return; } - if (this.selectedLabel === 'TAN') { + if (this.selectedLabel === 'TNP') { const currentGroup = this.tanGroups[this.tanGroupStage]; if (currentGroup.length > 0) { currentGroup.pop(); @@ -1459,7 +1666,7 @@ try { private handleFieldForSpecialLabels() { if (!this.selectedLabel) return; - if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') { + if (this.selectedLabel === 'FRP' || this.selectedLabel === 'QNP') { if (!this.isFirstGroupComplete) { this.firstGroup = ['F']; this.selectedNumbers = ['F']; @@ -1469,7 +1676,7 @@ try { this.selectedNumbers = [...this.firstGroup, '-', 'F']; this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false }); } - } else if (this.selectedLabel === 'TAN') { + } else if (this.selectedLabel === 'TNP') { if (this.isBoxed) { this.selectedNumbers = ['F']; this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: true }); @@ -1500,13 +1707,13 @@ try { canUseField(): boolean { 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.secondGroup.length === 0) return true; return false; } - if (this.selectedLabel === 'TAN' || this.multiLegLabels.includes(this.selectedLabel || '')) { - if (this.selectedLabel === 'TAN' && this.tanGroups[this.tanGroupStage].length === 0) return true; + if (this.selectedLabel === 'TNP' || this.multiLegLabels.includes(this.selectedLabel || '')) { + 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; return false; } @@ -1515,7 +1722,7 @@ try { openFieldModal() { 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(); } else { this.fieldModalOpen = true; @@ -1545,7 +1752,7 @@ try { confirmFieldEntry() { if (!this.fieldFEntered || !this.selectedLabel) return; - if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') { + if (this.selectedLabel === 'FRP' || this.selectedLabel === 'QNP') { if (!this.isFirstGroupComplete) { this.firstGroup = ['F']; this.selectedNumbers = ['F']; @@ -1554,7 +1761,7 @@ try { this.selectedNumbers = [...this.firstGroup, '-', 'F']; } this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false }); - } else if (this.selectedLabel === 'TAN') { + } else if (this.selectedLabel === 'TNP') { if (this.isBoxed) { this.selectedNumbers = ['F']; this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: true }); @@ -1579,8 +1786,8 @@ try { openPoolReplaceModal() { if (this.totalAmountLimitReached) return; - const groupA = ['WIN', 'SHP', 'THP', 'PLC', 'SHW']; - const groupB = ['FOR', 'QUI', 'TAN']; + const groupA = ['WNP', 'SHP', 'THP', 'PLP', 'SHW']; + const groupB = ['FRP', 'QNP', 'TNP']; if (groupA.includes(this.selectedLabel || '')) this.poolReplaceOptions = groupA; else if (groupB.includes(this.selectedLabel || '')) this.poolReplaceOptions = groupB; else this.poolReplaceOptions = [];