btc_horse/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts

1373 lines
45 KiB
TypeScript
Executable File

import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Subscription } from 'rxjs';
import { SelectionService, SelectionData } from '../selection.service/selection.service';
import { SharedStateService } from '../../service/shared-state.service';
import { LabelRestrictionService } from '../selection.service/label-restriction.service';
@Component({
selector: 'app-touch-pad-menu',
standalone: true,
imports: [CommonModule],
templateUrl: './touch-pad-menu.component.html',
styleUrls: ['./touch-pad-menu.component.css']
})
export class TouchPadMenuComponent implements OnInit, OnDestroy {
@Input() ticketingActive: boolean = false;
public twoGroupLabels = ['FOR', 'QUI'];
public multiLegLabels = ['TRE', 'MJP', 'JKP'];
public threeGroupLabels = ['TAN'];
public allowedFieldLabels = ['WIN', 'SHP', 'THP', 'PLC', 'SHW'];
labels: string[] = [
'WIN', 'SHP', 'THP', 'PLC', 'SHW', 'FOR',
'QUI', 'TAN', 'EXA', 'WSP', 'TRE', 'MJP',
'JKP', 'SJP', '.'
];
numbers: number[] = Array.from({ length: 30 }, (_, i) => i + 1);
runnerCount: number = 12;
labelRowsFlat: string[] = [];
numbersFlat: number[] = [];
blockedLabels = new Set<string>();
actualRunners: Set<number> = new Set();
wspTicketStage: number = 0;
selectedLabel: string | null = null;
selectedNumbers: (number | string)[] = [];
padValue: string = '';
canPrint = false;
calculatorOpen = false;
calcDisplay = '';
maxRowsReached: boolean = false;
totalAmountLimitReached: boolean = false;
showLimitPopup: boolean = false;
disabledLabels: string[] = ['SHW', 'SJP', '.'];
// TAN logic
tanGroupStage = 0;
tanGroups: (number | string)[][] = [[], [], []];
// FOR/QUI logic
isFirstGroupComplete = false;
firstGroup: (number | string)[] = [];
secondGroup: (number | string)[] = [];
// Multi-leg logic (TRE, MJP, JKP)
multiLegStage = 0;
multiLegGroups: (number | string)[][] = [[], [], [], [], []];
multiLegBaseRaceIdx: number = 0; // Track starting race index
currentLegRaceDisplay: string = ''; // Display current leg's race
currentPool: string | null = null; // Track current pool (mjp1, jkp1, trb1, trb2)
isBoxed: boolean = false;
// FIELD modal
fieldModalOpen = false;
fieldInput: string = '';
fieldFEntered = false;
// POOL REPLACE modal
poolReplaceOpen = false;
// TRE popup
trePopupVisible = false;
private currentRowSubscription: Subscription | null = null;
private selectionsSubscription: Subscription | null = null;
private runnerCountSubscription: Subscription | null = null;
private currentTotal: number = 0;
private currentSelections: SelectionData[] = [];
constructor(
private selectionService: SelectionService,
private sharedStateService: SharedStateService,
private labelRestrictionService: LabelRestrictionService
) {}
selectedRaceNumber: string = '1'; // Default
ngOnInit() {
this.runnerCountSubscription = this.sharedStateService.runnerCount$.subscribe(count => {
this.runnerCount = count || 12;
this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
this.numbersFlat = this.numberRows.flat();
this.updateLegRaceDisplay(this.currentPool || '');
// --- NEW: Update actualRunners when runner count changes ---
this.setActualRunners();
});
this.labelRowsFlat = this.labelRows.flat();
this.selectionsSubscription = this.selectionService.selections$.subscribe(selections => {
this.currentSelections = selections;
this.maxRowsReached = selections.length >= 5;
const totalAmount = selections.reduce((sum, selection) => sum + selection.total, 0);
this.totalAmountLimitReached = totalAmount >= 5000;
this.blockedLabels = this.labelRestrictionService.getBlockedLabels(selections);
if (!this.totalAmountLimitReached) {
this.showLimitPopup = false;
}
});
this.currentRowSubscription = this.selectionService.currentRow$.subscribe(row => {
this.currentTotal = row.total;
});
// --- NEW: Subscribe to race changes ---
// this.sharedStateService.selectedRace$.subscribe(() => {
// this.setActualRunners();
// // If currently in a multi-leg pool, update the numbers for the active leg
// if (this.currentPool && this.multiLegLabels.includes(this.selectedLabel || '')) {
// this.updateLegRaceDisplay(this.currentPool);
// const runnerCount = this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, this.multiLegStage);
// this.runnerCount = runnerCount || 12;
// this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
// this.numbersFlat = this.numberRows.flat();
// this.actualRunners = this.getActualRunnersForCurrentPoolLeg();
// } else {
// this.setActualRunners();
// }
// });
this.sharedStateService.selectedRace$.subscribe(race => {
this.selectedRaceNumber = String(race || '1');
this.setActualRunners();
if (this.currentPool && this.multiLegLabels.includes(this.selectedLabel || '')) {
this.updateLegRaceDisplay(this.currentPool);
const runnerCount = this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, this.multiLegStage);
this.runnerCount = runnerCount || 12;
this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
this.numbersFlat = this.numberRows.flat();
this.actualRunners = this.getActualRunnersForCurrentPoolLeg();
} else {
this.setActualRunners();
}
});
}
ngOnDestroy() {
this.currentRowSubscription?.unsubscribe();
this.selectionsSubscription?.unsubscribe();
this.runnerCountSubscription?.unsubscribe();
}
// --- NEW HELPER METHOD ---
getActualRunnersForCurrentPoolLeg(): Set<number> {
const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
const raceIdx = this.getRaceForLeg(this.currentPool!, this.multiLegStage) - 1;
const race = raceCardData?.raceVenueRaces?.races?.[raceIdx];
if (race?.runners && Array.isArray(race.runners)) {
return new Set(race.runners.map((r: any) => Number(r.number)));
}
if (Array.isArray(race)) {
return new Set(race.map((r: any) => Number(r.number || r)));
}
return new Set();
}
// --- MODIFIED METHOD ---
setActualRunners() {
if (this.currentPool && this.multiLegLabels.includes(this.selectedLabel || '')) {
this.actualRunners = this.getActualRunnersForCurrentPoolLeg();
} else {
this.actualRunners = this.getActualRunnersForCurrentRace();
}
}
// --- NEW METHOD ---
getActualRunnersForCurrentRace(): Set<number> {
const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
const selectedRaceIdx = this.sharedStateService.getSelectedRace() - 1;
const race = raceCardData?.raceVenueRaces?.races?.[selectedRaceIdx];
if (race?.runners && Array.isArray(race.runners)) {
return new Set(race.runners.map((r: any) => Number(r.number)));
}
if (Array.isArray(race)) {
return new Set(race.map((r: any) => Number(r.number || r)));
}
return new Set();
}
get labelRows() {
return this.chunk(this.labels, 3);
}
get numberRows() {
return this.chunk(this.numbers, 6);
}
get numericPadEnabled() {
return this.selectedLabel !== null && (this.selectedNumbers.length > 0 || this.selectedNumbers.includes('F'));
}
get showShashEnter(): boolean {
const label = this.selectedLabel || '';
if (['FOR', 'QUI', 'TAN'].includes(label) && this.isBoxed) {
return false;
}
const specialLabels = ['FOR', 'QUI', 'TAN', 'EXA', 'WSP', 'TRE', 'MJP', 'JKP', '.'];
return specialLabels.includes(label);
}
get isShashEnterDisabled(): boolean {
if (this.selectedLabel === 'TAN') {
if (this.isBoxed) {
return true;
}
return this.tanGroupStage >= 2 || this.tanGroups[this.tanGroupStage].length === 0;
} else if (this.multiLegLabels.includes(this.selectedLabel || '')) {
const maxLegs = this.getMaxLegs(this.currentPool || '');
return this.multiLegStage >= maxLegs || this.multiLegGroups[this.multiLegStage].length === 0;
} else if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
return this.isFirstGroupComplete || this.firstGroup.length === 0;
}
return false;
}
get showBackspace(): boolean {
return this.selectedLabel !== null &&
(this.selectedNumbers.length > 0 || this.selectedNumbers.includes('F')) &&
this.padValue.length === 0;
}
get isBoxToggleDisabled(): boolean {
if (!this.selectedLabel) return true;
const disallowedBoxLabels = ['WIN', 'SHP', 'THP', 'PLC', 'SHW', 'TRE', 'MJP', 'JKP'];
if (disallowedBoxLabels.includes(this.selectedLabel)) {
return true;
}
if (this.selectedNumbers.includes('F')) {
return true;
}
return false;
}
private chunk<T>(array: T[], size: number): T[][] {
return Array.from({ length: Math.ceil(array.length / size) }, (_, i) =>
array.slice(i * size, i * size + size)
);
}
isLabelDisabled(label: string): boolean {
return this.disabledLabels.includes(label) ||
this.totalAmountLimitReached ||
this.blockedLabels.has(label);
}
// --- MODIFIED METHOD ---
isNumberDisabled(number: number): boolean {
// Disable if number is not in actualRunners
if (!this.actualRunners.has(number)) {
return true;
}
// Allow all numbers for TAN when boxed
if (this.selectedLabel === 'TAN' && this.isBoxed) {
return false;
}
// Allow selection for TAN, multi-leg, or two-group labels
if (
this.selectedLabel === 'TAN' ||
this.multiLegLabels.includes(this.selectedLabel || '') ||
this.twoGroupLabels.includes(this.selectedLabel || '')
) {
return false;
}
// Disable if number is already selected or total amount limit reached
return this.selectedNumbers.includes(number) || this.totalAmountLimitReached;
}
selectLabel(label: string) {
if (this.totalAmountLimitReached || this.blockedLabels.has(label)) {
this.showLimitPopup = true;
return;
}
if (label === 'TRE') {
this.trePopupVisible = true;
return;
}
this.selectedLabel = label;
this.selectedNumbers = [];
this.padValue = '';
this.canPrint = false;
this.isBoxed = false;
// Store base race index and pool name for multi-leg pools
if (this.multiLegLabels.includes(label)) {
const poolName = label === 'MJP' ? 'mjp1' : label === 'JKP' ? 'jkp1' : label === 'TRE' ? 'trb1' : label;
this.currentPool = poolName;
this.multiLegBaseRaceIdx = this.getBaseRaceIndexForPool(poolName);
// Broadcast race and pool info for navbar
this.sharedStateService.updateSharedData({
type: 'multiLegPoolStart',
value: { label: poolName, baseRaceIdx: this.multiLegBaseRaceIdx }
});
this.updateLegRaceDisplay(poolName);
// --- NEW: Update runners and number pad immediately ---
this.actualRunners = this.getActualRunnersForCurrentPoolLeg();
this.runnerCount = this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, 0) || 12;
this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
this.numbersFlat = this.numberRows.flat();
} else {
this.currentPool = null;
this.multiLegBaseRaceIdx = 0;
this.currentLegRaceDisplay = '';
this.sharedStateService.updateSharedData({
type: 'multiLegPoolEnd',
value: null
});
// --- NEW: Update actualRunners for single race ---
this.setActualRunners();
}
//----------------------------------ADDED THIS -----------------------------------------------------
if (label === 'WSP') {
const wspLabels = ['WIN', 'SHP', 'PLC'];
this.wspTicketStage = 0;
this.selectionService.finalizeCurrentRow();
const currentSelections = this.selectionService.getSelections();
const blankRows = wspLabels.map(lbl => ({
label: lbl,
numbers: [],
value: 0,
total: 0,
isBoxed: false
}));
const totalNew = 0; // Each row is empty initially
const totalExisting = currentSelections.reduce((sum, r) => sum + r.total, 0);
if (totalExisting + totalNew <= 5000) {
this.selectionService.setSelections([...currentSelections, ...blankRows]);
}
// Clear the input area (so no editing conflicts)
this.selectionService.updatePartial({ label: '', numbers: [], value: 0, total: 0 });
return; // Skip default updatePartial below
}
//----------------------------------ended here----------------------------------------------------
// Reset TAN
this.tanGroupStage = 0;
this.tanGroups = [[], [], []];
// Reset FOR/QUI
this.isFirstGroupComplete = false;
this.firstGroup = [];
this.secondGroup = [];
// Reset Multi-leg
this.multiLegStage = 0;
this.multiLegGroups = [[], [], [], [], []];
this.selectionService.updatePartial({ label });
}
private getBaseRaceIndexForPool(poolName: string): number {
const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
const totalRaces = raceCardData?.raceVenueRaces?.races?.length || 10;
const maxLegs = this.getMaxLegs(poolName);
// Try to get pool-to-race mapping from raceCardData
const poolRaces = raceCardData?.raceVenueRaces?.pools?.[poolName] || [];
let baseRaceIdx = poolRaces.length > 0 ? poolRaces[0] : this.getDefaultBaseRace(poolName);
// Ensure enough races remain for the pool
if (baseRaceIdx + maxLegs - 1 > totalRaces) {
baseRaceIdx = Math.max(1, totalRaces - maxLegs + 1);
}
return baseRaceIdx;
}
private getDefaultBaseRace(poolName: string): number {
// Fallback to hardcoded values if raceCardData.pools is unavailable
const poolRaceMap: { [key: string]: number } = {
'mjp1': 1,
'jkp1': 3,
'trb1': 2,
'trb2': 5
};
return poolRaceMap[poolName] || this.sharedStateService.getSelectedRace();
}
selectNumber(number: number) {
if (!this.selectedLabel || this.totalAmountLimitReached || !this.actualRunners.has(number)) return;
// TAN Box mode
if (this.selectedLabel === 'TAN' && this.isBoxed) {
if (!this.selectedNumbers.includes(number)) {
const currentNumbers = this.selectedNumbers.filter(n => typeof n === 'number') as number[];
const allBoxed = [...currentNumbers, number];
const groupSize = Math.ceil(allBoxed.length / 3);
const group1 = allBoxed.slice(0, groupSize);
const group2 = allBoxed.slice(group1.length, group1.length + groupSize);
const group3 = allBoxed.slice(group1.length + group2.length);
const combined: (number | string)[] = [...group1];
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'
});
}
return;
}
// TAN unboxed
if (this.selectedLabel === 'TAN') {
if (!this.tanGroups[this.tanGroupStage].includes(number)) {
this.tanGroups[this.tanGroupStage].push(number);
const combined: (number | string)[] = [...this.tanGroups[0]];
if (this.tanGroupStage > 0) combined.push('-', ...this.tanGroups[1]);
if (this.tanGroupStage > 1) combined.push('-', ...this.tanGroups[2]);
this.selectedNumbers = combined;
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
}
return;
}
// Multi-leg logic (TRE, MJP, JKP)
if (this.multiLegLabels.includes(this.selectedLabel)) {
if (!this.multiLegGroups[this.multiLegStage].includes(number)) {
this.multiLegGroups[this.multiLegStage].push(number);
this.updateMultiLegSelection();
}
return;
}
// FOR/QUI logic
if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
if (!this.isFirstGroupComplete) {
if (!this.firstGroup.includes(number)) {
this.firstGroup.push(number);
this.selectedNumbers = [...this.firstGroup];
}
} else {
if (!this.secondGroup.includes(number)) {
this.secondGroup.push(number);
this.selectedNumbers = [...this.firstGroup, '-', ...this.secondGroup];
}
}
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
return;
}
// Default single-number selection (WIN, SHP, THP, etc.)
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
if (this.selectedLabel === 'WSP') {
const labelsToUpdate = ['WIN', 'SHP', 'PLC'];
const selections = this.selectionService.getSelections();
const updated = selections.map(sel => {
if (labelsToUpdate.includes(sel.label)) {
const newNumbers = [...sel.numbers];
if (!newNumbers.includes(number)) {
newNumbers.push(number);
}
return { ...sel, numbers: newNumbers };
}
return sel;
});
this.selectionService.setSelections(updated);
}
}
}
private updateMultiLegSelection() {
const combined: (number | string)[] = [];
for (let i = 0; i <= this.multiLegStage; i++) {
if (i > 0) combined.push('/');
combined.push(...this.multiLegGroups[i]);
}
this.selectedNumbers = combined;
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
this.updateLegRaceDisplay(this.currentPool || '');
}
private calculateMultiLegAmount(poolType: 'TRE' | 'MJP' | 'JKP', horsesPerLeg: number[], units: number, unitBet: number = 10): number {
return horsesPerLeg.reduce((acc, v) => acc * v, 1) * units * unitBet;
}
//-------------------------ON PAD ENTER------------------------------------//
// onPadEnter() {
// if (this.canPrint) {
// this.print();
// }
// }
onPadEnter() {
if (!this.canPrint) {
this.print();
}
const value = parseFloat(this.padValue) || 0;
if (this.selectedLabel === 'WSP') {
const labels = ['WIN', 'SHP', 'PLC'];
const targetLabel = labels[this.wspTicketStage];
const updatedSelections = this.selectionService.getSelections().map(sel => {
if (sel.label === targetLabel && JSON.stringify(sel.numbers) === JSON.stringify(this.selectedNumbers)) {
return {
...sel,
value,
total: value * (sel.numbers?.length || 0) * 10
};
}
return sel;
});
this.selectionService.setSelections(updatedSelections);
// Move to next stage
this.wspTicketStage = (this.wspTicketStage + 1) % 3;
this.padValue = '';
this.updateCanPrint();
return;
}
// ✅ Default path: finalize row and reset input
this.print();
}
//--------------------------------------------------------------------------//
onShashEnter() {
if (this.selectedLabel === 'TAN' && this.isBoxed) {
return;
}
if (this.selectedLabel === 'TAN') {
if (this.tanGroupStage < 2 && this.tanGroups[this.tanGroupStage].length > 0) {
this.tanGroupStage++;
const combined: (number | string)[] = [...this.tanGroups[0]];
if (this.tanGroupStage > 0) combined.push('-', ...this.tanGroups[1]);
if (this.tanGroupStage > 1) combined.push('-', ...this.tanGroups[2]);
this.selectedNumbers = combined;
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
}
return;
}
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
const maxLegs = this.getMaxLegs(this.currentPool || '');
if (this.multiLegStage < maxLegs - 1 && this.multiLegGroups[this.multiLegStage].length > 0) {
this.multiLegStage++;
this.updateMultiLegSelection();
this.updateLegRaceDisplay(this.currentPool || '');
// --- NEW: Update actualRunners for the new leg ---
this.setActualRunners();
}
return;
}
if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
if (!this.isFirstGroupComplete && this.firstGroup.length > 0) {
this.isFirstGroupComplete = true;
this.secondGroup = [];
this.selectedNumbers = [...this.firstGroup, '-'];
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
}
}
}
//-----------------------------ENTER PAD VALUE-------------------------------------
// enterPadVal(key: string) {
// if (!this.numericPadEnabled || this.totalAmountLimitReached) return;
// if (key === 'X') {
// this.padValue = '';
// } else if (/[0-9]/.test(key)) {
// const currentValue = parseInt(this.padValue + key) || 0;
// if (currentValue > 100) return;
// this.padValue += key;
// }
// this.updateCanPrint();
// const value = parseFloat(this.padValue) || 0;
// this.selectionService.updatePartial({
// value,
// isBoxed: this.isBoxed,
// label: this.selectedLabel || '',
// numbers: [...this.selectedNumbers]
// });
// }
enterPadVal(key: string) {
if (!this.numericPadEnabled || this.totalAmountLimitReached) return;
if (key === 'X') {
this.padValue = '';
if (this.selectedLabel === 'WSP') {
this.wspTicketStage = 0; // Reset stage if WSP
}
else if (/[0-9]/.test(key)) {
const currentValue = parseInt(this.padValue + key) || 0;
if (currentValue > 100) return;
this.padValue += key;
}
}
if (/[0-9]/.test(key)) {
const currentValue = parseInt(this.padValue + key) || 0;
if (currentValue > 100) return;
this.padValue += key;
}
this.updateCanPrint();
const value = parseFloat(this.padValue) || 0;
if (this.selectedLabel === 'WSP') {
const labels = ['WIN', 'SHP', 'PLC'];
const targetLabel = labels[this.wspTicketStage];
const updatedSelections = this.selectionService.getSelections().map(sel => {
if (sel.label === targetLabel && JSON.stringify(sel.numbers) === JSON.stringify(this.selectedNumbers)) {
const total = value * (sel.numbers?.length || 0) * 10;
return {
...sel,
value,
total
};
}
return sel;
});
this.selectionService.setSelections(updatedSelections);
return;
}
// 🔵 Default path for non-WSP
this.selectionService.updatePartial({
value,
isBoxed: this.isBoxed,
label: this.selectedLabel || '',
numbers: [...this.selectedNumbers]
});
}
//---------------------------------------------------------------------------------------------
updateCanPrint() {
this.canPrint = this.padValue.trim().length > 0 && /^[0-9]+$/.test(this.padValue);
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
const maxLegs = this.getMaxLegs(this.currentPool || '');
this.canPrint = this.canPrint && this.multiLegStage === maxLegs - 1 && this.multiLegGroups[this.multiLegStage].length > 0;
}
}
print() {
const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + sel.total, 0);
let currentRowAmount = 0;
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
const maxLegs = this.getMaxLegs(this.currentPool || '');
const horsesPerLeg = this.multiLegGroups.map((group, index) => {
if (group.includes('F')) {
return this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, index);
}
return group.length;
}).slice(0, maxLegs);
const units = parseFloat(this.padValue) || 0;
currentRowAmount = this.calculateMultiLegAmount(
this.selectedLabel as 'TRE' | 'MJP' | 'JKP',
horsesPerLeg,
units
);
if (currentRowAmount > 5000 || selectionsTotal + currentRowAmount > 5000) {
this.totalAmountLimitReached = true;
this.showLimitPopup = true;
return;
}
// Ensure all legs have selections
if (horsesPerLeg.some(count => count === 0)) {
return;
}
} else {
currentRowAmount = this.currentTotal;
if (selectionsTotal + currentRowAmount > 5000) {
this.totalAmountLimitReached = true;
this.showLimitPopup = true;
return;
}
}
this.selectionService.finalizeCurrentRow();
this.resetSelections();
}
//---------Helper Function-----------
getHorseNumbersForSelectedRace(): number[] {
try {
const raceCardDataStr = localStorage.getItem('raceCardData');
console.log('[DEBUG] raceCardDataStr:', raceCardDataStr);
if (!raceCardDataStr) {
console.warn('[DEBUG] No raceCardData found in localStorage');
return [];
}
const raceCardData = JSON.parse(raceCardDataStr);
console.log('[DEBUG] Parsed raceCardData:', raceCardData);
const selectedRaceIdx = parseInt(this.selectedRaceNumber, 10) - 1; // Convert '1' → 0
console.log('[DEBUG] selectedRaceNumber:', this.selectedRaceNumber);
console.log('[DEBUG] selectedRaceIdx:', selectedRaceIdx);
const races = raceCardData.raceVenueRaces?.races || [];
console.log('[DEBUG] races array:', races);
if (races[selectedRaceIdx]) {
console.log('[DEBUG] Horse numbers for selected race:', races[selectedRaceIdx]);
return races[selectedRaceIdx];
} else {
console.warn('[DEBUG] No horses found for selectedRaceIdx:', selectedRaceIdx);
return [];
}
} catch (err) {
console.error('[DEBUG] Error parsing raceCardData:', err);
return [];
}
}
//-------------------PRINT LOGIC----------------------------------------
printTicket() {
const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + sel.total, 0);
if (selectionsTotal + this.currentTotal > 5000) {
this.showLimitPopup = true;
return;
}
console.log('[DEBUG] Horse numbers for selected race:', this.getHorseNumbersForSelectedRace());
//--------------------Added Print here
console.log("🖨️ Print ticket clicked");
const selections = this.selectionService.getSelections();
const currentRow = this.selectionService.getCurrentRow();
let allRows = [...selections];
// ✅ Calculate total if currentRow is valid and not already finalized
if (currentRow.label && currentRow.numbers.length > 0 && currentRow.value > 0) {
if (!currentRow.total) {
let combinations = 1;
if (['TAN', 'FOR', 'QUI'].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
} else if (currentRow.label === 'BOX') {
const n = currentRow.numbers.length;
combinations = n > 1 ? (n * (n - 1)) / 2 : 0;
}
currentRow.total = combinations * currentRow.value * 10;
}
allRows.push(currentRow);
}
if (allRows.length === 0) {
console.warn("No valid rows to print.");
return;
}
const ticketCount = allRows.reduce((sum, row) => sum + (row.value || 0), 0);
const totalAmount = allRows.reduce((sum, row) => sum + (row.total || 0), 0);
const now = new Date();
const venue = 'MYS';
const day = String(now.getDate()).padStart(2, '0');
const month = String(now.getMonth() + 1).padStart(2, '0');
const year = String(now.getFullYear()).slice(-2);
const fullYear = now.getFullYear();
const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '');
const millis = now.getMilliseconds().toString().padStart(3, '0');
// const ticketId = `${venue}/${fullYear}${month}${day}/1`;
const ticketId = `${venue}/${fullYear}${month}${day}/${this.selectedRaceNumber}`;
const barcodeId = `1111${day}${month}${year}${timeStr}${millis}`;
// const winLabels = allRows.map(row => {
// const label = row.label.padEnd(10);
// const numbers = row.numbers.join(',').padEnd(15);
// const value = (`*${row.value || 0}`).padEnd(8);
// const total = `Rs ${row.total || 0}`.padStart(8);
// return `${label}${numbers}${value}${total}`;
// }).join('\n');
const winLabels = allRows.map(row => {
let displayNumbers = row.numbers;
// 🔁 If 'F', expand to all horses
if (row.numbers.length === 1 && row.numbers[0] === 'F') {
displayNumbers = this.getHorseNumbersForSelectedRace().map(n => n.toString());
}
const label = row.label.padEnd(10);
const numbers = displayNumbers.join(',').padEnd(15);
const value = (`*${row.value || 0}`).padEnd(8);
const total = `Rs ${row.total || 0}`.padStart(8);
return `${label}${numbers}${value}${total}`;
}).join('\n');
// ✅ Print preview
const printData = {
ticketId,
barcodeId,
venue,
date: `${day}-${month}-${fullYear}`,
winLabels,
ticketCount,
totalAmount,
gstNumber: '29ABCDE1234F2Z5'
};
// 🧾 Simulated console ticket
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 :`);
printData.winLabels.split('\n').forEach(row => console.log(row));
console.log(`*${printData.ticketCount}${printData.totalAmount}`);
console.log(`GST Number : ${printData.gstNumber}`);
console.log(`Date/Time : ${now.toLocaleString()}`);
console.log('-----------------------------');
// 🖨️ Send to printer API
const payload = {
type: 'ticket',
ticketId: printData.ticketId,
barcodeId: printData.barcodeId,
winLabels: printData.winLabels,
ticketCount: printData.ticketCount,
totalAmount: printData.totalAmount,
gstNumber: printData.gstNumber,
dateTime: now.toLocaleString()
};
fetch('http://localhost:9100/print', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
// ---------------------sending data to backend ---------------------------------
fetch('http://192.168.1.12:8083/api/tickets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
//----------------------------------------ends here --------------------------
.then(response => {
if (!response.ok) {
throw new Error(`Printer error: ${response.status}`);
}
return response.text();
})
.then(result => {
console.log("✅ Print successful:", result);
this.erase(); // ✅ Clear selections after successful print
})
.catch(error => {
console.error("❌ Print failed:", error);
this.erase(); // ✅ Clear selections after successful print
});
//--------------------Ended Print here -----------------------------
this.selectionService.finalizeCurrentRow();
this.resetSelections();
}
//----------------------------------PRINT ENDS HERE ------------------------------------------------------
erase() {
this.selectionService.clearSelections();
this.resetSelections();
}
resetSelections() {
this.selectedLabel = null;
this.selectedNumbers = [];
this.padValue = '';
this.canPrint = false;
this.isBoxed = false;
this.totalAmountLimitReached = false;
this.showLimitPopup = false;
this.tanGroupStage = 0;
this.tanGroups = [[], [], []];
this.isFirstGroupComplete = false;
this.firstGroup = [];
this.secondGroup = [];
this.multiLegStage = 0;
this.multiLegGroups = [[], [], [], [], []];
this.multiLegBaseRaceIdx = 0;
this.currentLegRaceDisplay = '';
this.currentPool = null;
this.fieldModalOpen = false;
this.fieldInput = '';
this.fieldFEntered = false;
this.wspTicketStage = 0;
// Clear multi-leg display in Navbar
this.sharedStateService.updateSharedData({
type: 'multiLegPoolEnd',
value: null
});
}
toggleBoxMode() {
if (this.totalAmountLimitReached) return;
this.isBoxed = !this.isBoxed;
const value = parseFloat(this.padValue) || 0;
if (this.selectedLabel === 'TAN' && this.isBoxed) {
this.tanGroupStage = 0;
this.tanGroups = [[], [], []];
this.selectedNumbers = [];
}
this.selectionService.updatePartial({
isBoxed: this.isBoxed,
label: this.selectedLabel || '',
numbers: [...this.selectedNumbers],
value
});
this.updateCanPrint();
}
removeLastNumber() {
if (!this.selectedLabel || (this.selectedNumbers.length === 0 && !this.selectedNumbers.includes('F'))) return;
if (this.selectedNumbers.includes('F') && this.allowedFieldLabels.includes(this.selectedLabel || '')) {
this.selectedNumbers = [];
this.selectionService.updatePartial({
numbers: [],
isBoxed: false,
label: this.selectedLabel || ''
});
return;
}
if (this.selectedLabel === 'TAN' && this.isBoxed) {
const currentNumbers = this.selectedNumbers.filter(n => typeof n === 'number') as number[];
if (currentNumbers.length > 0) {
currentNumbers.pop();
const groupSize = Math.ceil(currentNumbers.length / 3);
const group1 = currentNumbers.slice(0, groupSize);
const group2 = currentNumbers.slice(group1.length, group1.length + groupSize);
const group3 = currentNumbers.slice(group1.length + group2.length);
const combined: (number | string)[] = [...group1];
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'
});
}
return;
}
if (this.selectedLabel === 'TAN') {
const currentGroup = this.tanGroups[this.tanGroupStage];
if (currentGroup.length > 0) {
currentGroup.pop();
let combined: (number | string)[] = [...this.tanGroups[0]];
if (this.tanGroupStage > 0) combined.push('-', ...this.tanGroups[1]);
if (this.tanGroupStage > 1) combined.push('-', ...this.tanGroups[2]);
this.selectedNumbers = combined;
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
}
return;
}
if (this.multiLegLabels.includes(this.selectedLabel)) {
const currentGroup = this.multiLegGroups[this.multiLegStage];
if (currentGroup.length > 0) {
currentGroup.pop();
this.updateMultiLegSelection();
}
return;
}
if (this.twoGroupLabels.includes(this.selectedLabel)) {
if (!this.isFirstGroupComplete && this.firstGroup.length > 0) {
this.firstGroup.pop();
this.selectedNumbers = [...this.firstGroup];
} else if (this.secondGroup.length > 0) {
this.secondGroup.pop();
this.selectedNumbers = [...this.firstGroup, '-', ...this.secondGroup];
}
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
return;
}
this.selectedNumbers.pop();
this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
}
private getMaxLegs(poolName: string): number {
switch (poolName) {
case 'mjp1':
return 4;
case 'jkp1':
return 5;
case 'trb1':
case 'trb2':
case 'TRE':
return 3;
default:
return 5; // Default to 5 for unspecified pools
}
}
private getRaceForLeg(poolName: string, leg: number): number {
const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
const poolRaces = raceCardData?.raceVenueRaces?.pools?.[poolName] || [];
if (poolRaces.length > leg) {
return poolRaces[leg];
}
// Fallback to default race mapping
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);
}
private updateLegRaceDisplay(poolName: string) {
if (!['mjp1', 'jkp1', 'trb1', 'trb2'].includes(poolName)) {
this.currentLegRaceDisplay = '';
this.currentPool = null;
return;
}
const raceIdx = this.getRaceForLeg(poolName, this.multiLegStage);
this.currentLegRaceDisplay = `Leg ${this.multiLegStage + 1} (Race ${raceIdx})`;
const runnerCount = this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, this.multiLegStage);
this.sharedStateService.setRunnerCount(runnerCount);
this.sharedStateService.updateSharedData({
type: 'currentLegRace',
value: raceIdx
});
}
private getRunnerCountForLeg(baseIdx: number, leg: number): number {
const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
const raceIdx = this.getRaceForLeg(this.currentPool || '', leg) - 1;
const race = raceCardData?.raceVenueRaces?.races?.[raceIdx] || [];
if (race?.runners && Array.isArray(race.runners)) {
return race.runners.length;
}
if (Array.isArray(race)) {
return race.length;
}
return 12;
}
private handleFieldForSpecialLabels() {
if (!this.selectedLabel) return;
if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') {
if (!this.isFirstGroupComplete) {
this.firstGroup = ['F'];
this.selectedNumbers = ['F'];
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: [...this.selectedNumbers],
isBoxed: false
});
} else {
this.secondGroup = ['F'];
this.selectedNumbers = [...this.firstGroup, '-', 'F'];
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: [...this.selectedNumbers],
isBoxed: false
});
}
} else if (this.selectedLabel === 'TAN') {
if (this.isBoxed) {
this.selectedNumbers = ['F'];
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: ['F'],
isBoxed: true
});
} else {
this.tanGroups[this.tanGroupStage] = ['F'];
const combined: (number | string)[] = [...this.tanGroups[0]];
if (this.tanGroupStage > 0) combined.push('-', ...this.tanGroups[1]);
if (this.tanGroupStage > 1) combined.push('-', ...this.tanGroups[2]);
this.selectedNumbers = combined;
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: [...this.selectedNumbers],
isBoxed: false
});
}
} else if (this.multiLegLabels.includes(this.selectedLabel)) {
this.multiLegGroups[this.multiLegStage] = ['F'];
this.updateMultiLegSelection();
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: [...this.selectedNumbers],
isBoxed: false
});
}
}
openCalculator() { this.calculatorOpen = true; this.calcDisplay = ''; }
closeCalculator() { this.calculatorOpen = false; }
press(val: string) { if (this.calcDisplay === 'Error') this.calcDisplay = ''; this.calcDisplay += val; }
clearDisplay() { this.calcDisplay = ''; }
backspace() { this.calcDisplay = this.calcDisplay === 'Error' ? '' : this.calcDisplay.slice(0, -1); }
calculate() {
try { this.calcDisplay = eval(this.calcDisplay).toString(); }
catch { this.calcDisplay = 'Error'; }
}
canUseField(): boolean {
if (this.totalAmountLimitReached) return false;
if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') {
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.multiLegLabels.includes(this.selectedLabel || '') && this.multiLegGroups[this.multiLegStage].length === 0) {
return true;
}
return false;
}
return (
this.selectedLabel !== null &&
this.allowedFieldLabels.includes(this.selectedLabel) &&
this.selectedNumbers.length === 0
);
}
openFieldModal() {
if (this.totalAmountLimitReached) return;
if (['FOR', 'QUI', 'TAN'].includes(this.selectedLabel || '') || this.multiLegLabels.includes(this.selectedLabel || '')) {
this.handleFieldForSpecialLabels();
} else {
this.fieldModalOpen = true;
this.fieldInput = '';
this.fieldFEntered = false;
}
}
closeFieldModal() {
this.fieldModalOpen = false;
}
handleFieldKey(key: string) {
if (key === 'BACK') {
this.fieldInput = this.fieldInput.slice(0, -1);
if (!this.fieldInput.includes('F')) this.fieldFEntered = false;
return;
}
if (key === 'F') {
if (!this.fieldFEntered) {
this.fieldFEntered = true;
this.fieldInput = 'F';
}
} else {
this.fieldInput += key;
}
}
confirmFieldEntry() {
if (!this.fieldFEntered || !this.selectedLabel) return;
if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') {
if (!this.isFirstGroupComplete) {
this.firstGroup = ['F'];
this.selectedNumbers = ['F'];
} else {
this.secondGroup = ['F'];
this.selectedNumbers = [...this.firstGroup, '-', 'F'];
}
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: [...this.selectedNumbers],
isBoxed: false
});
} else if (this.selectedLabel === 'TAN') {
if (this.isBoxed) {
this.selectedNumbers = ['F'];
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: ['F'],
isBoxed: true
});
} else {
this.tanGroups[this.tanGroupStage] = ['F'];
const combined: (number | string)[] = [...this.tanGroups[0]];
if (this.tanGroupStage > 0) combined.push('-', ...this.tanGroups[1]);
if (this.tanGroupStage > 1) combined.push('-', ...this.tanGroups[2]);
this.selectedNumbers = combined;
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: [...this.selectedNumbers],
isBoxed: false
});
}
} else if (this.multiLegLabels.includes(this.selectedLabel)) {
this.multiLegGroups[this.multiLegStage] = ['F'];
this.updateMultiLegSelection();
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: [...this.selectedNumbers],
isBoxed: false
});
} else {
this.selectedNumbers = ['F'];
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: ['F'],
isBoxed: false
});
}
this.closeFieldModal();
}
openPoolReplaceModal() {
if (this.totalAmountLimitReached) return;
this.poolReplaceOpen = true;
}
closePoolReplaceModal() {
this.poolReplaceOpen = false;
}
treButtonClick(btnNum: number) {
this.trePopupVisible = false;
this._selectTreAfterPopup(btnNum);
}
private _selectTreAfterPopup(btnNum: number) {
this.selectedLabel = 'TRE';
this.selectedNumbers = [];
this.padValue = '';
this.canPrint = false;
this.isBoxed = false;
this.tanGroupStage = 0;
this.tanGroups = [[], [], []];
this.isFirstGroupComplete = false;
this.firstGroup = [];
this.secondGroup = [];
this.multiLegStage = 0;
this.multiLegGroups = [[], [], [], [], []];
// Map TRE button to specific pool name and base race dynamically
const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
const trePoolMap: { [key: number]: { name: string } } = {
1: { name: 'trb1' },
2: { name: 'trb2' }
};
const poolInfo = trePoolMap[btnNum] || { name: 'trb1' };
this.currentPool = poolInfo.name;
this.multiLegBaseRaceIdx = this.getBaseRaceIndexForPool(poolInfo.name);
// Broadcast TRE selection
this.sharedStateService.updateSharedData({
type: 'multiLegPoolStart',
value: { label: poolInfo.name, baseRaceIdx: this.multiLegBaseRaceIdx }
});
this.updateLegRaceDisplay(poolInfo.name);
this.selectionService.updatePartial({ label: 'TRE' });
// --- NEW: Update runners and number pad immediately ---
this.actualRunners = this.getActualRunnersForCurrentPoolLeg();
this.runnerCount = this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, 0) || 12;
this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
this.numbersFlat = this.numberRows.flat();
}
closeTrePopup() {
this.trePopupVisible = false;
}
closeLimitPopup() {
this.showLimitPopup = false;
}
copyNumbers() {
if (!this.selectedLabel) {
console.warn('Please select a label before copying numbers.');
return;
}
const selections = this.selectionService.getSelections();
if (selections.length === 0) {
console.warn('No previous rows to copy from.');
return;
}
// Copy numbers from the most recent finalized row
const lastRow = selections[selections.length - 1];
const numbersToCopy = [...lastRow.numbers];
// Apply the copied numbers directly
this.selectedNumbers = numbersToCopy;
// Validate against actual runners
const validNumbers = this.selectedNumbers.filter(num => {
if (typeof num === 'string') return num === 'F' || num === '-';
return this.actualRunners.has(num);
});
this.selectedNumbers = validNumbers;
// Update the current row in the selection service
this.selectionService.updatePartial({
label: this.selectedLabel,
numbers: [...this.selectedNumbers],
isBoxed: this.isBoxed,
value: parseFloat(this.padValue) || 0
});
this.updateCanPrint();
}
}