1373 lines
45 KiB
TypeScript
Executable File
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();
|
|
}
|
|
|
|
} |