1889 lines
63 KiB
TypeScript
Executable File
1889 lines
63 KiB
TypeScript
Executable File
import {
|
|
Component,
|
|
Input,
|
|
OnInit,
|
|
OnDestroy,
|
|
NgZone,
|
|
ChangeDetectionStrategy
|
|
} 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';
|
|
import _ from 'lodash';
|
|
|
|
@Component({
|
|
selector: 'app-touch-pad-menu',
|
|
standalone: true,
|
|
imports: [CommonModule],
|
|
templateUrl: './touch-pad-menu.component.html',
|
|
styleUrls: ['./touch-pad-menu.component.css'],
|
|
changeDetection: ChangeDetectionStrategy.OnPush // 🔥 CRUCIAL for performance
|
|
})
|
|
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;
|
|
poolReplaceOptions: string[] = [];
|
|
|
|
// 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[] = [];
|
|
|
|
enabledHorseNumbers: number[] = [];
|
|
prevEnabledKey: string = '';
|
|
|
|
raceCardData: any = {};
|
|
|
|
constructor(
|
|
private selectionService: SelectionService,
|
|
private sharedStateService: SharedStateService,
|
|
private labelRestrictionService: LabelRestrictionService,
|
|
private ngZone: NgZone // <-- inject NgZone
|
|
) {}
|
|
|
|
|
|
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(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();
|
|
}
|
|
});
|
|
|
|
const data = localStorage.getItem('raceCardData');
|
|
if (data) {
|
|
this.raceCardData = JSON.parse(data);
|
|
} else {
|
|
this.raceCardData = {};
|
|
}
|
|
}
|
|
|
|
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', 'TRE', 'MJP', 'JKP', '.'];
|
|
if (this.multiLegLabels.includes(label)) {
|
|
const maxLegs = this.getMaxLegs(this.currentPool || '');
|
|
// Hide Shash Enter if on the final leg
|
|
return this.multiLegStage < maxLegs - 1;
|
|
}
|
|
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', '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) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
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;
|
|
}
|
|
// 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) {
|
|
return this.selectedNumbers.includes(number);
|
|
}
|
|
// TAN (unboxed): Disable numbers already selected in the current group
|
|
if (this.selectedLabel === 'TAN') {
|
|
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
|
|
if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
|
|
if (!this.isFirstGroupComplete) {
|
|
return this.firstGroup.includes(number);
|
|
} else {
|
|
return this.secondGroup.includes(number);
|
|
}
|
|
}
|
|
// Default case for WIN, SHP, THP, PLC, etc.: Disable if already selected
|
|
return this.selectedNumbers.includes(number);
|
|
}
|
|
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 existingWSP = currentSelections.filter(sel => wspLabels.includes(sel.label));
|
|
|
|
if (existingWSP.length === 0) {
|
|
const blankRows = wspLabels.map(lbl => ({
|
|
label: lbl,
|
|
numbers: [],
|
|
value: 0,
|
|
total: 0,
|
|
isBoxed: false
|
|
}));
|
|
const totalNew = 0;
|
|
const totalExisting = currentSelections.reduce((sum, r) => sum + r.total, 0);
|
|
if (totalExisting + totalNew <= 5000) {
|
|
this.selectionService.setSelections([...currentSelections, ...blankRows]);
|
|
}
|
|
}
|
|
this.selectionService.updatePartial({ label: '', numbers: [], value: 0, total: 0 });
|
|
return;
|
|
}
|
|
|
|
//----------------------------------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 || '')) {
|
|
console.log('Selected label:', this.selectedLabel);
|
|
console.log('Current number clicked:', number);
|
|
|
|
if (!this.isFirstGroupComplete) {
|
|
console.log('First group not complete. Current firstGroup:', this.firstGroup);
|
|
|
|
if (!this.firstGroup.includes(number)) {
|
|
console.log(`Adding ${number} to firstGroup`);
|
|
this.firstGroup.push(number);
|
|
this.selectedNumbers = [...this.firstGroup];
|
|
} else {
|
|
console.log(`${number} already exists in firstGroup`);
|
|
}
|
|
|
|
} else {
|
|
console.log('First group complete. Moving to secondGroup. Current secondGroup:', this.secondGroup);
|
|
|
|
if (!this.secondGroup.includes(number)) {
|
|
console.log(`Adding ${number} to secondGroup`);
|
|
this.secondGroup.push(number);
|
|
this.selectedNumbers = [...this.firstGroup, '-', ...this.secondGroup];
|
|
} else {
|
|
console.log(`${number} already exists in secondGroup`);
|
|
}
|
|
}
|
|
|
|
console.log('Updated selectedNumbers:', this.selectedNumbers);
|
|
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;
|
|
}
|
|
|
|
createVirtualRowsFromWSP(): SelectionData[] {
|
|
const base = this.selectionService.getCurrentRow();
|
|
if (!base.numbers.length || base.value <= 0 || ['WIN', 'SHP', 'PLC'].includes(base.label)) {
|
|
return [];
|
|
}
|
|
return ['WIN', 'SHP', 'PLC'].map(label => {
|
|
const newRow: SelectionData = {
|
|
...base,
|
|
label,
|
|
total: 0
|
|
};
|
|
newRow.total = this.calculateTotal(newRow);
|
|
return newRow;
|
|
});
|
|
}
|
|
|
|
private calculateTotal(row: SelectionData): number {
|
|
if (!row.numbers || row.numbers.length === 0 || row.value <= 0) {
|
|
return 0;
|
|
}
|
|
let combinations = 1;
|
|
if (['TAN', 'FOR', 'QUI'].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);
|
|
combinations = legs.reduce((a, b) => a * b, 1);
|
|
} else if (row.isBoxed) {
|
|
const n = row.numbers.length;
|
|
combinations = n > 1 ? (n * (n - 1)) / 2 : 0;
|
|
}
|
|
return combinations * row.value * 10;
|
|
}
|
|
|
|
clearWSPSelection() {
|
|
if (this.selectedLabel !== 'WSP') return;
|
|
|
|
const labels = ['WIN', 'SHP', 'PLC'];
|
|
const targetLabel = labels[this.wspTicketStage];
|
|
|
|
// Update only the current WSP stage's value to 0
|
|
const updatedSelections = this.selectionService.getSelections().map(sel => {
|
|
if (sel.label === targetLabel && JSON.stringify(sel.numbers) === JSON.stringify(this.selectedNumbers)) {
|
|
return {
|
|
...sel,
|
|
value: 0,
|
|
total: 0
|
|
};
|
|
}
|
|
return sel;
|
|
});
|
|
|
|
this.selectionService.setSelections(updatedSelections);
|
|
this.padValue = '';
|
|
this.updateCanPrint();
|
|
}
|
|
|
|
onPadEnter() {
|
|
if (this.maxRowsReached) return;
|
|
|
|
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 selections = this.selectionService.getSelections();
|
|
|
|
// Find the current WSP row to ensure numbers are synchronized
|
|
const currentWSPRow = selections.find(sel => sel.label === targetLabel);
|
|
if (currentWSPRow) {
|
|
this.selectedNumbers = [...currentWSPRow.numbers]; // Synchronize selectedNumbers
|
|
}
|
|
|
|
const updatedSelections = selections.map(sel => {
|
|
if (sel.label === targetLabel) {
|
|
const total = value * (sel.numbers?.length || 0) * 10;
|
|
return {
|
|
...sel,
|
|
value,
|
|
total
|
|
};
|
|
}
|
|
return sel;
|
|
});
|
|
|
|
this.selectionService.setSelections(updatedSelections);
|
|
|
|
// Only increment stage if not at the last stage (PLC)
|
|
if (this.wspTicketStage < 2) {
|
|
this.wspTicketStage++;
|
|
// Update selectedNumbers for the next stage
|
|
const nextLabel = labels[this.wspTicketStage];
|
|
const nextWSPRow = updatedSelections.find(sel => sel.label === nextLabel);
|
|
if (nextWSPRow) {
|
|
this.selectedNumbers = [...nextWSPRow.numbers];
|
|
}
|
|
}
|
|
|
|
this.padValue = '';
|
|
this.updateCanPrint();
|
|
return;
|
|
}
|
|
|
|
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') {
|
|
if (this.selectedLabel === 'WSP') {
|
|
this.clearWSPSelection();
|
|
return;
|
|
}
|
|
this.padValue = '';
|
|
this.selectionService.updatePartial({
|
|
label: this.selectedLabel || '',
|
|
numbers: [...this.selectedNumbers],
|
|
value: 0,
|
|
total: 0,
|
|
isBoxed: this.isBoxed
|
|
});
|
|
this.updateCanPrint();
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
const currentTotal = updatedSelections.find(sel => sel.label === targetLabel)?.total || 0;
|
|
if (currentTotal === 0 && value > 0) {
|
|
console.log('[DEBUG] WSP row invalid (total = 0), clearing row');
|
|
this.selectionService.setSelections(
|
|
updatedSelections.filter(sel => sel.label !== targetLabel || sel.numbers.length > 0)
|
|
);
|
|
this.wspTicketStage = 0;
|
|
this.padValue = '';
|
|
this.selectedNumbers = [];
|
|
this.selectedLabel = null;
|
|
this.selectionService.updatePartial({
|
|
label: '',
|
|
numbers: [],
|
|
value: 0,
|
|
total: 0,
|
|
isBoxed: false
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
|
|
const maxLegs = this.getMaxLegs(this.currentPool || '');
|
|
const legsFilled = this.multiLegGroups.slice(0, maxLegs).every(group => group.length > 0);
|
|
|
|
if (!legsFilled) {
|
|
console.log('[DEBUG] Multi-leg pool incomplete (not all legs filled), clearing row');
|
|
this.selectionService.updatePartial({
|
|
label: '',
|
|
numbers: [],
|
|
value: 0,
|
|
total: 0,
|
|
isBoxed: false
|
|
});
|
|
this.selectedLabel = null;
|
|
this.selectedNumbers = [];
|
|
this.padValue = '';
|
|
this.isBoxed = false;
|
|
this.multiLegStage = 0;
|
|
this.multiLegGroups = [[], [], [], [], []];
|
|
this.currentPool = null;
|
|
this.multiLegBaseRaceIdx = 0;
|
|
this.currentLegRaceDisplay = '';
|
|
this.sharedStateService.updateSharedData({
|
|
type: 'multiLegPoolEnd',
|
|
value: null
|
|
});
|
|
this.updateCanPrint();
|
|
return;
|
|
}
|
|
|
|
this.selectionService.updatePartial({
|
|
value,
|
|
isBoxed: this.isBoxed,
|
|
label: this.selectedLabel || '',
|
|
numbers: [...this.selectedNumbers]
|
|
});
|
|
|
|
const currentRow = this.selectionService.getCurrentRow();
|
|
if (currentRow.total === 0 && value > 0 && currentRow.numbers.length > 0) {
|
|
console.log('[DEBUG] Multi-leg row invalid (total = 0), auto-clearing current row');
|
|
this.selectionService.updatePartial({
|
|
label: '',
|
|
numbers: [],
|
|
value: 0,
|
|
total: 0,
|
|
isBoxed: false
|
|
});
|
|
this.selectedLabel = null;
|
|
this.selectedNumbers = [];
|
|
this.padValue = '';
|
|
this.isBoxed = false;
|
|
this.multiLegStage = 0;
|
|
this.multiLegGroups = [[], [], [], [], []];
|
|
this.currentPool = null;
|
|
this.multiLegBaseRaceIdx = 0;
|
|
this.currentLegRaceDisplay = '';
|
|
this.sharedStateService.updateSharedData({
|
|
type: 'multiLegPoolEnd',
|
|
value: null
|
|
});
|
|
this.updateCanPrint();
|
|
}
|
|
return;
|
|
}
|
|
|
|
this.selectionService.updatePartial({
|
|
value,
|
|
isBoxed: this.isBoxed,
|
|
label: this.selectedLabel || '',
|
|
numbers: [...this.selectedNumbers]
|
|
});
|
|
|
|
const currentRow = this.selectionService.getCurrentRow();
|
|
if (currentRow.total === 0 && value > 0 && currentRow.numbers.length > 0) {
|
|
console.log('[DEBUG] Row invalid (total = 0), auto-clearing current row');
|
|
this.selectionService.updatePartial({
|
|
label: '',
|
|
numbers: [],
|
|
value: 0,
|
|
total: 0,
|
|
isBoxed: false
|
|
});
|
|
this.selectedLabel = null;
|
|
this.selectedNumbers = [];
|
|
this.padValue = '';
|
|
this.isBoxed = false;
|
|
this.updateCanPrint();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------------------------
|
|
|
|
updateCanPrint() {
|
|
// Disable Enter if maxRowsReached
|
|
if (this.maxRowsReached) {
|
|
this.canPrint = false;
|
|
return;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Add this getter for print button enable logic
|
|
get canPrintTicket(): boolean {
|
|
// At least one valid row in finalized selections or current row
|
|
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'];
|
|
const wspSelections = selections.filter(sel => wspLabels.includes(sel.label));
|
|
|
|
// Check if we are at the last stage (PLC) and all rows are valid
|
|
const allWSPRowsValid = wspSelections.length === 3 && wspSelections.every(row =>
|
|
row.label &&
|
|
row.numbers &&
|
|
row.numbers.length > 0 &&
|
|
row.value >= 1 &&
|
|
row.total > 0
|
|
);
|
|
|
|
return this.wspTicketStage === 2 && allWSPRowsValid;
|
|
}
|
|
|
|
// For non-WSP, keep existing logic: any valid row enables printing
|
|
const hasValidRow = selections.some(
|
|
row => !!row.label && !!row.numbers && row.numbers.length > 0 && row.value > 0 && row.total > 0
|
|
) || (
|
|
!!currentRow.label && !!currentRow.numbers && currentRow.numbers.length > 0 && currentRow.value > 0 && currentRow.total > 0
|
|
);
|
|
return Boolean(hasValidRow);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
if (this.selectedLabel === 'WSP') {
|
|
const virtualRows = this.createVirtualRowsFromWSP();
|
|
const currentSelections = this.selectionService.getSelections();
|
|
const nonWSPSelections = currentSelections.filter(sel => !['WIN', 'SHP', 'PLC'].includes(sel.label));
|
|
this.selectionService.setSelections([...nonWSPSelections, ...virtualRows]);
|
|
}
|
|
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 [];
|
|
}
|
|
}
|
|
|
|
// Helper: Get horse numbers for a specific race index (0-based)
|
|
getHorseNumbersForRaceIdx(raceIdx: number): number[] {
|
|
try {
|
|
const raceCardDataStr = localStorage.getItem('raceCardData');
|
|
if (!raceCardDataStr) return [];
|
|
const raceCardData = JSON.parse(raceCardDataStr);
|
|
const races = raceCardData.raceVenueRaces?.races || [];
|
|
const race = races[raceIdx];
|
|
if (Array.isArray(race)) {
|
|
// If race is array of runners, extract their numbers
|
|
return race
|
|
.map((runner: any) => {
|
|
if (typeof runner === 'number') return runner;
|
|
if (typeof runner === 'object' && runner.number) return Number(runner.number);
|
|
if (typeof runner === 'object' && runner.horseNumber) return Number(runner.horseNumber);
|
|
return null;
|
|
})
|
|
.filter((n: any): n is number => typeof n === 'number' && n > 0);
|
|
}
|
|
return [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
clearLocalTickets() {
|
|
localStorage.removeItem('localTickets');
|
|
console.log('🧹 localTickets cleared from localStorage');
|
|
|
|
// Clear the print count
|
|
localStorage.removeItem('printClickCount');
|
|
console.log('🧼 printClickCount cleared from localStorage');
|
|
// Reset via shared state
|
|
this.sharedStateService.setSalesTotal(0);
|
|
this.sharedStateService.setReceiveTotal(0);
|
|
// window.location.reload();
|
|
this.selectionService.clearSelections();
|
|
this.resetSelections();
|
|
|
|
// Optionally clear print clicks
|
|
// localStorage.removeItem('printClickCount');
|
|
}
|
|
|
|
|
|
//-------------------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");
|
|
|
|
let selections = this.selectionService.getSelections();
|
|
const currentRow = this.selectionService.getCurrentRow();
|
|
|
|
if (this.selectedLabel === 'WSP') {
|
|
selections = selections.filter(sel => ['WIN', 'SHP', 'PLC'].includes(sel.label));
|
|
}
|
|
|
|
let allRows = [...selections];
|
|
|
|
// ✅ Just count clicks — update click count in localStorage
|
|
let clickCount = Number(localStorage.getItem('printClickCount') || '0');
|
|
clickCount += 1;
|
|
localStorage.setItem('printClickCount', clickCount.toString());
|
|
|
|
|
|
// ✅ 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`;
|
|
// For multi-leg pools (TRE, MJP, JKP), show the pool name (trb1, trb2, mjp1, jkp1) instead of race number
|
|
let ticketId: string;
|
|
if (['TRE', 'MJP', 'JKP'].includes(this.selectedLabel || '') && this.currentPool) {
|
|
ticketId = `${venue}/${fullYear}${month}${day}/${this.currentPool}`;
|
|
} else {
|
|
ticketId = `${venue}/${fullYear}${month}${day}/${this.selectedRaceNumber}`;
|
|
}
|
|
const barcodeId = `1111${day}${month}${year}${timeStr}${millis}`;
|
|
|
|
|
|
//----------------------------------------WINLABELS START HERE ------------------------------------------------------
|
|
const winLabels = allRows.map(row => {
|
|
let displayNumbers = row.numbers;
|
|
let displayLabel = row.label; // Use row.label by default
|
|
|
|
// Override label for TRE to use currentPool (trb1 or trb2)
|
|
if (row.label === 'TRE' && this.currentPool) {
|
|
displayLabel = this.currentPool; // Use trb1 or trb2
|
|
}
|
|
|
|
// --- Multi-leg pools: Expand 'F' for each leg ---
|
|
if (['TRE', 'MJP', 'JKP'].includes(row.label)) {
|
|
// Split by '/' for legs
|
|
let legs: (number | string)[][] = [];
|
|
let currentLeg: (number | string)[] = [];
|
|
for (const n of displayNumbers) {
|
|
if (n === '/') {
|
|
legs.push(currentLeg);
|
|
currentLeg = [];
|
|
} else {
|
|
currentLeg.push(n);
|
|
}
|
|
}
|
|
if (currentLeg.length) legs.push(currentLeg);
|
|
|
|
// For each leg, expand 'F' to correct horse numbers for that leg
|
|
const poolName = row.label === 'MJP' ? 'mjp1' : row.label === 'JKP' ? 'jkp1' : this.currentPool || 'trb1';
|
|
const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
|
|
const poolRaces = raceCardData?.raceVenueRaces?.pools?.[poolName] || [];
|
|
// Fallback to hardcoded mapping if needed
|
|
const raceMap: { [key: string]: number[] } = {
|
|
'mjp1': [1, 2, 3, 4],
|
|
'jkp1': [3, 4, 5, 6, 7],
|
|
'trb1': [2, 3, 4],
|
|
'trb2': [5, 6, 7]
|
|
};
|
|
const raceIndices: number[] = poolRaces.length > 0 ? poolRaces : (raceMap[poolName] || []);
|
|
// If no mapping, fallback to consecutive races from 1
|
|
const baseRaceIdx = raceIndices.length > 0 ? raceIndices[0] : 1;
|
|
|
|
let expandedLegs: string[] = legs.map((leg, i) => {
|
|
// Find race index for this leg
|
|
let raceIdx = raceIndices.length > i ? raceIndices[i] - 1 : (baseRaceIdx - 1 + i);
|
|
// let expanded = leg.flatMap(n =>
|
|
// n === 'F'
|
|
// ? this.getHorseNumbersForRaceIdx(raceIdx).map(num => num.toString())
|
|
// : [n]
|
|
// );
|
|
let expanded = leg.flatMap((n, idx) => {
|
|
if (n === 'F') {
|
|
const horses = this.getHorseNumbersForRaceIdx(raceIdx).map(num => num.toString()).join(',');
|
|
const isFirst = idx === 0;
|
|
const isLast = idx === leg.length - 1;
|
|
if (isFirst && !isLast) return [`${horses}-`];
|
|
if (!isFirst && isLast) return [`-${horses}`];
|
|
if (isFirst && isLast) return [horses]; // only F in the leg
|
|
return [`-${horses}-`];
|
|
}
|
|
return [n];
|
|
});
|
|
|
|
// Remove '-' and '#' for display
|
|
return expanded.filter(n => n !== '-' && n !== '#').join(',');
|
|
});
|
|
|
|
// Join legs with '/'
|
|
let numbersStr = expandedLegs.join(' / ');
|
|
|
|
const label = displayLabel.padEnd(10);
|
|
const numbers = numbersStr.padEnd(15);
|
|
const value = (`*${row.value || 0}`).padEnd(8);
|
|
return `${label}${numbers} ${value}`;
|
|
}
|
|
|
|
// 🐎 Expand 'F' to full horse numbers for other pools
|
|
if (displayNumbers.includes('F')) {
|
|
// displayNumbers = displayNumbers.flatMap(n =>
|
|
// n === 'F' ? this.getHorseNumbersForSelectedRace().map(num => num.toString()) : [n]
|
|
// );
|
|
displayNumbers = displayNumbers.flatMap((n, idx, arr) => {
|
|
if (n === 'F') {
|
|
const horses = this.getHorseNumbersForSelectedRace().map(num => num.toString()).join(',');
|
|
const isFirst = idx === 0;
|
|
const isLast = idx === arr.length - 1;
|
|
if (isFirst && !isLast) return [`${horses}-`];
|
|
if (!isFirst && isLast) return [`-${horses}`];
|
|
if (isFirst && isLast) return [horses]; // only F
|
|
return [`-${horses}-`];
|
|
}
|
|
return [n];
|
|
});
|
|
|
|
}
|
|
|
|
let numbersStr = '';
|
|
|
|
// 🎯 FOR, QUI, TAN logic with box check
|
|
if (['FOR', 'QUI'].includes(row.label)) {
|
|
const actualNumbers = displayNumbers.filter(n => n !== '#' && n !== '-').join(',');
|
|
if (row.numbers.includes('#') || row.isBoxed) { // ✅ box condition
|
|
numbersStr = `${actualNumbers} - ${actualNumbers}`;
|
|
} else {
|
|
numbersStr = actualNumbers;
|
|
}
|
|
} else {
|
|
// 📝 All other pools
|
|
numbersStr = displayNumbers.filter(n => n !== '#' && n !== '-').join(',');
|
|
}
|
|
|
|
const label = displayLabel.padEnd(10);
|
|
const numbers = numbersStr.padEnd(15);
|
|
const value = (`*${row.value || 0}`).padEnd(8);
|
|
const total = `Rs ${row.total || 0}`.padStart(8);
|
|
return `${label}${numbers} ${value} ${total}`;
|
|
}).join('\n');
|
|
|
|
//------------------------------------WIN LABELS ENDS HERE --------------------------------
|
|
|
|
// ✅ 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 ---------------------------------
|
|
|
|
try {
|
|
const existingTicketsStr = localStorage.getItem('localTickets');
|
|
const existingTickets = existingTicketsStr ? JSON.parse(existingTicketsStr) : [];
|
|
|
|
existingTickets.push(payload);
|
|
|
|
localStorage.setItem('localTickets', JSON.stringify(existingTickets));
|
|
|
|
console.log('📦 [DEBUG] Ticket saved locally to localStorage.');
|
|
} catch (error) {
|
|
console.error('❌ Failed to store ticket locally:', error);
|
|
}
|
|
|
|
//-------------------LOG PRINT DETAILS --------------------------------------------------
|
|
|
|
try {
|
|
const existingTicketsStr = localStorage.getItem('localTicketsViewlog');
|
|
const existingTickets = existingTicketsStr ? JSON.parse(existingTicketsStr) : [];
|
|
|
|
// ⛔️ If there are already 10 tickets, remove the oldest (index 0)
|
|
if (existingTickets.length >= 10) {
|
|
existingTickets.shift(); // remove 0th element
|
|
}
|
|
|
|
// ✅ Push the new ticket
|
|
existingTickets.push(payload);
|
|
|
|
// 📝 Save updated list back to localStorage
|
|
localStorage.setItem('localTicketsViewlog', JSON.stringify(existingTickets));
|
|
|
|
console.log('📦 [DEBUG] Ticket saved locally to localStorage.');
|
|
} catch (error) {
|
|
console.error('❌ Failed to store ticket locally:', error);
|
|
}
|
|
|
|
//--------------------------------ENDED HERE ------------------------------------------------
|
|
|
|
try {
|
|
localStorage.setItem('localTicketsnew', JSON.stringify([payload]));
|
|
console.log('📦 [DEBUG] Latest ticket stored in localStorage (previous cleared).');
|
|
} catch (error) {
|
|
console.error('❌ Failed to store ticket locally:', error);
|
|
}
|
|
|
|
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
|
|
});
|
|
|
|
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;
|
|
// Determine allowed group based on current selection
|
|
const groupA = ['WIN', 'SHP', 'THP', 'PLC', 'SHW'];
|
|
const groupB = ['FOR', 'QUI', 'TAN'];
|
|
if (groupA.includes(this.selectedLabel || '')) {
|
|
this.poolReplaceOptions = groupA;
|
|
} else if (groupB.includes(this.selectedLabel || '')) {
|
|
this.poolReplaceOptions = groupB;
|
|
} else {
|
|
this.poolReplaceOptions = [];
|
|
}
|
|
this.poolReplaceOpen = true;
|
|
}
|
|
|
|
handlePoolReplace(label: string) {
|
|
this.selectedLabel = label;
|
|
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 = [[], [], [], [], []];
|
|
this.multiLegBaseRaceIdx = 0;
|
|
this.currentLegRaceDisplay = '';
|
|
this.currentPool = null;
|
|
this.fieldModalOpen = false;
|
|
this.fieldInput = '';
|
|
this.fieldFEntered = false;
|
|
this.wspTicketStage = 0;
|
|
this.poolReplaceOpen = false;
|
|
// Update selection service with new label
|
|
this.selectionService.updatePartial({ label });
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
// Add trackByHorse for use in *ngFor
|
|
trackByHorse(index: number, item: number): number {
|
|
return item;
|
|
}
|
|
|
|
// Example usage of _.uniq for enabledHorseNumbers (if you ever set it)
|
|
setEnabledHorseNumbers(numbers: number[]) {
|
|
this.enabledHorseNumbers = _.uniq(numbers);
|
|
}
|
|
|
|
// If you ever need to deduplicate numbers before rendering:
|
|
get dedupedEnabledHorseNumbers(): number[] {
|
|
return _.uniq(this.enabledHorseNumbers);
|
|
}
|
|
// Update canEnterRow to require value between 1 and 100
|
|
get canEnterRow(): boolean {
|
|
if (this.maxRowsReached) {
|
|
console.log('[DEBUG] canEnterRow: maxRowsReached is true, disabling Enter');
|
|
return false;
|
|
}
|
|
|
|
// Special handling for WSP
|
|
if (this.selectedLabel === 'WSP') {
|
|
const isValidPadValue = this.padValue.trim().length > 0 && /^[0-9]+$/.test(this.padValue);
|
|
const hasNumbers = this.selectedNumbers.length > 0;
|
|
console.log('[DEBUG] canEnterRow (WSP):', {
|
|
isValidPadValue,
|
|
hasNumbers,
|
|
padValue: this.padValue,
|
|
selectedNumbers: this.selectedNumbers,
|
|
wspTicketStage: this.wspTicketStage
|
|
});
|
|
return isValidPadValue && hasNumbers;
|
|
}
|
|
|
|
// Default logic for non-WSP
|
|
const currentRow = this.selectionService.getCurrentRow();
|
|
const result = !!currentRow.label &&
|
|
!!currentRow.numbers &&
|
|
currentRow.numbers.length > 0 &&
|
|
typeof currentRow.value === 'number' &&
|
|
currentRow.value >= 1 &&
|
|
currentRow.value <= 100 &&
|
|
currentRow.total > 0;
|
|
console.log('[DEBUG] canEnterRow (non-WSP):', {
|
|
label: currentRow.label,
|
|
numbers: currentRow.numbers,
|
|
value: currentRow.value,
|
|
total: currentRow.total,
|
|
result
|
|
});
|
|
return result;
|
|
}
|
|
} |