diff --git a/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts b/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts index 470e56c..a29f920 100755 --- a/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts +++ b/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts @@ -1,4 +1,3 @@ -// touch-pad-menu.component.ts import { Component, Input, @@ -13,7 +12,7 @@ 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 { StopbetService } from '../../service/stopbet.service'; // Import StopbetService +import { StopbetService } from '../../service/stopbet.service'; import _ from 'lodash'; @Component({ @@ -65,12 +64,12 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { firstGroup: (number | string)[] = []; secondGroup: (number | string)[] = []; - // Multi-leg logic (TRE, MJP, JPP) + // Multi-leg logic multiLegStage = 0; multiLegGroups: (number | string)[][] = [[], [], [], [], []]; - multiLegBaseRaceIdx: number = 0; // Track starting race index (1-based) - currentLegRaceDisplay: string = ''; // Display current leg's race - currentPool: string | null = null; // canonical pool key like 'mjp1','trb1','JPP1' + multiLegBaseRaceIdx: number = 0; // 1-based index from structuredRaceCard pools + currentLegRaceDisplay: string = ''; + currentPool: string | null = null; // exact pool key as present in structuredRaceCard (e.g. 'TBP1','MJP1','JPP1') isBoxed: boolean = false; @@ -89,7 +88,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { private currentRowSubscription: Subscription | null = null; private selectionsSubscription: Subscription | null = null; private runnerCountSubscription: Subscription | null = null; - private stopbetSubscription: Subscription | null = null; // Subscription for stopbet statuses + private stopbetSubscription: Subscription | null = null; private currentTotal: number = 0; private currentSelections: SelectionData[] = []; @@ -102,26 +101,25 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { selectedRaceNumber: string = '1'; - private stopbetStatuses: Map = new Map(); // Local copy of stopbet statuses + private stopbetStatuses: Map = new Map(); constructor( private selectionService: SelectionService, private sharedStateService: SharedStateService, private labelRestrictionService: LabelRestrictionService, - private stopbetService: StopbetService, // Inject StopbetService + private stopbetService: StopbetService, private ngZone: NgZone, private cdr: ChangeDetectorRef ) {} ngOnInit() { - // Prefer rpinfo.structuredRaceCard + // Always prefer rpinfo.structuredRaceCard if present, else fall back to raceCardData key (but still use only structuredRaceCard content) const rpinfo = this.safeGetJSON('rpinfo'); if (rpinfo && rpinfo.structuredRaceCard) { this.structuredRaceCard = rpinfo.structuredRaceCard; } else { - // fallback const rc = this.safeGetJSON('raceCardData'); - this.structuredRaceCard = (rc && rc.structuredRaceCard) ? rc.structuredRaceCard : (rc || {}); + this.structuredRaceCard = (rc && rc.structuredRaceCard) ? rc.structuredRaceCard : {}; } this.raceCardData = this.structuredRaceCard; @@ -139,7 +137,6 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { this.numbersFlat = this.numberRows.flat(); this.updateLegRaceDisplay(this.currentPool || ''); this.btid = localStorage.getItem('btid'); - // --- NEW: Update actualRunners when runner count changes --- this.setActualRunners(); this.cdr.markForCheck(); }); @@ -151,10 +148,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { this.maxRowsReached = selections.length >= 5; const totalAmount = selections.reduce((sum: number, selection: SelectionData) => sum + (selection.total || 0), 0); this.totalAmountLimitReached = totalAmount >= 5000; - - // NEW: this.refreshBlockedLabels(this.selectedLabel); - if (!this.totalAmountLimitReached) { this.showLimitPopup = false; } @@ -166,11 +160,9 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { this.cdr.markForCheck(); }); - // Subscribe to selectedRace (from navbar) this.sharedStateService.selectedRace$.subscribe((race: number) => { this.selectedRaceNumber = String(race || '1'); if (this.currentPool && this.multiLegLabels.includes(this.selectedLabel || '')) { - // multi leg currently active: update display & runners to reflect pool mapping this.updateLegRaceDisplay(this.currentPool); const runnerCount = this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, this.multiLegStage); this.runnerCount = runnerCount || 12; @@ -183,7 +175,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { this.cdr.markForCheck(); }); - // legacy storage - keep for compatibility if rpinfo absent + // Keep raw storage copy if present (but all logic below uses structuredRaceCard) const data = localStorage.getItem('raceCardData'); if (data) { try { this.raceCardData = JSON.parse(data); } catch { this.raceCardData = this.raceCardData || {}; } @@ -207,63 +199,127 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { } } - // Normalize pool name to canonical keys used in structuredRaceCard - private normalizePoolName(name: string | null | undefined): string | null { + // --- STRICT pool/key mapping using structuredRaceCard only --- + private normalizePoolNameToKey(name: string | null | undefined): string | null { if (!name) return null; - const n = String(name).toLowerCase(); - - if (n.startsWith('trb') || n.startsWith('tbp')) { - return n.includes('2') ? 'trb2' : 'trb1'; + const n = String(name).trim(); + // Expect caller to pass canonical pool names like 'mjp1','jkp1','trb1' or uppercase 'MJP1' etc. + // Normalize to uppercase keys used in structuredRaceCard.pools (e.g., 'TBP1','MJP1','JPP1') + const lower = n.toLowerCase(); + if (lower.startsWith('tbp') || lower.startsWith('trb')) { + const possible = ['TBP1', 'TBP2', 'TRB1', 'TRB2']; + return this.findExistingPoolKey(possible) || n.toUpperCase(); } - if (n.startsWith('mjp')) { - return n.includes('2') ? 'mjp2' : 'mjp1'; + if (lower.startsWith('mjp')) { + const possible = ['MJP1', 'MJP2']; + return this.findExistingPoolKey(possible) || n.toUpperCase(); } - if (n.startsWith('jkp') || n.startsWith('jpp') || n.startsWith('jkp')) { - return n.includes('2') ? 'jkp2' : 'jkp1'; + if (lower.startsWith('jkp') || lower.startsWith('jpp')) { + const possible = ['JPP1', 'JPP2', 'JKP1', 'JKP2']; + return this.findExistingPoolKey(possible) || n.toUpperCase(); } - // default: return lowercase - return n; + return n.toUpperCase(); } - // --- NEW HELPER METHOD --- - getActualRunnersForCurrentPoolLeg(): Set { + private findExistingPoolKey(possibleKeys: string[]): string | null { + const pools = this.structuredRaceCard?.pools || {}; + for (const k of possibleKeys) { + if (k in pools) return k; + } + const poolKeys = Object.keys(pools || {}); + for (const k of possibleKeys) { + const lc = k.toLowerCase(); + const found = poolKeys.find(pk => pk.toLowerCase().includes(lc)); + if (found) return found; + } + return null; + } + + // --- RACE/POOL LOOKUPS: use structuredRaceCard only (no static fallbacks) --- + private getRaceIndicesForPoolExact(poolKey: string): number[] { + const pools = this.structuredRaceCard?.pools || {}; + return Array.isArray(pools?.[poolKey]) ? pools[poolKey].slice() : []; + } + + private getRaceForLegExact(poolKey: string, leg: number): number | null { + const combo = this.structuredRaceCard?.comboRaces || {}; + if (Array.isArray(combo?.[poolKey]) && combo[poolKey].length > leg) { + return combo[poolKey][leg]; + } + const pools = this.structuredRaceCard?.pools || {}; + if (Array.isArray(pools?.[poolKey]) && pools[poolKey].length > leg) { + return pools[poolKey][leg]; + } + return null; + } + + private getBaseRaceIndexForPoolExact(poolKey: string): number | null { + const poolRaces = this.getRaceIndicesForPoolExact(poolKey); + if (poolRaces.length > 0) return poolRaces[0]; + return null; + } + + private getRunnerCountForLegExact(raceNo: number | null): number { + if (raceNo === null) return 0; const races = this.structuredRaceCard?.raceVenueRaces?.races || []; - const poolName = this.normalizePoolName(this.currentPool) || ''; - const poolRaces: number[] = this.structuredRaceCard?.pools?.[poolName] || []; - const raceIdx = poolRaces.length > this.multiLegStage - ? poolRaces[this.multiLegStage] - 1 - : (this.multiLegBaseRaceIdx - 1) + this.multiLegStage; - // Check if the race is open - if (!this.stopbetStatuses.has(raceIdx + 1) || this.stopbetStatuses.get(raceIdx + 1) === 'N') { - const race = races[raceIdx]; - if (race?.horses && Array.isArray(race.horses)) { - return new Set(race.horses.map((num: any) => Number(num))); - } - } - return new Set(); + const race = races.find((r: any) => Number(r?.raceNo) === Number(raceNo)); + if (!race) return 0; + if (Array.isArray(race.horses)) return race.horses.length; + return 0; + } + + getActualRunnersForCurrentPoolLeg(): Set { + if (!this.currentPool) return new Set(); + const poolKey = this.normalizePoolNameToKey(this.currentPool); + if (!poolKey) return new Set(); + + const raceNo = this.getRaceForLegExact(poolKey, this.multiLegStage); + if (raceNo === null) return new Set(); + + const stop = this.stopbetStatuses.get(raceNo); + if (stop === 'Y') return new Set(); + + const races = this.structuredRaceCard?.raceVenueRaces?.races || []; + const race = races.find((r: any) => Number(r?.raceNo) === Number(raceNo)); + if (!race || !Array.isArray(race.horses)) return new Set(); + return new Set(race.horses.map((n: any) => Number(n))); + } + + getActualRunnersForCurrentRace(): Set { + const races = this.structuredRaceCard?.raceVenueRaces?.races || []; + const selectedRaceIdx = parseInt(this.selectedRaceNumber, 10); + const race = races.find((r: any) => Number(r?.raceNo) === Number(selectedRaceIdx)); + if (!race || !Array.isArray(race.horses)) return new Set(); + const stop = this.stopbetStatuses.get(selectedRaceIdx); + if (stop === 'Y') return new Set(); + return new Set(race.horses.map((n: any) => Number(n))); } - // --- MODIFIED METHOD --- setActualRunners() { if (this.currentPool && this.multiLegLabels.includes(this.selectedLabel || '')) { this.actualRunners = this.getActualRunnersForCurrentPoolLeg(); + const raceNo = this.getRaceForLegExact(this.currentPool || '', this.multiLegStage); + this.runnerCount = this.getRunnerCountForLegExact(raceNo); } else { this.actualRunners = this.getActualRunnersForCurrentRace(); + const selectedRaceIdx = parseInt(this.selectedRaceNumber, 10); + this.runnerCount = this.getRunnerCountForLegExact(selectedRaceIdx); } } - // --- NEW METHOD --- - getActualRunnersForCurrentRace(): Set { + getHorseNumbersForSelectedRace(): number[] { const races = this.structuredRaceCard?.raceVenueRaces?.races || []; - const selectedRaceIdx = parseInt(this.selectedRaceNumber, 10) - 1; - // Check if the race is open - if (!this.stopbetStatuses.has(selectedRaceIdx + 1) || this.stopbetStatuses.get(selectedRaceIdx + 1) === 'N') { - const race = races[selectedRaceIdx]; - if (race?.horses && Array.isArray(race.horses)) { - return new Set(race.horses.map((num: any) => Number(num))); - } - } - return new Set(); + const selectedRaceIdx = parseInt(this.selectedRaceNumber, 10); + const race = races.find((r: any) => Number(r?.raceNo) === Number(selectedRaceIdx)); + if (!race || !Array.isArray(race.horses)) return []; + return race.horses.map((n: any) => Number(n)); + } + + getHorseNumbersForRaceIdx(raceNo: number): number[] { + const races = this.structuredRaceCard?.raceVenueRaces?.races || []; + const race = races.find((r: any) => Number(r?.raceNo) === Number(raceNo)); + if (!race || !Array.isArray(race.horses)) return []; + return race.horses.map((n: any) => Number(n)); } get labelRows() { @@ -286,7 +342,6 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { const specialLabels = ['FRP', 'QNP', 'TNP', 'EXA', 'TBP', 'MJP', 'JPP', '.']; 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); @@ -294,9 +349,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { get isShashEnterDisabled(): boolean { if (this.selectedLabel === 'TNP') { - if (this.isBoxed) { - return true; - } + 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 || ''); @@ -316,16 +369,9 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { get isBoxToggleDisabled(): boolean { if (!this.selectedLabel) return true; const disallowedBoxLabels = ['WNP', 'SHP', 'THP', 'PLP', 'WSP', 'SHW', 'TBP', 'MJP', 'JPP']; - if (disallowedBoxLabels.includes(this.selectedLabel)) { - return true; - } - if (this.selectedNumbers.includes('F')) { - return true; - } - // Disable Box toggle for TNP, QNP, FRP after any number is selected - if (['TNP', 'QNP', 'FRP'].includes(this.selectedLabel) && this.selectedNumbers.length > 0) { - return true; - } + if (disallowedBoxLabels.includes(this.selectedLabel)) return true; + if (this.selectedNumbers.includes('F')) return true; + if (['TNP', 'QNP', 'FRP'].includes(this.selectedLabel) && this.selectedNumbers.length > 0) return true; return false; } @@ -343,32 +389,24 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { isLabelDisabled(label: string): boolean { if (this.disabledLabels.includes(label) || this.totalAmountLimitReached || this.blockedLabels.has(label)) return true; - - // Additional check for multi-leg pools: disable if any race in the pool is stopped if (this.multiLegLabels.includes(label)) { - const poolName = label === 'MJP' ? 'mjp1' : label === 'JPP' ? 'jkp1' : 'trb1'; - const raceIndices = this.getRaceIndicesForPool(poolName); - if (raceIndices.some(race => !this.isOpen(race))) { - return true; - } + const poolKey = this.labelToPoolKey(label); + const raceIndices = this.getRaceIndicesForPoolExact(poolKey || ''); + if (raceIndices.length === 0) return true; + if (raceIndices.some(raceNo => this.stopbetStatuses.get(raceNo) === 'Y')) return true; } return false; } - private getRaceIndicesForPool(poolName: string): number[] { - const poolKey = this.normalizePoolName(poolName) || poolName; - const poolRaces: number[] = this.structuredRaceCard?.pools?.[poolKey] || []; - if (poolRaces.length > 0) return poolRaces; - 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[poolKey] || []; + private labelToPoolKey(label: string): string | null { + switch (label) { + case 'TBP': return this.findExistingPoolKey(['TBP1', 'TBP2', 'TRB1', 'TRB2']); + case 'MJP': return this.findExistingPoolKey(['MJP1', 'MJP2']); + case 'JPP': return this.findExistingPoolKey(['JPP1', 'JPP2', 'JKP1', 'JKP2']); + default: return null; + } } - // --- MODIFIED METHOD --- isNumberDisabled(number: number): boolean { // Disable if number not present in actualRunners if (!this.actualRunners.has(number)) return true; @@ -388,13 +426,9 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { } // Two-group pools (FRP, QNP): Disable numbers already selected in the current group if (this.twoGroupLabels.includes(this.selectedLabel || '')) { - if (!this.isFirstGroupComplete) { - return this.firstGroup.includes(number); - } else { - return this.secondGroup.includes(number); - } + if (!this.isFirstGroupComplete) return this.firstGroup.includes(number); + return this.secondGroup.includes(number); } - // Default case for WNP, SHP, THP, PLP, etc.: Disable if already selected return this.selectedNumbers.includes(number); } @@ -416,19 +450,27 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { // Store base race index and pool name for multi-leg pools if (this.multiLegLabels.includes(label)) { - // Map to canonical pool key - const poolName = label === 'MJP' ? 'mjp1' : label === 'JPP' ? 'jkp1' : 'trb1'; - this.currentPool = poolName; - this.multiLegBaseRaceIdx = this.getBaseRaceIndexForPool(poolName); // Broadcast race and pool info for navbar - // Broadcast race and pool info for navbar + const poolKey = this.labelToPoolKey(label); + if (!poolKey) { + this.selectedLabel = null; + this.refreshBlockedLabels(null); + return; + } + this.currentPool = poolKey; + const baseRace = this.getBaseRaceIndexForPoolExact(poolKey); + if (!baseRace) { + this.selectedLabel = null; + this.refreshBlockedLabels(null); + return; + } + this.multiLegBaseRaceIdx = baseRace; this.sharedStateService.updateSharedData({ type: 'multiLegPoolStart', - value: { label: poolName, baseRaceIdx: this.multiLegBaseRaceIdx } + value: { label: poolKey, baseRaceIdx: this.multiLegBaseRaceIdx } }); - this.updateLegRaceDisplay(poolName); - // --- NEW: Update runners and number pad immediately --- + this.updateLegRaceDisplay(poolKey); this.actualRunners = this.getActualRunnersForCurrentPoolLeg(); - this.runnerCount = this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, 0) || 12; + this.runnerCount = this.getRunnerCountForLegExact(this.getRaceForLegExact(poolKey, 0)); this.numbers = Array.from({ length: 30 }, (_, i) => i + 1); this.numbersFlat = this.numberRows.flat(); // Call after pool change @@ -463,9 +505,8 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { total: 0, isBoxed: false })); - const totalNew = 0; const totalExisting = currentSelections.reduce((sum, r) => sum + r.total, 0); - if (totalExisting + totalNew <= 5000) { + if (totalExisting <= 5000) { this.selectionService.setSelections([...currentSelections, ...blankRows]); // Call after setSelections this.refreshBlockedLabels(label); @@ -494,53 +535,6 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { this.refreshBlockedLabels(label); } - private getBaseRaceIndexForPool(poolName: string): number { - const races = this.structuredRaceCard?.raceVenueRaces?.races || []; - const totalRaces = races.length || 10; - const maxLegs = this.getMaxLegs(poolName); - const poolKey = this.normalizePoolName(poolName) || poolName; - const poolRaces: number[] = this.structuredRaceCard?.pools?.[poolKey] || []; - let baseRaceIdx = poolRaces.length > 0 ? poolRaces[0] : this.getDefaultBaseRace(poolKey); - // Ensure baseRaceIdx is open - if (!this.isOpen(baseRaceIdx)) { - baseRaceIdx = this.getOpenRaceStartingFrom(baseRaceIdx); - } - 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': 3, - 'jkp1': 2, - 'trb1': 4 - }; - return poolRaceMap[poolName.toLowerCase()] || parseInt(this.selectedRaceNumber, 10); - } - - private isOpen(race: number): boolean { - const status = this.stopbetStatuses.get(race); - return status === 'N' || status === undefined; // Assume open if unknown - } - - private getOpenRaceStartingFrom(start: number): number { - const max = this.structuredRaceCard?.raceVenueRaces?.races?.length || 10; - for (let r = start; r <= max; r++) { - if (this.isOpen(r)) { - return r; - } - } - for (let r = 1; r < start; r++) { - if (this.isOpen(r)) { - return r; - } - } - return start; - } - selectNumber(number: number) { if (!this.selectedLabel || this.totalAmountLimitReached || !this.actualRunners.has(number)) return; @@ -588,40 +582,35 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy { return; } - - // FRP/QNP logic -if (this.twoGroupLabels.includes(this.selectedLabel || '')) { - console.log('Selected label:', this.selectedLabel); - console.log('Current number clicked:', number); + 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.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`); + } + } - 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`); + console.log('Updated selectedNumbers:', this.selectedNumbers); + this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] }); + return; } - } 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 selection if (!this.selectedNumbers.includes(number)) { this.selectedNumbers.push(number); @@ -1033,47 +1022,15 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) { this.resetSelections(); } - // Helpers: get horse numbers for selected race and specific race index - getHorseNumbersForSelectedRace(): number[] { - const races = this.structuredRaceCard?.raceVenueRaces?.races || []; - const selectedRaceIdx = parseInt(this.selectedRaceNumber, 10) - 1; - const race = races[selectedRaceIdx]; - if (race?.horses && Array.isArray(race.horses)) return race.horses.map((n: any) => Number(n)); - return []; - } - - getHorseNumbersForRaceIdx(raceIdx: number): number[] { - const races = this.structuredRaceCard?.raceVenueRaces?.races || []; - const race = races[raceIdx]; - if (race?.horses && Array.isArray(race.horses)) return race.horses.map((n: any) => Number(n)); - 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); - this.selectionService.clearSelections(); - this.resetSelections(); - } - - -//-------------------PRINT LOGIC---------------------------------------- -async printTicket() { - const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + sel.total, 0); + //-------------------PRINT LOGIC---------------------------------------- + async printTicket() { + const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + (sel.total || 0), 0); if (selectionsTotal + this.currentTotal > 5000) { this.showLimitPopup = true; - this.cdr.markForCheck(); // <-- Ensure popup visibility updates + this.cdr.markForCheck(); return; } - console.log('[DEBUG] Horse numbers for selected race:', this.getHorseNumbersForSelectedRace()); - //--------------------Added Print here - + console.log('[DEBUG] Horse numbers for selected race:', this.getHorseNumbersForSelectedRace()); console.log("๐Ÿ–จ๏ธ Print ticket clicked"); let selections = this.selectionService.getSelections(); @@ -1085,26 +1042,21 @@ async printTicket() { 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 (['TNP', 'FRP', 'QNP'].includes(currentRow.label)) { - combinations = currentRow.numbers.length * (currentRow.numbers.length - 1); - } else if (['TBP', 'JPP', '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; - } + let clickCount = Number(localStorage.getItem('printClickCount') || '0'); + clickCount += 1; + localStorage.setItem('printClickCount', clickCount.toString()); + if (currentRow.label && currentRow.numbers.length > 0 && currentRow.value > 0) { + if (!currentRow.total) { + let combinations = 1; + if (['TNP', 'FRP', 'QNP'].includes(currentRow.label)) { + combinations = currentRow.numbers.length * (currentRow.numbers.length - 1); + } else if (['TBP', 'JPP', 'MJP'].includes(currentRow.label)) { + combinations = 1; + } 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); @@ -1137,476 +1089,314 @@ async printTicket() { ticketId = `${venue}/${fullYear}${month}${day}/${this.selectedRaceNumber}`; } const barcodeId = `${btId}${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 + const winLabels = allRows.map(row => { + let displayNumbers = row.numbers; + let displayLabel = row.label; - // Override label for TRE to use currentPool (trb1 or trb2) - if (row.label === 'TBP' && this.currentPool) { - displayLabel = this.currentPool; // Use trb1 or trb2 - } - -// // --- Multi-leg pools: Expand 'F' for each leg --- -// if (['TBP', 'MJP', 'JPP'].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 === 'JPP' ? '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) => { -// if (n === 'F') { -// // Expand F โ†’ horse numbers, no extra dashes -// return this.getHorseNumbersForRaceIdx(raceIdx).map(num => num.toString()); -// } -// return [n]; // Keep original (including '-') if user entered -// }); - -// // 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}`; -// } - -// Helper to pad ticket count to 3 digits (if you don't already have it elsewhere) -function padTicketCountToThree(n: number) { - const num = Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0; - return String(num).padStart(3, '0'); -} - -// Replace the existing TBP / MJP / JPP block with this: -if (['TBP', 'MJP', 'JPP'].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); - - // Choose pool name (used only for race->horses lookup) - const poolName = row.label === 'MJP' ? 'mjp1' : row.label === 'JPP' ? 'jkp1' : (this.currentPool || 'trb1'); - - // Get pool races from cached raceCardData; fallback to hard-coded map - const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}'); - const poolRaces = raceCardData?.raceVenueRaces?.pools?.[poolName] || []; - 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] || []); - const baseRaceIdx = raceIndices.length > 0 ? raceIndices[0] : 1; - - // Expand each leg: expand 'F' to actual horse numbers for that leg, normalize numeric tokens to two digits - const normalizeToken = (tok: string | number) => { - const s = String(tok); - return /^\d+$/.test(s) ? s.padStart(2, '0') : s; - }; - - const expandedLegs: string[] = legs.map((leg, i) => { - const raceIdx = raceIndices.length > i ? raceIndices[i] - 1 : (baseRaceIdx - 1 + i); - const expanded = leg.flatMap((n) => { - if (n === 'F') { - // expand to horse numbers for that race index - return this.getHorseNumbersForRaceIdx(raceIdx).map(num => String(num)); + if (row.label === 'TBP' && this.currentPool) { + displayLabel = this.currentPool; } - return [n]; - }); - // remove any stray '-' and '#', normalize numeric tokens to two digits, then join by ',' - return expanded - .filter(n => n !== '-' && n !== '#') - .map(normalizeToken) - .join(','); - }); - // Join legs with '/' (no extra spaces to match your desired output) - const numbersStr = expandedLegs.join('/'); + if (['TBP', 'MJP', 'JPP'].includes(row.label)) { + 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); - // Label: always show TBP (uppercase) for TBP rows; for MJP/JPP leave as-is - const labelForDisplay = row.label === 'TBP' ? 'TBP' : displayLabel; + const poolName = row.label === 'MJP' ? 'MJP1' : row.label === 'JPP' ? 'JPP1' : (this.currentPool || 'TBP1'); + const poolKey = this.normalizePoolNameToKey(poolName) || poolName; + const raceIndices = this.getRaceIndicesForPoolExact(poolKey); + const baseRaceIdx = raceIndices.length > 0 ? raceIndices[0] : null; - // Value: zero-pad tickets to 3 digits like *007 - const valueStr = `*${padTicketCountToThree(row.value || 0)}`; + const normalizeToken = (tok: string | number) => { + const s = String(tok); + return /^\d+$/.test(s) ? s.padStart(2, '0') : s; + }; - // Build line: e.g. "TBP 01,02/02,03/02,03*007" - return `${labelForDisplay} ${numbersStr}${valueStr}`; -} + const expandedLegs: string[] = legs.map((leg, i) => { + const raceNo = baseRaceIdx !== null ? (raceIndices.length > i ? raceIndices[i] : (baseRaceIdx + i)) : null; + const expanded = leg.flatMap((n) => { + if (n === 'F' && raceNo !== null) { + return this.getHorseNumbersForRaceIdx(raceNo).map(num => String(num)); + } + return [n]; + }); + return expanded.filter(n => n !== '-' && n !== '#').map(normalizeToken).join(','); + }); + const numbersStr = expandedLegs.join('/'); + const labelForDisplay = row.label === 'TBP' ? 'TBP' : displayLabel; + const valueStr = `*${String(row.value || 0).padStart(3, '0')}`; + return `${labelForDisplay} ${numbersStr}${valueStr}`; + } - // ๐ŸŽ Expand 'F' to full horse numbers for other pools - if (displayNumbers.includes('F')) { - -displayNumbers = displayNumbers.flatMap((n) => { - if (n === 'F') { - // Expand F โ†’ horse numbers, no extra dashes - return this.getHorseNumbersForSelectedRace().map(num => num.toString()); - } - return [n]; // Keep original (including '-') if user entered -}); + if (displayNumbers.includes('F')) { + displayNumbers = displayNumbers.flatMap((n) => { + if (n === 'F') { + return this.getHorseNumbersForSelectedRace().map(num => num.toString()); + } + return [n]; + }); + } + let numbersStr = ''; + if (['FRP', 'QNP'].includes(row.label)) { + const actualNumbers = displayNumbers.filter(n => n !== '#').join(','); + if (row.numbers.includes('#') || row.isBoxed) { + numbersStr = `${actualNumbers} - ${actualNumbers}`; + } else { + numbersStr = actualNumbers; + } + } else { + numbersStr = displayNumbers.filter(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'); - let numbersStr = ''; - - // ๐ŸŽฏ FRP, QNP, TNP logic with box check - if (['FRP', 'QNP'].includes(row.label)) { - // const actualNumbers = displayNumbers.filter(n => n !== '#' && n !== '-').join(','); - const actualNumbers = displayNumbers.filter(n => n !== '#').join(','); - if (row.numbers.includes('#') || row.isBoxed) { // โœ… box condition - numbersStr = `${actualNumbers} - ${actualNumbers}`; - } else { - numbersStr = actualNumbers; + // --- CONSOLE LOG FORMATTING --- + function formatNumbers(numStr: string) { + const legs = numStr.split(/\s*\/\s*/).map(leg => { + const parts = leg.split(',').map(p => p.trim()).filter(Boolean); + return parts.map(p => { + const numeric = p.match(/^\d+$/); + return numeric ? p.padStart(2, '0') : p; + }).join(','); + }); + return legs.join('/'); } - } else { - // ๐Ÿ“ All other pools - // numbersStr = displayNumbers.filter(n => n !== '#' && n !== '-').join(','); - numbersStr = displayNumbers.filter(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'); - -//------------------------------------WNP 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('-----------------------------'); - - - - -// --------------------------------------------CONSOLE LOGS EDITTED FORMATTED ---------------------------------- -// ------------------- Simulated console-ticket printing (REPLACE THIS BLOCK) ------------------- -console.log('--- Simulated Ticket Print ---'); -console.log(`Ticket ID : ${printData.ticketId}`); -console.log(`Barcode ID : ${printData.barcodeId}`); -console.log(`|||||| ||| | ||||| |||| |`); // Dummy barcode -console.log(`WIN Labels :`); - -// helper to pad each horse number in a leg (handles " / " multi-leg separators) -function formatNumbers(numStr: string) { - const legs = numStr.split(/\s*\/\s*/).map(leg => { - const parts = leg.split(',').map(p => p.trim()).filter(Boolean); - return parts.map(p => { - const numeric = p.match(/^\d+$/); - return numeric ? p.padStart(2, '0') : p; - }).join(','); - }); - return legs.join('/'); -} // helper to pad ticket count to 3 digits (001..999) -function padTicketCountToThree(n: number) { - const num = Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0; - return String(num).padStart(3, '0'); -} - -// Build compact entries "LABEL numbers*NNN" -const formattedEntries: string[] = []; -printData.winLabels.split('\n').forEach(line => { - const m = line.match(/^(\S+)\s+(.+?)\s+\*(\d+)\b/); - if (m) { - const label = m[1]; - const rawNums = m[2].trim(); - const value = Number(m[3]) || 0; - const formattedNums = formatNumbers(rawNums); - const valueStr = `*${padTicketCountToThree(value)}`; - formattedEntries.push(`${label} ${formattedNums}${valueStr}`); - } else { - if (line.trim()) formattedEntries.push(line.trim()); - } -}); - - -function compactifyEntry(entry: string) { - const m = entry.match(/^(\S+)\s+(.+?)(\*\d{1,})?$/); - if (!m) return entry; - - const label = m[1]; - let numsPart = (m[2] || '').trim(); - const starPart = m[3] || ''; - - // Normalize whitespace - numsPart = numsPart.replace(/\s+/g, ' ').trim(); - - // Build groups by treating '-' token as a group separator - const rawTokens = numsPart.split(',').map(s => s.trim()); - - const groups: string[][] = []; - let currentGroup: string[] = []; - - for (const tok of rawTokens) { - if (!tok || tok === '#') { - // skip stray empty / '#' tokens - continue; + function padTicketCountToThree(n: number) { + const num = Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0; + return String(num).padStart(3, '0'); } - if (tok === '-') { - // push current group (even if empty) and start new group + + function compactifyEntry(entry: string) { + const m = entry.match(/^(\S+)\s+(.+?)(\*\d{1,})?$/); + if (!m) return entry; + + const label = m[1]; + let numsPart = (m[2] || '').trim(); + const starPart = m[3] || ''; + + numsPart = numsPart.replace(/\s+/g, ' ').trim(); + + const rawTokens = numsPart.split(',').map(s => s.trim()); + const groups: string[][] = []; + let currentGroup: string[] = []; + + for (const tok of rawTokens) { + if (!tok || tok === '#') continue; + if (tok === '-') { + groups.push(currentGroup); + currentGroup = []; + continue; + } + currentGroup.push(tok); + } groups.push(currentGroup); - currentGroup = []; - continue; + + const normalizeToken = (tok: string) => { + const num = tok.match(/^\d+$/); + return num ? tok.padStart(2, '0') : tok; + }; + + const filteredGroups = groups + .map(g => g.filter(t => t && t !== '-' && t !== '#')) + .filter((g, idx) => !(g.length === 0 && groups.length === 1)); + + if (filteredGroups.length > 1) { + const groupStrs = filteredGroups.map(g => g.map(normalizeToken).join(',')); + return `${label} ${groupStrs.join('-')}${starPart}`; + } + + const singleTokens = filteredGroups[0] || []; + if (singleTokens.length >= 2 && singleTokens.length % 2 === 0) { + const half = singleTokens.length / 2; + const firstHalf = singleTokens.slice(0, half).map(normalizeToken).join(','); + const secondHalf = singleTokens.slice(half).map(normalizeToken).join(','); + if (firstHalf === secondHalf) { + return `${label} ${firstHalf}-${secondHalf}${starPart}`; + } + } + + const normalized = singleTokens.map(normalizeToken).join(','); + return `${label} ${normalized}${starPart}`; } - currentGroup.push(tok); - } - // push last group - groups.push(currentGroup); - // helper to normalize token (zero-pad numeric) - const normalizeToken = (tok: string) => { - const num = tok.match(/^\d+$/); - return num ? tok.padStart(2, '0') : tok; - }; + const printData = { + ticketId, + barcodeId, + venue, + date: `${day}-${month}-${fullYear}`, + winLabels, + ticketCount, + totalAmount, + gstNumber: '29ABCDE1234F2Z5' + }; - // Filter out groups that became empty due to stray tokens, but preserve intentional empties if user had them. - const filteredGroups = groups - .map(g => g.filter(t => t && t !== '-' && t !== '#')) - .filter((g, idx) => !(g.length === 0 && groups.length === 1)); // keep single empty group only if it was the only group + console.log('--- Simulated Ticket Print ---'); + console.log(`Ticket ID : ${printData.ticketId}`); + console.log(`Barcode ID : ${printData.barcodeId}`); + console.log(`|||||| ||| | ||||| |||| |`); + console.log(`WIN Labels :`); - // If there are multiple groups (from '-' markers), format them joined by hyphen - if (filteredGroups.length > 1) { - const groupStrs = filteredGroups.map(g => g.map(normalizeToken).join(',')); - return `${label} ${groupStrs.join('-')}${starPart}`; - } + let formattedEntries: string[] = []; + printData.winLabels.split('\n').forEach(line => { + const m = line.match(/^(\S+)\s+(.+?)\s+\*(\d+)\b/); + if (m) { + const label = m[1]; + const rawNums = m[2].trim(); + const value = Number(m[3]) || 0; + const formattedNums = formatNumbers(rawNums); + const valueStr = `*${padTicketCountToThree(value)}`; + formattedEntries.push(`${label} ${formattedNums}${valueStr}`); + } else { + if (line.trim()) formattedEntries.push(line.trim()); + } + }); - // No explicit '-' groups (single group only). fallback to duplicated-halves detection - const singleTokens = filteredGroups[0] || []; - if (singleTokens.length >= 2 && singleTokens.length % 2 === 0) { - const half = singleTokens.length / 2; - const firstHalf = singleTokens.slice(0, half).map(normalizeToken).join(','); - const secondHalf = singleTokens.slice(half).map(normalizeToken).join(','); - if (firstHalf === secondHalf) { - return `${label} ${firstHalf}-${secondHalf}${starPart}`; + let formattedWinLabels = formattedEntries.map(compactifyEntry).join(''); + if (!formattedWinLabels) { + formattedWinLabels = printData.winLabels || '(no win labels)'; } - } - // Final fallback: normalized comma-joined tokens - const normalized = singleTokens.map(normalizeToken).join(','); - return `${label} ${normalized}${starPart}`; -} + const formattedTotal = `โ‚น${printData.totalAmount}`; + console.log(formattedWinLabels); + console.log(formattedTotal); + console.log(`GST Number : ${printData.gstNumber}`); + console.log(`Date/Time : ${now.toLocaleString()}`); + console.log('-----------------------------'); + // --- BACKEND COMMIT --- + const ticketParts = (printData.ticketId || '').split('/'); + const raceVenue = ticketParts[0] || 'MYS'; + const dateStr = ticketParts[1] || ''; + const raceDt = dateStr && dateStr.length === 8 + ? `${dateStr.slice(0,4)}/${dateStr.slice(4,6)}/${dateStr.slice(6,8)}` + : (new Date()).toISOString().slice(0,10).replace(/-/g,'/'); + const raceNumRaw = (ticketParts[2] || '').trim(); + let raceNum = '01'; -// Apply compactify to each entry and join into one string -let formattedWinLabels = formattedEntries.map(compactifyEntry).join(''); - -// Fallback if nothing parsed -if (!formattedWinLabels) { - formattedWinLabels = printData.winLabels || '(no win labels)'; -} - -// Show formatted result and totals -const formattedTotal = `โ‚น${printData.totalAmount}`; -console.log(formattedWinLabels); -console.log(formattedTotal); -console.log(`GST Number : ${printData.gstNumber}`); -console.log(`Date/Time : ${now.toLocaleString()}`); -console.log('-----------------------------'); - -// ---------------------------------- CONSOLE LOG ENDS HERE --------------------------------------------------------- - - - -//----------------------------------------SENDIND DATA TO BACKEND ---------------------------------------------------- -// Build the commit payload as before (btId, raceVenue, raceDt, raceNum, betInfo, nHorseBits, tktVal) -const ticketParts = (printData.ticketId || '').split('/'); -const raceVenue = ticketParts[0] || 'MYS'; -const dateStr = ticketParts[1] || ''; // "20250907" -const raceDt = dateStr && dateStr.length === 8 - ? `${dateStr.slice(0,4)}/${dateStr.slice(4,6)}/${dateStr.slice(6,8)}` - : (new Date()).toISOString().slice(0,10).replace(/-/g,'/'); -// const raceNumRaw = ticketParts[2] || '1'; -// const raceNum = String(Number(raceNumRaw)).padStart(2,'0'); -// --- with this improved extraction (keeps safe fallbacks) --- -const raceNumRaw = (ticketParts[2] || '').trim(); // e.g. "jkp1", "mjp1", "trb2" -let raceNum = '01'; // default/fallback - -const sufMatch = raceNumRaw.match(/^([a-zA-Z]+)(\d+)$/); -if (sufMatch) { - const prefix = sufMatch[1].toLowerCase(); // "jkp", "mjp", "trb" - const num = sufMatch[2]; // "1", "2", ... - // map known prefixes to desired single-letter codes - const prefixMap: Record = { - jkp: 'J', - mjp: 'M', - trb: 'T' - // add more mappings if you have other station codes - }; - const letter = prefixMap[prefix] ?? prefix.charAt(0).toUpperCase(); - raceNum = `${letter}${num}`; // "J1", "M1", "T2", ... -} else { - // if suffix was purely numeric, preserve old numeric behavior (zero-padded) - const asNum = Number(raceNumRaw); - if (Number.isFinite(asNum) && raceNumRaw !== '') { - raceNum = String(Math.floor(asNum)).padStart(2, '0'); // "01", "02", etc. - } else { - // fallback stays '01' (change if you prefer different fallback) - raceNum = '01'; - } -} -const btId_bc = (printData.barcodeId || '0000').toString().slice(0,4); -const betInfo = formattedWinLabels || printData.winLabels || ''; -const nHorseBits = [0,0,0,0,0,0,0,0]; -const tktVal = Number(printData.totalAmount) || 0; - -const commitPayload = { - btMake: "I", - btId: btId_bc, - raceVenue: raceVenue, - raceDt: raceDt, - raceNum: raceNum, - betInfo: betInfo, - nHorseBits: nHorseBits, - moneyTyp: "C", - tktVal: tktVal, - repeatFlag: "N" -}; - -console.log('โžก๏ธ Sending commit to /isr/commit:', commitPayload); - -let commitJson = null; -try { - const commitResp = await fetch('http://localhost:8084/isr/commit', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(commitPayload), - // optionally set a timeout by using AbortController if you need it - }); - - // attempt to parse JSON safely - const txt = await commitResp.text(); - try { - commitJson = txt ? JSON.parse(txt) : null; - } catch (e) { - console.warn('โš ๏ธ /isr/commit returned non-JSON response:', txt); - commitJson = null; - } - - // If HTTP status not OK, treat as failure - if (!commitResp.ok) { - console.error('โŒ /isr/commit HTTP error', commitResp.status, commitJson ?? txt); - this.cdr.markForCheck(); // <-- Ensure UI updates on error - return; // stop โ€” don't print - } - if (!(commitJson && commitJson.success === true)) { - console.error('โŒ /isr/commit failed or returned success=false:', commitJson); - if (commitJson && commitJson.message) { - console.error('Server message:', commitJson.message); + const sufMatch = raceNumRaw.match(/^([a-zA-Z]+)(\d+)$/); + if (sufMatch) { + const prefix = sufMatch[1].toLowerCase(); + const num = sufMatch[2]; + const prefixMap: Record = { + jkp: 'J', + mjp: 'M', + trb: 'T', + tbp: 'T' + }; + const letter = prefixMap[prefix] ?? prefix.charAt(0).toUpperCase(); + raceNum = `${letter}${num}`; + } else { + const asNum = Number(raceNumRaw); + if (Number.isFinite(asNum) && raceNumRaw !== '') { + raceNum = String(Math.floor(asNum)).padStart(2, '0'); + } } - this.cdr.markForCheck(); // <-- Ensure UI updates on error - return; // do not proceed to printing - } - // If we reach here, commit succeeded - console.log('โœ… /isr/commit success:', commitJson); - this.erase(); // <-- Clear selections after successful print -// ๐Ÿ‘‰ Save whole response into localStorage under key "issueT" -try { - localStorage.setItem('issueT', JSON.stringify(commitJson)); - console.log('Saved commitJson into localStorage key: issueT'); - localStorage.setItem('cancelT', JSON.stringify(commitJson)); -} catch (e) { - console.warn('Failed to save issueT:', e); -} -} catch (error) { - console.error("โŒ Print failed:", error); - this.cdr.markForCheck(); // <-- Ensure UI updates on error - // Optionally, decide whether to clear selections on failure - // this.erase(); // Uncomment if you want to clear on error -} + const btId_bc = (printData.barcodeId || '0000').toString().slice(0,4); + const betInfo = formattedWinLabels || printData.winLabels || ''; + const nHorseBits = [0,0,0,0,0,0,0,0]; + const tktVal = Number(printData.totalAmount) || 0; -//---------------------------------------------- BACK END ENDS HERE ----------------------------------------------------- - // read issueT from localStorage -let issueT: any = null; -try { - const raw = localStorage.getItem('issueT'); - issueT = raw ? JSON.parse(raw) : null; -} catch (e) { - console.warn('Could not parse issueT from localStorage:', e); -} + const commitPayload = { + btMake: "I", + btId: btId_bc, + raceVenue: raceVenue, + raceDt: raceDt, + raceNum: raceNum, + betInfo: betInfo, + nHorseBits: nHorseBits, + moneyTyp: "C", + tktVal: tktVal, + repeatFlag: "N" + }; -// fallback to printData.barcodeId if issueT is missing -const barcodeIdToUse = issueT?.tktNumUsed ?? printData.barcodeId; + console.log('โžก๏ธ Sending commit to /isr/commit:', commitPayload); -const payload = { - type: 'ticket', - ticketId: printData.ticketId, - barcodeId: barcodeIdToUse, // โœ… use tktNumUsed from issueT - winLabels: printData.winLabels, - ticketCount: printData.ticketCount, - totalAmount: printData.totalAmount, - gstNumber: printData.gstNumber, - dateTime: now.toLocaleString() -}; + let commitJson = null; + try { + const commitResp = await fetch('http://localhost:8084/isr/commit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(commitPayload) + }); -console.log('Printer payload:', payload); + const txt = await commitResp.text(); + try { + commitJson = txt ? JSON.parse(txt) : null; + } catch (e) { + console.warn('โš ๏ธ /isr/commit returned non-JSON response:', txt); + commitJson = null; + } + + if (!commitResp.ok) { + console.error('โŒ /isr/commit HTTP error', commitResp.status, commitJson ?? txt); + this.cdr.markForCheck(); + return; + } + if (!(commitJson && commitJson.success === true)) { + console.error('โŒ /isr/commit failed or returned success=false:', commitJson); + if (commitJson && commitJson.message) { + console.error('Server message:', commitJson.message); + } + this.cdr.markForCheck(); + return; + } + console.log('โœ… /isr/commit success:', commitJson); + try { + localStorage.setItem('issueT', JSON.stringify(commitJson)); + console.log('Saved commitJson into localStorage key: issueT'); + localStorage.setItem('cancelT', JSON.stringify(commitJson)); + } catch (e) { + console.warn('Failed to save issueT:', e); + } + } catch (error) { + console.error("โŒ Print failed:", error); + this.cdr.markForCheck(); + return; + } + + let issueT: any = null; + try { + const raw = localStorage.getItem('issueT'); + issueT = raw ? JSON.parse(raw) : null; + } catch (e) { + console.warn('Could not parse issueT from localStorage:', e); + } + + const barcodeIdToUse = issueT?.tktNumUsed ?? printData.barcodeId; + + const payload = { + type: 'ticket', + ticketId: printData.ticketId, + barcodeId: barcodeIdToUse, + winLabels: printData.winLabels, + ticketCount: printData.ticketCount, + totalAmount: printData.totalAmount, + gstNumber: printData.gstNumber, + dateTime: now.toLocaleString() + }; + + console.log('Printer payload:', payload); fetch('http://localhost:9100/print', { @@ -1617,90 +1407,81 @@ console.log('Printer payload:', payload); // ---------------------sending data to backend --------------------------------- - try { - const existingTicketsStr = localStorage.getItem('localTickets'); - const existingTickets = existingTicketsStr ? JSON.parse(existingTicketsStr) : []; + 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); + } - 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); -} + try { + const existingTicketsStr = localStorage.getItem('localTicketsViewlog'); + const existingTickets = existingTicketsStr ? JSON.parse(existingTicketsStr) : []; + if (existingTickets.length >= 10) { + existingTickets.shift(); + } + existingTickets.push(payload); + 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).'); - localStorage.setItem('canceltickets', JSON.stringify([payload])); -} catch (error) { - console.error('โŒ Failed to store ticket locally:', error); -} + try { + localStorage.setItem('localTicketsnew', JSON.stringify([payload])); + console.log('๐Ÿ“ฆ [DEBUG] Latest ticket stored in localStorage (previous cleared).'); + localStorage.setItem('canceltickets', JSON.stringify([payload])); + } 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) -}) + 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 - }) + .then(response => { + if (!response.ok) { + throw new Error(`Printer error: ${response.status}`); + } + return response.text(); + }) + .then(result => { + console.log("โœ… Print successful:", result); + this.erase(); + }) + .catch(error => { + console.error("โŒ Print failed:", error); + this.erase(); + }); - .catch(error => { - console.error("โŒ Print failed:", error); - this.erase(); // โœ… Clear selections after successful print - }); + this.erase(); + } - this.erase(); // โœ… Clear selections after successful print - - //--------------------Ended Print here ---------------------------- - - this.selectionService.finalizeCurrentRow(); - // Call after finalizeCurrentRow - this.refreshBlockedLabels(null); + clearLocalTickets() { + localStorage.removeItem('localTickets'); + console.log('๐Ÿงน localTickets cleared from localStorage'); + localStorage.removeItem('printClickCount'); + console.log('๐Ÿงผ printClickCount cleared from localStorage'); + this.sharedStateService.setSalesTotal(0); + this.sharedStateService.setReceiveTotal(0); + this.selectionService.clearSelections(); this.resetSelections(); } - + //----------------------------------PRINT ENDS HERE ------------------------------------------------------ erase() { this.selectionService.clearSelections(); this.resetSelections(); this.refreshBlockedLabels(null); - this.cdr.markForCheck(); // <-- Force UI update + this.cdr.markForCheck(); } resetSelections() { @@ -1729,7 +1510,7 @@ try { this.refreshBlockedLabels(null); this.updateCanPrint(); this.sharedStateService.updateSharedData({ type: 'multiLegPoolEnd', value: null }); - this.cdr.markForCheck(); // <-- Force UI update + this.cdr.markForCheck(); } toggleBoxMode() { @@ -1814,45 +1595,36 @@ try { this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] }); } - private getMaxLegs(poolName: string): number { - const p = (this.normalizePoolName(poolName) || '').toLowerCase(); - switch (p) { - case 'mjp1': case 'mjp2': return 4; - case 'jkp1': case 'jkp2': return 5; - case 'trb1': case 'trb2': return 3; - default: return 3; - } + private getMaxLegs(poolKey: string): number { + const key = this.normalizePoolNameToKey(poolKey) || poolKey; + const indices = this.getRaceIndicesForPoolExact(key); + return indices.length; } private getRaceForLeg(poolName: string, leg: number): number { - const poolKey = this.normalizePoolName(poolName) || poolName || ''; - const poolRaces: number[] = this.structuredRaceCard?.pools?.[poolKey] || []; - if (poolRaces.length > leg) return poolRaces[leg]; - const comboRaces: number[] = this.structuredRaceCard?.comboRaces?.[poolKey] || []; - if (comboRaces.length > leg) return comboRaces[leg]; - return (this.multiLegBaseRaceIdx || 1) + leg; + const key = this.normalizePoolNameToKey(poolName) || poolName; + const rn = this.getRaceForLegExact(key, leg); + return rn ?? 0; } private updateLegRaceDisplay(poolName: string) { - const poolKey = this.normalizePoolName(poolName) || ''; - if (!['mjp1', 'jkp1', 'trb1', 'trb2', 'mjp2', 'jkp2'].includes(poolKey)) { + const poolKey = this.normalizePoolNameToKey(poolName) || poolName; + const raceNo = this.getRaceForLegExact(poolKey, this.multiLegStage); + if (!raceNo) { this.currentLegRaceDisplay = ''; - this.currentPool = null; return; } - const raceIdx = this.getRaceForLeg(poolKey, 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 }); + this.currentLegRaceDisplay = `Leg ${this.multiLegStage + 1} (Race ${raceNo})`; + const runnerCount = this.getRunnerCountForLegExact(raceNo); + if (runnerCount) this.sharedStateService.setRunnerCount(runnerCount); + this.sharedStateService.updateSharedData({ type: 'currentLegRace', value: raceNo }); } private getRunnerCountForLeg(baseIdx: number, leg: number): number { - const races = this.structuredRaceCard?.raceVenueRaces?.races || []; - const raceIdx = this.getRaceForLeg(this.currentPool || '', leg) - 1; - const race = races[raceIdx]; - if (race?.horses && Array.isArray(race.horses)) return race.horses.length; - return 12; + if (!this.currentPool) return 0; + const poolKey = this.normalizePoolNameToKey(this.currentPool) || this.currentPool; + const raceNo = this.getRaceForLegExact(poolKey, leg); + return this.getRunnerCountForLegExact(raceNo); } private handleFieldForSpecialLabels() { @@ -1916,11 +1688,11 @@ try { if (this.totalAmountLimitReached) return; if (['FRP', 'QNP', 'TNP'].includes(this.selectedLabel || '') || this.multiLegLabels.includes(this.selectedLabel || '')) { this.handleFieldForSpecialLabels(); - } else { - this.fieldModalOpen = true; - this.fieldInput = ''; - this.fieldFEntered = false; + return; } + this.fieldModalOpen = true; + this.fieldInput = ''; + this.fieldFEntered = false; } closeFieldModal() { this.fieldModalOpen = false; } @@ -1936,13 +1708,15 @@ try { this.fieldFEntered = true; this.fieldInput = 'F'; } - } else { + return; + } + if (/[0-9]/.test(key)) { this.fieldInput += key; } } confirmFieldEntry() { - if (!this.fieldFEntered || !this.selectedLabel) return; + if (!this.selectedLabel) return; if (this.selectedLabel === 'FRP' || this.selectedLabel === 'QNP') { if (!this.isFirstGroupComplete) { @@ -1953,7 +1727,11 @@ try { this.selectedNumbers = [...this.firstGroup, '-', 'F']; } this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false }); - } else if (this.selectedLabel === 'TNP') { + this.closeFieldModal(); + return; + } + + if (this.selectedLabel === 'TNP') { if (this.isBoxed) { this.selectedNumbers = ['F']; this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: true }); @@ -1965,14 +1743,38 @@ try { this.selectedNumbers = combined; this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false }); } - } else if (this.multiLegLabels.includes(this.selectedLabel)) { + this.closeFieldModal(); + return; + } + + 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.closeFieldModal(); + return; + } + + if (this.fieldFEntered) { this.selectedNumbers = ['F']; this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: false }); + this.closeFieldModal(); + return; } + + const num = Number(this.fieldInput); + if (!Number.isFinite(num)) { + console.warn('[FIELD] invalid numeric entry:', this.fieldInput); + return; + } + + if (!this.actualRunners.has(num)) { + console.warn('[FIELD] number not available on touch-pad for this race/leg:', num); + return; + } + + this.selectedNumbers = [num]; + this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false }); this.closeFieldModal(); } @@ -2007,9 +1809,7 @@ try { this.fieldFEntered = false; this.wspTicketStage = 0; this.poolReplaceOpen = false; - // Update selection service with new label this.selectionService.updatePartial({ label }); - // recompute blocked labels including newly-selected label this.refreshBlockedLabels(label); } @@ -2021,40 +1821,74 @@ try { } private _selectTreAfterPopup(btnNum: number) { - this.selectedLabel = 'TBP'; - 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 = [[], [], [], [], []]; + // Initialize default state + this.selectedLabel = 'TBP'; + 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 button to pool - const poolInfo = btnNum === 2 ? { name: 'trb2' } : { name: 'trb1' }; - this.currentPool = poolInfo.name; - this.multiLegBaseRaceIdx = this.getBaseRaceIndexForPool(poolInfo.name); + // Map button to pool and normalize + const poolInfo = btnNum === 2 ? { name: 'trb2' } : { name: 'trb1' }; + const poolKey = this.normalizePoolNameToKey(poolInfo.name); - this.sharedStateService.updateSharedData({ - type: 'multiLegPoolStart', - value: { label: poolInfo.name, baseRaceIdx: this.multiLegBaseRaceIdx } - }); - - this.updateLegRaceDisplay(poolInfo.name); - this.selectionService.updatePartial({ label: 'TBP' }); - - 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(); - // Call after pool change - this.refreshBlockedLabels('TBP'); + // Handle invalid pool key + if (!poolKey) { + console.warn(`[DEBUG] Invalid pool key for ${poolInfo.name}`); + this.selectedLabel = null; + this.currentPool = null; + this.multiLegBaseRaceIdx = 0; + this.currentLegRaceDisplay = ''; + this.selectionService.updatePartial({ label: '', numbers: [], value: 0, total: 0, isBoxed: false }); + this.sharedStateService.updateSharedData({ type: 'multiLegPoolEnd', value: null }); + this.refreshBlockedLabels(null); + this.cdr.markForCheck(); + return; } + // Get base race index and handle null case + const baseRaceIdx = this.getBaseRaceIndexForPoolExact(poolKey); + if (baseRaceIdx === null) { + console.warn(`[DEBUG] No base race index found for pool ${poolKey}`); + this.selectedLabel = null; + this.currentPool = null; + this.multiLegBaseRaceIdx = 0; + this.currentLegRaceDisplay = ''; + this.selectionService.updatePartial({ label: '', numbers: [], value: 0, total: 0, isBoxed: false }); + this.sharedStateService.updateSharedData({ type: 'multiLegPoolEnd', value: null }); + this.refreshBlockedLabels(null); + this.cdr.markForCheck(); + return; + } + + // Set up multi-leg pool + this.currentPool = poolKey; + this.multiLegBaseRaceIdx = baseRaceIdx; + + // Notify shared state of pool start + this.sharedStateService.updateSharedData({ + type: 'multiLegPoolStart', + value: { label: poolKey, baseRaceIdx: this.multiLegBaseRaceIdx } + }); + + // Update UI and state + this.updateLegRaceDisplay(poolKey); + this.selectionService.updatePartial({ label: 'TBP' }); + this.actualRunners = this.getActualRunnersForCurrentPoolLeg(); + this.runnerCount = this.getRunnerCountForLegExact(this.getRaceForLegExact(poolKey, 0)) || 12; + this.numbers = Array.from({ length: 30 }, (_, i) => i + 1); + this.numbersFlat = this.numberRows.flat(); + this.refreshBlockedLabels('TBP'); + this.cdr.markForCheck(); +} + closeTrePopup() { this.trePopupVisible = false; } closeLimitPopup() { this.showLimitPopup = false; }