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(); actualRunners: Set = 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 { 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 { 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(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(); } }