import { Component, OnInit, HostListener, OnDestroy, NgZone } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BtcService } from '../../service/btc.service'; import { Router } from '@angular/router'; import { catchError, interval, of, Subscription, switchMap } from 'rxjs'; import { SharedStateService } from '../../service/shared-state.service'; @Component({ selector: 'app-navbar', templateUrl: './navbar.component.html', styleUrls: ['./navbar.component.css'], standalone: true, imports: [CommonModule], }) export class NavbarComponent implements OnInit, OnDestroy { dateTime: string = ''; isMenuOpen: boolean = false; screenWidth: number = window.innerWidth; private subscription!: Subscription; userName: string = ''; btid: string | null = null; liveStatusOk: boolean = true; showVenueModal = false; showRaceModal = false; selectedVenue = 'Select Venue'; selectedRace: number = 1; currentLegRaceDisplay: string = ''; // Display current leg's race or pool start showWalletModal = false; showResultModal = false; showMessagesModal = false; showLogModal = false; raceCardData: any = {}; raceData: any[] = []; objectKeys = Object.keys; selectedRaceId: number = 0; enabledHorseNumbers: number[] = []; multiLegBaseRaceIdx: number = 0; currentPool: string | null = null; private prevEnabledKey = ''; // For memoization wallet = { withdraw: 0, deposit: 0, payout: 0, cancel: 0, ticketing: 0, balance: 0, }; logs = [{ description: '', venue: '', ticketNumber: '', poolName: '', totalAmount: '', }]; messages: string[] = [ 'System ready.', 'Please select a venue.', 'Races updated.', 'Live status stable.', ]; constructor( private btcService: BtcService, private router: Router, private sharedStateService: SharedStateService, private zone: NgZone // <-- Add NgZone ) {} ngOnInit() { this.userName = localStorage.getItem('userName') || ''; this.btid = localStorage.getItem('btid'); // Use NgZone to run setInterval outside Angular's change detection this.zone.runOutsideAngular(() => { setInterval(() => { this.zone.run(() => this.updateDateTime()); }, 1000); }); this.subscription = interval(5000) .pipe( switchMap(() => this.btcService.pingLiveStatus().pipe( catchError((error) => { console.error('[LIVE STATUS] Ping failed:', error); this.liveStatusOk = false; return of(null); }) )) ).subscribe((response) => { if (response !== null) { console.log('[LIVE STATUS] OK'); this.liveStatusOk = true; } }); this.sharedStateService.sharedData$.subscribe(data => { if (data.type === 'currentLegRace') { this.selectedRace = data.value; if (this.currentPool) { const leg = this.getLegIndexForRace(this.currentPool, data.value); this.currentLegRaceDisplay = `Leg ${leg + 1} (Race ${data.value}) for ${this.currentPool}`; this.updateEnabledHorseNumbersForMultiLeg(this.multiLegBaseRaceIdx); } else { this.currentLegRaceDisplay = ''; this.updateEnabledHorseNumbers(); } } if (data.type === 'multiLegPoolStart') { const { label, baseRaceIdx } = data.value; this.currentPool = label; this.multiLegBaseRaceIdx = baseRaceIdx; this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${label}`; this.updateEnabledHorseNumbersForMultiLeg(baseRaceIdx); console.log(`[Multi-leg Pool] Selected: ${label}, Base Race: ${baseRaceIdx}`); } if (data.type === 'multiLegPoolEnd') { this.currentPool = null; this.multiLegBaseRaceIdx = 0; this.currentLegRaceDisplay = ''; this.updateEnabledHorseNumbers(); } if (data.type === 'selectedRace') { this.currentPool = null; this.multiLegBaseRaceIdx = 0; this.currentLegRaceDisplay = ''; this.selectedRace = data.value; this.updateEnabledHorseNumbers(); } }); } private getLegIndexForRace(poolName: string, race: number): number { 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]?.indexOf(race) ?? 0; } private updateEnabledHorseNumbersForMultiLeg(baseRaceIdx: number) { const raceCardData = this.raceCardData?.raceVenueRaces?.races || []; const legCount = this.getLegCountForLabel(); const key = `${this.currentPool}-${baseRaceIdx}-${legCount}`; if (this.prevEnabledKey === key) return; // 🧠 Memoization this.prevEnabledKey = key; let combinedHorseNumbers: number[] = []; const raceIndices = Array.from({ length: legCount }, (_, i) => this.getRaceForLeg(this.currentPool || '', i) - 1 ); for (const raceIdx of raceIndices) { const race = raceCardData[raceIdx] || []; if (Array.isArray(race)) { const horses = race .map((runner: any) => runner?.horseNumber) .filter((n: number) => typeof n === 'number' && n >= 1 && n <= 30); combinedHorseNumbers.push(...horses); } } this.enabledHorseNumbers = Array.from(new Set(combinedHorseNumbers)); this.sharedStateService.updateSharedData({ type: 'enabledHorseNumbers', value: this.enabledHorseNumbers, }); console.log('[Multi-leg Pool] Updated enabled horse numbers:', this.enabledHorseNumbers); } private getLegCountForLabel(): number { switch (this.currentPool) { case 'mjp1': return 4; case 'jkp1': return 5; case 'trb1': case 'trb2': return 3; default: return 3; } } private getRaceForLeg(poolName: string, leg: number): number { 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); } updateDateTime() { const now = new Date(); this.dateTime = now.toLocaleString(); } @HostListener('window:resize', ['$event']) onResize(event: any) { this.screenWidth = event.target.innerWidth; if (this.screenWidth > 800) { this.isMenuOpen = false; } } toggleMenu() { this.isMenuOpen = !this.isMenuOpen; } openVenueModal() { const cachedData = localStorage.getItem('raceCardData'); if (cachedData) { this.raceCardData = JSON.parse(cachedData); // console.log('📦 Loaded race card from localStorage:', this.raceCardData); // comment out for perf this.selectedVenue = this.raceCardData?.Venue || 'Select Venue'; this.updateEnabledHorseNumbers(); } else { this.raceCardData = { raceVenueRaces: { races: [] }, pools: {} }; // console.warn('⚠️ No race card data found in localStorage.'); // comment out for perf } console.log('[MODAL] Opening venue modal'); this.showVenueModal = true; } openRaceModal() { console.log('[MODAL] Opening race modal'); this.showRaceModal = true; const venueIndex = Object.keys(this.raceCardData?.raceVenueRaces?.races || []) .findIndex((_, idx) => idx === this.selectedRaceId); if (venueIndex !== -1) { this.raceData = this.raceCardData.raceVenueRaces.races[venueIndex] || []; } } closeModals() { console.log('[MODAL] Closing all modals'); this.showVenueModal = false; this.showRaceModal = false; this.showWalletModal = false; this.showResultModal = false; this.showMessagesModal = false; this.showLogModal = false; } selectVenue(index: number) { const venue = this.raceCardData?.Venue || 'Unknown Venue'; this.selectedVenue = venue; this.selectedRaceId = index; this.sharedStateService.updateSharedData({ type: 'selectedVenue', value: this.selectedVenue, }); console.log('[VENUE] Venue resolved to:', this.selectedVenue); this.closeModals(); } selectRace(race: number) { this.selectedRace = race; this.currentPool = null; this.multiLegBaseRaceIdx = 0; this.currentLegRaceDisplay = ''; this.sharedStateService.updateSharedData({ type: 'selectedRace', value: this.selectedRace, }); const raceCard = JSON.parse(localStorage.getItem('raceCardData') || '{}'); const raceList = raceCard?.raceVenueRaces?.races || []; const selectedRaceData = raceList[race - 1] || []; const runnerCount = selectedRaceData.length || 12; this.sharedStateService.setRunnerCount(runnerCount); this.updateEnabledHorseNumbers(); console.log('[RACE] Race selected:', this.selectedRace, '| Runner count:', runnerCount); this.closeModals(); } updateEnabledHorseNumbers() { const raceIndex = this.selectedRace - 1; const race = this.raceCardData?.raceVenueRaces?.races?.[raceIndex]; if (Array.isArray(race)) { this.enabledHorseNumbers = race .map((runner: any) => runner?.horseNumber) .filter((n: any) => typeof n === 'number' && n >= 1 && n <= 30); } else { this.enabledHorseNumbers = []; } this.sharedStateService.updateSharedData({ type: 'enabledHorseNumbers', value: this.enabledHorseNumbers, }); console.log('[HORSE NUMBERS] Enabled horse numbers:', this.enabledHorseNumbers); } selectHorseNumber(number: number) { console.log('[HORSE] Selected horse number:', number); } openWalletModal() { console.log('[MODAL] Opening wallet modal'); this.showWalletModal = true; } openResultModal() { console.log('[MODAL] Opening result modal'); this.showResultModal = true; } openMessagesModal() { console.log('[MODAL] Opening messages modal'); this.showMessagesModal = true; } openLogModal() { console.log('[MODAL] Opening log modal'); this.showLogModal = true; } // formattedTicketLogs: { // label: string; // numbers: number[]; // count: number; // amount: number; // }[] = []; formattedTicketLogs: { pool: string; horses: string; ticketCountLabel: string; price: string; numbers: number[]; count: number; amount: number; }[] = []; // openViewLog() { // const storedTickets = localStorage.getItem('localTicketsViewlog'); // //console.log('Stored Tickets (raw JSON):', storedTickets); // if (storedTickets) { // const tickets = JSON.parse(storedTickets); // // console.log('Parsed Tickets:', tickets); // this.formattedTicketLogs = tickets.map((ticket: any, index: number) => { // const rawLabel = ticket.winLabels || ''; // const numbers = ticket.numbers || []; // const count = ticket.ticketCount || 0; // const amount = ticket.totalAmount || 0; // let pool = '', horses = '', ticketCountLabel = '', price = ''; // // 🧠 Extract parts from rawLabel using RegExp // const labelMatch = rawLabel.match(/^(\w+)\s+([\d,]+)\s+(\*\d+)\s+(Rs\s*\d+)/); // if (labelMatch) { // pool = labelMatch[1]; // horses = labelMatch[2]; // ticketCountLabel = labelMatch[3]; // price = labelMatch[4]; // } // console.log(`--- Ticket ${index + 1} ---`); // console.log('Pool:', pool); // console.log('Horses:', horses); // console.log('Ticket Label:', ticketCountLabel); // console.log('Price:', price); // console.log('Numbers:', numbers); // console.log('Count:', count); // console.log('Amount:', amount); // return { // pool, // horses, // ticketCountLabel, // price, // numbers, // count, // amount // }; // }); // } else { // console.log('No tickets found in localStorage.'); // this.formattedTicketLogs = []; // } // this.showLogModal = true; // console.log('Log modal opened. Final formattedTicketLogs:', this.formattedTicketLogs); // } openViewLog() { const storedTickets = localStorage.getItem('localTicketsViewlog'); if (storedTickets) { const tickets = JSON.parse(storedTickets); this.formattedTicketLogs = tickets.map((ticket: any, index: number) => { const rawLabel = ticket.winLabels?.trim() || ''; const numbers = ticket.numbers || []; const count = ticket.ticketCount || 0; const amount = ticket.totalAmount || 0; let pool = '', horses = '', horsesArray: string[][] = [], ticketCountLabel = '', price = ''; if (rawLabel) { // 1️⃣ Extract pool (first word) const parts = rawLabel.split(/\s+/); pool = parts[0]; // 2️⃣ Extract ticket count (*n) & price if exists const countMatch = rawLabel.match(/\*(\d+)(?:\s+(Rs\s*\d+))?/); if (countMatch) { ticketCountLabel = `*${countMatch[1]}`; price = countMatch[2] || ''; } // 3️⃣ Extract horses part (between pool name & ticket count) const horsesPartMatch = rawLabel.match(/^\w+\s+(.+?)\s+\*\d+/); if (horsesPartMatch) { horses = horsesPartMatch[1].trim(); // Special pools split into races if (['MJP', 'JKP', 'TRE'].includes(pool)) { horsesArray = horses.split('/').map(r => r.trim().split(',').map(h => h.trim()) ); } else { horsesArray = [horses.split(',').map(h => h.trim())]; } } } // console.log(`--- Ticket ${index + 1} ---`); // console.log('Pool:', pool); // console.log('Horses (raw):', horses); // console.log('Horses (array):', horsesArray); // console.log('Ticket Label:', ticketCountLabel); // console.log('Price:', price); // console.log('Numbers:', numbers); // console.log('Count:', count); // console.log('Amount:', amount); return { pool, horses, horsesArray, ticketCountLabel, price, numbers, count, amount }; }); } else { console.log('No tickets found in localStorage.'); this.formattedTicketLogs = []; } this.showLogModal = true; console.log('Log modal opened. Final formattedTicketLogs:', this.formattedTicketLogs); } logout(): void { const name = localStorage.getItem('userName') || 'Unknown User'; const employeeId = localStorage.getItem('employeeId') || '000000'; const printData = { name, employeeId, action: 'logout', type: 'logout' // 👈 This is the missing piece }; console.log('[LOGOUT] Initiating logout with printData:', printData); fetch('http://localhost:9100/print', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(printData), }) .then((res) => { if (!res.ok) throw new Error('Logout print failed'); console.log('[LOGOUT] Print successful'); (window as any).electronAPI?.closeSecondScreen?.(); localStorage.clear(); this.router.navigate(['/logout']); }) .catch((err) => { console.error('[LOGOUT] Error printing:', err); (window as any).electronAPI?.closeSecondScreen?.(); localStorage.clear(); this.router.navigate(['/logout']); }); } // logout(): void { // const name = localStorage.getItem('userName') || 'Unknown User'; // const employeeId = localStorage.getItem('employeeId') || '000000'; // const printData = { name, employeeId, action: 'logout' }; // console.log('[LOGOUT] Initiating logout with printData:', printData); // fetch('http://localhost:9100/print', { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify(printData), // }) // .then((res) => { // if (!res.ok) throw new Error('Logout print failed'); // console.log('[LOGOUT] Print successful'); // (window as any).electronAPI?.closeSecondScreen?.(); // localStorage.clear(); // this.router.navigate(['/logout']); // }) // .catch((err) => { // console.error('[LOGOUT] Error printing:', err); // (window as any).electronAPI?.closeSecondScreen?.(); // localStorage.clear(); // this.router.navigate(['/logout']); // }); // } ngOnDestroy(): void { if (this.subscription) { this.subscription.unsubscribe(); } } // Add trackByHorse for use in *ngFor trackByHorse(index: number, item: number): number { return item; } }