feat : issue tickets to bakend from GUI
This commit is contained in:
parent
b680150a88
commit
6d752fcfb2
@ -1,6 +1,7 @@
|
||||
import { Component, OnInit, HostListener, OnDestroy, NgZone } from '@angular/core';
|
||||
import { 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 = [];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user