feat : issue tickets to bakend from GUI

This commit is contained in:
Sibin Sabu 2025-09-14 13:51:18 +05:30
parent b680150a88
commit 6d752fcfb2
3 changed files with 641 additions and 213 deletions

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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<T>(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 = [];