564 lines
16 KiB
TypeScript
Executable File
564 lines
16 KiB
TypeScript
Executable File
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;
|
||
}
|
||
}
|