diff --git a/btc-UI/src/app/components/navbar/navbar.component.ts b/btc-UI/src/app/components/navbar/navbar.component.ts
index a36b418..397a6d7 100755
--- a/btc-UI/src/app/components/navbar/navbar.component.ts
+++ b/btc-UI/src/app/components/navbar/navbar.component.ts
@@ -19,20 +19,19 @@ export class NavbarComponent implements OnInit, OnDestroy {
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
+ currentLegRaceDisplay: string = '';
showWalletModal = false;
showResultModal = false;
showMessagesModal = false;
showLogModal = false;
- raceCardData: any = {};
+ raceCardData: any = null;
raceData: any[] = [];
objectKeys = Object.keys;
@@ -41,37 +40,19 @@ export class NavbarComponent implements OnInit, OnDestroy {
multiLegBaseRaceIdx: number = 0;
currentPool: string | null = null;
- private prevEnabledKey = ''; // For memoization
+ private prevEnabledKey = '';
- wallet = {
- withdraw: 0,
- deposit: 0,
- payout: 0,
- cancel: 0,
- ticketing: 0,
- balance: 0,
- };
+ wallet = { withdraw: 0, deposit: 0, payout: 0, cancel: 0, ticketing: 0, balance: 0 };
- logs = [{
- description: '',
- venue: '',
- ticketNumber: '',
- poolName: '',
- totalAmount: '',
- }];
+ logs = [{ description: '', venue: '', ticketNumber: '', poolName: '', totalAmount: '' }];
- messages: string[] = [
- 'System ready.',
- 'Please select a venue.',
- 'Races updated.',
- 'Live status stable.',
- ];
+ 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
+ private zone: NgZone
) {}
ngOnInit() {
@@ -79,30 +60,19 @@ export class NavbarComponent implements OnInit, OnDestroy {
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);
+ 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);
- })
+ 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;
- }
+ if (response !== null) { this.liveStatusOk = true; console.log('[LIVE STATUS] OK'); }
});
-
-
this.sharedStateService.sharedData$.subscribe(data => {
if (data.type === 'currentLegRace') {
this.selectedRace = data.value;
@@ -117,11 +87,11 @@ export class NavbarComponent implements OnInit, OnDestroy {
}
if (data.type === 'multiLegPoolStart') {
const { label, baseRaceIdx } = data.value;
- this.currentPool = label;
+ this.currentPool = this.normalizePoolName(label);
this.multiLegBaseRaceIdx = baseRaceIdx;
- this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${label}`;
+ this.currentLegRaceDisplay = `Starting at Race ${baseRaceIdx} for ${this.currentPool}`;
this.updateEnabledHorseNumbersForMultiLeg(baseRaceIdx);
- console.log(`[Multi-leg Pool] Selected: ${label}, Base Race: ${baseRaceIdx}`);
+ console.log(`[Multi-leg Pool] Selected: ${this.currentPool}, Base Race: ${baseRaceIdx}`);
}
if (data.type === 'multiLegPoolEnd') {
this.currentPool = null;
@@ -137,115 +107,115 @@ export class NavbarComponent implements OnInit, OnDestroy {
this.updateEnabledHorseNumbers();
}
});
+
+ this.loadRaceCardData();
+ }
+
+ private safeGetJSON(key: string): any | null {
+ try {
+ const raw = localStorage.getItem(key);
+ if (!raw) return null;
+ return JSON.parse(raw);
+ } catch (err) {
+ console.warn(`[safeGetJSON] failed parse ${key}`, err);
+ return null;
+ }
+ }
+
+ private loadRaceCardData() {
+ const cached = this.safeGetJSON('rpinfo');
+ if (cached && cached.structuredRaceCard) {
+ this.raceCardData = cached.structuredRaceCard;
+ this.selectedVenue = this.raceCardData?.venue || 'Select Venue';
+ this.updateEnabledHorseNumbers();
+ return;
+ }
+ const rc = this.safeGetJSON('raceCardData');
+ if (rc) {
+ this.raceCardData = rc;
+ this.selectedVenue = this.raceCardData?.venue || 'Select Venue';
+ this.updateEnabledHorseNumbers();
+ return;
+ }
+ this.raceCardData = { error: 'Race card not available locally' };
+ this.selectedVenue = 'Select Venue';
+ }
+
+ private normalizePoolName(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';
+ if (n.startsWith('mjp')) return n.includes('2') ? 'mjp2' : 'mjp1';
+ if (n.startsWith('jkp') || n.startsWith('jpp') || n.startsWith('jk')) return n.includes('2') ? 'jkp2' : 'jkp1';
+ return n;
}
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;
+ if (!this.raceCardData) return 0;
+ const normalized = this.normalizePoolName(poolName) || poolName;
+ const poolMap: { [key: string]: number[] } = this.raceCardData?.pools || {};
+ const arr = poolMap[normalized] || poolMap[poolName] || [];
+ return arr.indexOf(race) >= 0 ? arr.indexOf(race) : 0;
}
private updateEnabledHorseNumbersForMultiLeg(baseRaceIdx: number) {
- const raceCardData = this.raceCardData?.raceVenueRaces?.races || [];
+ const racesArr = this.raceCardData?.raceVenueRaces?.races || [];
const legCount = this.getLegCountForLabel();
- const key = `${this.currentPool}-${baseRaceIdx}-${legCount}`;
-
- if (this.prevEnabledKey === key) return; // π§ Memoization
+ const poolKey = this.currentPool || '';
+ const key = `${poolKey}-${baseRaceIdx}-${legCount}`;
+ if (this.prevEnabledKey === key) return;
this.prevEnabledKey = key;
let combinedHorseNumbers: number[] = [];
-
- const raceIndices = Array.from({ length: legCount }, (_, i) =>
- this.getRaceForLeg(this.currentPool || '', i) - 1
- );
+ const raceIndices = Array.from({ length: legCount }, (_, i) => this.getRaceForLeg(poolKey, 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);
+ const race = racesArr[raceIdx] || {};
+ if (Array.isArray(race.horses)) {
+ const horses = race.horses.filter((n: any) => 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,
- });
-
+ 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;
+ const p = (this.currentPool || '').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 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);
+ const normalized = this.normalizePoolName(poolName) || poolName;
+ const poolMap: { [key: string]: number[] } = this.raceCardData?.pools || {};
+ const arr = poolMap[normalized] || poolMap[poolName] || [];
+ if (arr.length > leg) return arr[leg];
+ return (this.multiLegBaseRaceIdx || 1) + leg;
}
- updateDateTime() {
- const now = new Date();
- this.dateTime = now.toLocaleString();
- }
+ updateDateTime() { this.dateTime = new Date().toLocaleString(); }
@HostListener('window:resize', ['$event'])
onResize(event: any) {
this.screenWidth = event.target.innerWidth;
- if (this.screenWidth > 800) {
- this.isMenuOpen = false;
- }
+ if (this.screenWidth > 800) this.isMenuOpen = false;
}
- toggleMenu() {
- this.isMenuOpen = !this.isMenuOpen;
- }
+ 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;
- }
+ openVenueModal() { this.loadRaceCardData(); 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] || [];
- }
+ const race = this.raceCardData?.raceVenueRaces?.races?.[this.selectedRaceId];
+ this.raceData = race ? (race.horses || []) : [];
}
closeModals() {
@@ -259,16 +229,9 @@ export class NavbarComponent implements OnInit, OnDestroy {
}
selectVenue(index: number) {
- const venue = this.raceCardData?.Venue || 'Unknown Venue';
- this.selectedVenue = venue;
+ this.selectedVenue = this.raceCardData?.venue || 'Unknown Venue';
this.selectedRaceId = index;
-
- this.sharedStateService.updateSharedData({
- type: 'selectedVenue',
- value: this.selectedVenue,
- });
-
- console.log('[VENUE] Venue resolved to:', this.selectedVenue);
+ this.sharedStateService.updateSharedData({ type: 'selectedVenue', value: this.selectedVenue });
this.closeModals();
}
@@ -277,244 +240,103 @@ export class NavbarComponent implements OnInit, OnDestroy {
this.currentPool = null;
this.multiLegBaseRaceIdx = 0;
this.currentLegRaceDisplay = '';
+ this.sharedStateService.updateSharedData({ type: 'selectedRace', value: this.selectedRace });
- 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;
-
+ const raceData = this.raceCardData?.raceVenueRaces?.races?.[race - 1] || {};
+ const runnerCount = Array.isArray(raceData.horses) ? raceData.horses.length : (raceData.runners?.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];
+ const race = this.raceCardData?.raceVenueRaces?.races?.[raceIndex] || {};
+ let horses: any[] = [];
- 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,
- });
+ if (Array.isArray(race?.horses)) horses = race.horses;
+ else if (Array.isArray(race?.runners)) horses = race.runners.map((r: any) => r.number ?? r);
+ else if (Array.isArray(race)) horses = race;
+ this.enabledHorseNumbers = horses.map((n: any) => Number(n)).filter((n: number) => !Number.isNaN(n) && n >= 1 && n <= 30);
+ 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);
- }
+ selectHorseNumber(number: number) { console.log('[HORSE] Selected horse number:', number); }
- openWalletModal() {
- console.log('[MODAL] Opening wallet modal');
- this.showWalletModal = true;
- }
+ openWalletModal() { this.showWalletModal = true; }
+ openResultModal() { this.showResultModal = true; }
+ openMessagesModal() { this.showMessagesModal = true; }
+ openLogModal() { this.showLogModal = true; }
- openResultModal() {
- console.log('[MODAL] Opening result modal');
- this.showResultModal = true;
- }
+ formattedTicketLogs: any[] = [];
- 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;
- horsesArray: string[][];
- ticketCountLabel: string;
- price: string;
- numbers: number[];
- count: number;
- amount: number;
- maskedBarcode: string;
- displayBarcode: string;
-}[] = [];
-
-openViewLog() {
- const storedTickets = localStorage.getItem('localTicketsViewlog');
-
- if (storedTickets) {
+ openViewLog() {
+ const storedTickets = localStorage.getItem('localTicketsViewlog');
+ if (!storedTickets) { this.formattedTicketLogs = []; this.showLogModal = true; return; }
const tickets = JSON.parse(storedTickets);
-
- this.formattedTicketLogs = tickets.map((ticket: any, index: number) => {
+ this.formattedTicketLogs = tickets.map((ticket: any) => {
const rawLabel = ticket.winLabels?.trim() || '';
- const numbers = ticket.numbers || [];
- const count = ticket.ticketCount || 0;
- const amount = ticket.totalAmount || 0;
- const barcodeId = ticket.barcodeId || '';
- let pool = '', horses = '', horsesArray: string[][] = [], ticketCountLabel = '', price = '', maskedBarcode = '' , displayBarcode = '';
+ const barcodeId = ticket.barcodeId || '';
+ let pool = '', horses = '', horsesArray: string[][] = [], ticketCountLabel = '', price = '', maskedBarcode = '', displayBarcode = '';
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)
+ if (countMatch) { ticketCountLabel = `*${countMatch[1]}`; price = countMatch[2] || ''; }
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())
- );
+ if (['MJP', 'JKP', 'TRE', 'trb1', 'trb2', 'mjp1', 'jkp1'].includes(pool)) {
+ horsesArray = horses.split('/').map((r: string) => r.trim().split(',').map(h => h.trim()));
} else {
horsesArray = [horses.split(',').map(h => h.trim())];
}
}
}
- if (barcodeId) {
- const last4 = barcodeId.slice(-4);
+ if (barcodeId) {
+ const last4 = barcodeId.slice(-4);
+ maskedBarcode = btoa(barcodeId.slice(0, -4));
+ displayBarcode = '********' + last4;
+ }
- // Encrypt everything except last4
- const encryptedPart = btoa(barcodeId.slice(0, -4));
-
- // Store masked barcode (if you still need encrypted form)
- maskedBarcode = encryptedPart;
-
- // For GUI β show ******** + last4
- displayBarcode = '********' + last4;
-}
-
-
- console.log(maskedBarcode);
- console.log('Decoded:', atob(maskedBarcode));
-
-
- return {
- pool,
- horses,
- horsesArray,
- ticketCountLabel,
- price,
- numbers,
- count,
- amount,
- maskedBarcode,
- displayBarcode
- };
+ return { pool, horses, horsesArray, ticketCountLabel, price, numbers: ticket.numbers || [], count: ticket.ticketCount || 0, amount: ticket.totalAmount || 0, maskedBarcode, displayBarcode };
});
- } else {
- console.log('No tickets found in localStorage.');
- this.formattedTicketLogs = [];
+
+ this.showLogModal = true;
}
- 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' };
+ console.log('[LOGOUT] Initiating logout with printData:', printData);
-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']);
+ fetch('http://localhost:9100/print', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(printData),
})
- .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']);
- // });
- // }
+ .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();
- }
+ if (this.subscription) this.subscription.unsubscribe();
}
- // Add trackByHorse for use in *ngFor
- trackByHorse(index: number, item: number): number {
- return item;
- }
+ trackByHorse(index: number, item: number): number { return item; }
}
diff --git a/btc-UI/src/app/components/sidebar/sidebar.component.html b/btc-UI/src/app/components/sidebar/sidebar.component.html
index 6a9e745..2023479 100755
--- a/btc-UI/src/app/components/sidebar/sidebar.component.html
+++ b/btc-UI/src/app/components/sidebar/sidebar.component.html
@@ -221,7 +221,7 @@
-
+
+
+
+
VIEW RC
+
+
+ π Venue: {{ raceCardData.venue }}
+ π
Date: {{ raceCardData.date }}
+
+
+
π Race Lists
+
+
+
+ | Races |
+ Race Numbers |
+
+
+
+
+ | Race {{ race.raceNo }} |
+ {{ race.horses.join(', ') }} |
+
+
+
+
+
+
+
π―Races
+
+
+
+ | Pool |
+ Races |
+
+
+
+
+ | {{ pool }} |
+ {{ raceCardData.pools.comboRaces[pool].join(', ') }} |
+
+
+
+
+
+
+
+ β {{ raceCardData?.error }}
+
+
+
diff --git a/btc-UI/src/app/components/sidebar/sidebar.component.ts b/btc-UI/src/app/components/sidebar/sidebar.component.ts
index 935b550..d66f686 100755
--- a/btc-UI/src/app/components/sidebar/sidebar.component.ts
+++ b/btc-UI/src/app/components/sidebar/sidebar.component.ts
@@ -441,17 +441,52 @@ ${receiptText}
raceCardData: any = null; // β
Hold fetched data
- constructor(
- private btcService: BtcService
+
+// openViewRcPopup() {
+// const cachedData = localStorage.getItem('rpinfo');
- ) {}
+// if (cachedData) {
+// this.raceCardData = JSON.parse(cachedData);
+// console.log('π¦ Loaded race card from localStorage:', this.raceCardData);
+// } else {
+// this.raceCardData = { error: 'Race card not available locally' };
+// console.warn('β οΈ No race card data found in localStorage.');
+// }
- openViewRcPopup() {
- const cachedData = localStorage.getItem('raceCardData');
+// this.showViewRc = true;
+// }
+
+// openViewRcPopup() {
+// const cachedData = localStorage.getItem('rpinfo');
+
+// if (cachedData) {
+// try {
+// this.raceCardData = JSON.parse(cachedData); // now it's an array
+// console.log('π¦ Loaded race card from localStorage:', this.raceCardData);
+// } catch (e) {
+// console.error('Error parsing rpinfo:', e);
+// this.raceCardData = { error: 'Invalid race card data' };
+// }
+// } else {
+// this.raceCardData = { error: 'Race card not available locally' };
+// console.warn('β οΈ No race card data found in localStorage.');
+// }
+
+// this.showViewRc = true;
+// }
+
+openViewRcPopup() {
+ const cachedData = localStorage.getItem('rpinfo');
if (cachedData) {
- this.raceCardData = JSON.parse(cachedData);
- console.log('π¦ Loaded race card from localStorage:', this.raceCardData);
+ try {
+ const parsed = JSON.parse(cachedData);
+ this.raceCardData = parsed.structuredRaceCard; // β
only keep structured
+ console.log('π¦ Loaded structured race card:', this.raceCardData);
+ } catch (e) {
+ console.error('Error parsing rpinfo:', e);
+ this.raceCardData = { error: 'Invalid race card data' };
+ }
} else {
this.raceCardData = { error: 'Race card not available locally' };
console.warn('β οΈ No race card data found in localStorage.');
@@ -460,6 +495,8 @@ ${receiptText}
this.showViewRc = true;
}
+
+
objectKeys = Object.keys;
closeViewRcPopup() {
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 4b97aba..7a105cf 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
@@ -19,7 +19,7 @@ import _ from 'lodash';
imports: [CommonModule],
templateUrl: './touch-pad-menu.component.html',
styleUrls: ['./touch-pad-menu.component.css'],
- changeDetection: ChangeDetectionStrategy.OnPush // π₯ CRUCIAL for performance
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class TouchPadMenuComponent implements OnInit, OnDestroy {
@Input() ticketingActive: boolean = false;
@@ -65,9 +65,9 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
// Multi-leg logic (TRE, MJP, JKP)
multiLegStage = 0;
multiLegGroups: (number | string)[][] = [[], [], [], [], []];
- multiLegBaseRaceIdx: number = 0; // Track starting race index
+ multiLegBaseRaceIdx: number = 0; // Track starting race index (1-based)
currentLegRaceDisplay: string = ''; // Display current leg's race
- currentPool: string | null = null; // Track current pool (mjp1, jkp1, trb1, trb2)
+ currentPool: string | null = null; // canonical pool key like 'mjp1','trb1','jkp1'
isBoxed: boolean = false;
@@ -94,24 +94,41 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
btid: string | null = null;
raceCardData: any = {};
+ structuredRaceCard: any = {};
+
+ selectedRaceNumber: string = '1';
+ selectionService: SelectionService;
+ sharedStateService: SharedStateService;
+ labelRestrictionService: LabelRestrictionService;
constructor(
- private selectionService: SelectionService,
- private sharedStateService: SharedStateService,
- private labelRestrictionService: LabelRestrictionService,
- private ngZone: NgZone // <-- inject NgZone
- ) {}
-
-
- selectedRaceNumber: string = '1'; // Default
+ selectionService: SelectionService,
+ sharedStateService: SharedStateService,
+ labelRestrictionService: LabelRestrictionService,
+ private ngZone: NgZone
+ ) {
+ this.selectionService = selectionService;
+ this.sharedStateService = sharedStateService;
+ this.labelRestrictionService = labelRestrictionService;
+ }
ngOnInit() {
- this.runnerCountSubscription = this.sharedStateService.runnerCount$.subscribe(count => {
+ // Prefer rpinfo.structuredRaceCard
+ 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.raceCardData = this.structuredRaceCard;
+
+ this.runnerCountSubscription = this.sharedStateService.runnerCount$.subscribe((count: number) => {
this.runnerCount = count || 12;
this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
this.numbersFlat = this.numberRows.flat();
this.updateLegRaceDisplay(this.currentPool || '');
-
this.btid = localStorage.getItem('btid');
// --- NEW: Update actualRunners when runner count changes ---
this.setActualRunners();
@@ -119,10 +136,10 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
this.labelRowsFlat = this.labelRows.flat();
- this.selectionsSubscription = this.selectionService.selections$.subscribe(selections => {
+ this.selectionsSubscription = this.selectionService.selections$.subscribe((selections: SelectionData[]) => {
this.currentSelections = selections;
this.maxRowsReached = selections.length >= 5;
- const totalAmount = selections.reduce((sum, selection) => sum + selection.total, 0);
+ const totalAmount = selections.reduce((sum: number, selection: SelectionData) => sum + (selection.total || 0), 0);
this.totalAmountLimitReached = totalAmount >= 5000;
this.blockedLabels = this.labelRestrictionService.getBlockedLabels(selections);
if (!this.totalAmountLimitReached) {
@@ -130,33 +147,30 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
}
});
- this.currentRowSubscription = this.selectionService.currentRow$.subscribe(row => {
- this.currentTotal = row.total;
+ this.currentRowSubscription = this.selectionService.currentRow$.subscribe((row: SelectionData) => {
+ this.currentTotal = row.total || 0;
});
- // --- NEW: Subscribe to race changes ---
-
- this.sharedStateService.selectedRace$.subscribe(race => {
- this.selectedRaceNumber = String(race || '1');
- this.setActualRunners();
-
- if (this.currentPool && this.multiLegLabels.includes(this.selectedLabel || '')) {
- this.updateLegRaceDisplay(this.currentPool);
- const runnerCount = this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, this.multiLegStage);
- this.runnerCount = runnerCount || 12;
- this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
- this.numbersFlat = this.numberRows.flat();
- this.actualRunners = this.getActualRunnersForCurrentPoolLeg();
- } else {
- this.setActualRunners();
- }
-});
+ // 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;
+ this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
+ this.numbersFlat = this.numberRows.flat();
+ this.actualRunners = this.getActualRunnersForCurrentPoolLeg();
+ } else {
+ this.setActualRunners();
+ }
+ });
+ // legacy storage - keep for compatibility if rpinfo absent
const data = localStorage.getItem('raceCardData');
if (data) {
- this.raceCardData = JSON.parse(data);
- } else {
- this.raceCardData = {};
+ try { this.raceCardData = JSON.parse(data); } catch { this.raceCardData = this.raceCardData || {}; }
}
}
@@ -166,16 +180,45 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
this.runnerCountSubscription?.unsubscribe();
}
+ private safeGetJSON(key: string): any | null {
+ try {
+ const raw = localStorage.getItem(key);
+ if (!raw) return null;
+ return JSON.parse(raw);
+ } catch {
+ return null;
+ }
+ }
+
+ // Normalize pool name to canonical keys used in structuredRaceCard
+ private normalizePoolName(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';
+ }
+ if (n.startsWith('mjp')) {
+ return n.includes('2') ? 'mjp2' : 'mjp1';
+ }
+ if (n.startsWith('jkp') || n.startsWith('jpp') || n.startsWith('jkp')) {
+ return n.includes('2') ? 'jkp2' : 'jkp1';
+ }
+ // default: return lowercase
+ return n;
+ }
+
// --- NEW HELPER METHOD ---
getActualRunnersForCurrentPoolLeg(): Set
{
- const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
- const raceIdx = this.getRaceForLeg(this.currentPool!, this.multiLegStage) - 1;
- const race = raceCardData?.raceVenueRaces?.races?.[raceIdx];
- if (race?.runners && Array.isArray(race.runners)) {
- return new Set(race.runners.map((r: any) => Number(r.number)));
- }
- if (Array.isArray(race)) {
- return new Set(race.map((r: any) => Number(r.number || r)));
+ 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;
+ const race = races[raceIdx];
+ if (race?.horses && Array.isArray(race.horses)) {
+ return new Set(race.horses.map((num: any) => Number(num)));
}
return new Set();
}
@@ -191,14 +234,11 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
// --- NEW METHOD ---
getActualRunnersForCurrentRace(): Set {
- const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
- const selectedRaceIdx = this.sharedStateService.getSelectedRace() - 1;
- const race = raceCardData?.raceVenueRaces?.races?.[selectedRaceIdx];
- if (race?.runners && Array.isArray(race.runners)) {
- return new Set(race.runners.map((r: any) => Number(r.number)));
- }
- if (Array.isArray(race)) {
- return new Set(race.map((r: any) => Number(r.number || r)));
+ 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 new Set(race.horses.map((num: any) => Number(num)));
}
return new Set();
}
@@ -216,18 +256,18 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
}
get showShashEnter(): boolean {
- const label = this.selectedLabel || '';
- if (['FOR', 'QUI', 'TAN'].includes(label) && this.isBoxed) {
- return false;
+ const label = this.selectedLabel || '';
+ if (['FOR', 'QUI', 'TAN'].includes(label) && this.isBoxed) {
+ return false;
+ }
+ const specialLabels = ['FOR', 'QUI', 'TAN', 'EXA', 'TRE', 'MJP', 'JKP', '.'];
+ 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);
}
- const specialLabels = ['FOR', 'QUI', 'TAN', 'EXA', 'TRE', 'MJP', 'JKP', '.'];
- 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);
-}
get isShashEnterDisabled(): boolean {
if (this.selectedLabel === 'TAN') {
@@ -268,7 +308,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
get isWSPSelection(): boolean {
const currentRow = this.selectionService.getCurrentRow();
- return ['WIN', 'SHP', 'PLC'].includes(currentRow.label) ||
+ return ['WIN', 'SHP', 'PLC'].includes(currentRow.label) ||
this.selectionService.getSelections().some(sel => ['WIN', 'SHP', 'PLC'].includes(sel.label));
}
@@ -279,44 +319,41 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
}
isLabelDisabled(label: string): boolean {
- return this.disabledLabels.includes(label) ||
- this.totalAmountLimitReached ||
+ return this.disabledLabels.includes(label) ||
+ this.totalAmountLimitReached ||
this.blockedLabels.has(label);
}
// --- MODIFIED METHOD ---
isNumberDisabled(number: number): boolean {
- // Disable if number is not in actualRunners
- if (!this.actualRunners.has(number)) {
- return true;
- }
- // Disable if total amount limit reached
- if (this.totalAmountLimitReached) {
- return true;
- }
- // Allow all numbers for TAN when boxed, but disable selected numbers
- if (this.selectedLabel === 'TAN' && this.isBoxed) {
+ // Disable if number not present in actualRunners
+ if (!this.actualRunners.has(number)) return true;
+ // Disable if total amount limit reached
+ if (this.totalAmountLimitReached) return true;
+ // Allow all numbers for TAN when boxed, but disable selected numbers
+ if (this.selectedLabel === 'TAN' && this.isBoxed) {
+ return this.selectedNumbers.includes(number);
+ }
+ // TAN (unboxed): Disable numbers already selected in the current group
+ if (this.selectedLabel === 'TAN') {
+ return this.tanGroups[this.tanGroupStage].includes(number);
+ }
+ // Multi-leg pools (TRE, MJP, JKP): Disable numbers already selected in the current leg
+ if (this.multiLegLabels.includes(this.selectedLabel || '')) {
+ return this.multiLegGroups[this.multiLegStage].includes(number);
+ }
+ // Two-group pools (FOR, QUI): 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);
+ }
+ }
+ // Default case for WIN, SHP, THP, PLC, etc.: Disable if already selected
return this.selectedNumbers.includes(number);
}
- // TAN (unboxed): Disable numbers already selected in the current group
- if (this.selectedLabel === 'TAN') {
- return this.tanGroups[this.tanGroupStage].includes(number);
- }
- // Multi-leg pools (TRE, MJP, JKP): Disable numbers already selected in the current leg
- if (this.multiLegLabels.includes(this.selectedLabel || '')) {
- return this.multiLegGroups[this.multiLegStage].includes(number);
- }
- // Two-group pools (FOR, QUI): 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);
- }
- }
- // Default case for WIN, SHP, THP, PLC, etc.: Disable if already selected
- return this.selectedNumbers.includes(number);
-}
+
selectLabel(label: string) {
if (this.totalAmountLimitReached || this.blockedLabels.has(label)) {
this.showLimitPopup = true;
@@ -326,6 +363,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
this.trePopupVisible = true;
return;
}
+
this.selectedLabel = label;
this.selectedNumbers = [];
this.padValue = '';
@@ -334,13 +372,14 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
// Store base race index and pool name for multi-leg pools
if (this.multiLegLabels.includes(label)) {
- const poolName = label === 'MJP' ? 'mjp1' : label === 'JKP' ? 'jkp1' : label === 'TRE' ? 'trb1' : label;
+ // Map to canonical pool key
+ const poolName = label === 'MJP' ? 'mjp1' : label === 'JKP' ? 'jkp1' : 'trb1';
this.currentPool = poolName;
- this.multiLegBaseRaceIdx = this.getBaseRaceIndexForPool(poolName);
+ this.multiLegBaseRaceIdx = this.getBaseRaceIndexForPool(poolName); // Broadcast race and pool info for navbar
// Broadcast race and pool info for navbar
- this.sharedStateService.updateSharedData({
- type: 'multiLegPoolStart',
- value: { label: poolName, baseRaceIdx: this.multiLegBaseRaceIdx }
+ this.sharedStateService.updateSharedData({
+ type: 'multiLegPoolStart',
+ value: { label: poolName, baseRaceIdx: this.multiLegBaseRaceIdx }
});
this.updateLegRaceDisplay(poolName);
// --- NEW: Update runners and number pad immediately ---
@@ -368,7 +407,6 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
this.selectionService.finalizeCurrentRow();
const currentSelections = this.selectionService.getSelections();
const existingWSP = currentSelections.filter(sel => wspLabels.includes(sel.label));
-
if (existingWSP.length === 0) {
const blankRows = wspLabels.map(lbl => ({
label: lbl,
@@ -386,34 +424,29 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
this.selectionService.updatePartial({ label: '', numbers: [], value: 0, total: 0 });
return;
}
-
//----------------------------------ended here----------------------------------------------------
- // Reset TAN
+ // Reset group states
this.tanGroupStage = 0;
this.tanGroups = [[], [], []];
-
// Reset FOR/QUI
this.isFirstGroupComplete = false;
this.firstGroup = [];
this.secondGroup = [];
-
// Reset Multi-leg
this.multiLegStage = 0;
this.multiLegGroups = [[], [], [], [], []];
+
this.selectionService.updatePartial({ label });
}
private getBaseRaceIndexForPool(poolName: string): number {
- const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
- const totalRaces = raceCardData?.raceVenueRaces?.races?.length || 10;
+ const races = this.structuredRaceCard?.raceVenueRaces?.races || [];
+ const totalRaces = races.length || 10;
const maxLegs = this.getMaxLegs(poolName);
-
- // Try to get pool-to-race mapping from raceCardData
- const poolRaces = raceCardData?.raceVenueRaces?.pools?.[poolName] || [];
- let baseRaceIdx = poolRaces.length > 0 ? poolRaces[0] : this.getDefaultBaseRace(poolName);
-
- // Ensure enough races remain for the pool
+ const poolKey = this.normalizePoolName(poolName) || poolName;
+ const poolRaces: number[] = this.structuredRaceCard?.pools?.[poolKey] || [];
+ let baseRaceIdx = poolRaces.length > 0 ? poolRaces[0] : this.getDefaultBaseRace(poolKey);
if (baseRaceIdx + maxLegs - 1 > totalRaces) {
baseRaceIdx = Math.max(1, totalRaces - maxLegs + 1);
}
@@ -423,18 +456,17 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
private getDefaultBaseRace(poolName: string): number {
// Fallback to hardcoded values if raceCardData.pools is unavailable
const poolRaceMap: { [key: string]: number } = {
- 'mjp1': 1,
- 'jkp1': 3,
- 'trb1': 2,
- 'trb2': 5
+ 'mjp1': 3,
+ 'jkp1': 2,
+ 'trb1': 4
};
- return poolRaceMap[poolName] || this.sharedStateService.getSelectedRace();
+ return poolRaceMap[poolName.toLowerCase()] || parseInt(this.selectedRaceNumber, 10);
}
selectNumber(number: number) {
if (!this.selectedLabel || this.totalAmountLimitReached || !this.actualRunners.has(number)) return;
- // TAN Box mode
+ // TAN boxed
if (this.selectedLabel === 'TAN' && this.isBoxed) {
if (!this.selectedNumbers.includes(number)) {
const currentNumbers = this.selectedNumbers.filter(n => typeof n === 'number') as number[];
@@ -469,8 +501,8 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
return;
}
- // Multi-leg logic (TRE, MJP, JKP)
- if (this.multiLegLabels.includes(this.selectedLabel)) {
+ // Multi-leg
+ if (this.multiLegLabels.includes(this.selectedLabel || '')) {
if (!this.multiLegGroups[this.multiLegStage].includes(number)) {
this.multiLegGroups[this.multiLegStage].push(number);
this.updateMultiLegSelection();
@@ -512,30 +544,27 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
return;
}
-
- // Default single-number selection (WIN, SHP, THP, etc.)
- if (!this.selectedNumbers.includes(number)) {
- this.selectedNumbers.push(number);
- this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
-
+ // default single selection
+ if (!this.selectedNumbers.includes(number)) {
+ this.selectedNumbers.push(number);
+ this.selectionService.updatePartial({ numbers: [...this.selectedNumbers] });
// β
Special logic: If WSP, mirror number to WIN, SHP, and THP
- if (this.selectedLabel === 'WSP') {
- const labelsToUpdate = ['WIN', 'SHP', 'PLC'];
- const selections = this.selectionService.getSelections();
- const updated = selections.map(sel => {
- if (labelsToUpdate.includes(sel.label)) {
- const newNumbers = [...sel.numbers];
- if (!newNumbers.includes(number)) {
- newNumbers.push(number);
+
+ if (this.selectedLabel === 'WSP') {
+ const labelsToUpdate = ['WIN', 'SHP', 'PLC'];
+ const selections = this.selectionService.getSelections();
+ const updated = selections.map(sel => {
+ if (labelsToUpdate.includes(sel.label)) {
+ const newNumbers = [...sel.numbers];
+ if (!newNumbers.includes(number)) newNumbers.push(number);
+ return { ...sel, numbers: newNumbers };
}
- return { ...sel, numbers: newNumbers };
- }
- return sel;
- });
- this.selectionService.setSelections(updated);
+ return sel;
+ });
+ this.selectionService.setSelections(updated);
+ }
}
}
- }
private updateMultiLegSelection() {
const combined: (number | string)[] = [];
@@ -590,15 +619,10 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
const labels = ['WIN', 'SHP', 'PLC'];
const targetLabel = labels[this.wspTicketStage];
-
// Update only the current WSP stage's value to 0
const updatedSelections = this.selectionService.getSelections().map(sel => {
if (sel.label === targetLabel && JSON.stringify(sel.numbers) === JSON.stringify(this.selectedNumbers)) {
- return {
- ...sel,
- value: 0,
- total: 0
- };
+ return { ...sel, value: 0, total: 0 };
}
return sel;
});
@@ -609,63 +633,52 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
}
onPadEnter() {
- if (this.maxRowsReached) return;
+ if (this.maxRowsReached) return;
+
+ if (!this.canPrint) {
+ this.print();
+ }
+
+ const value = parseFloat(this.padValue) || 0;
+ if (this.selectedLabel === 'WSP') {
+ const labels = ['WIN', 'SHP', 'PLC'];
+ const targetLabel = labels[this.wspTicketStage];
+ const selections = this.selectionService.getSelections();
+ // Find the current WSP row to ensure numbers are synchronized
+
+ const currentWSPRow = selections.find(sel => sel.label === targetLabel);
+ if (currentWSPRow) {
+ this.selectedNumbers = [...currentWSPRow.numbers];
+ }
+
+ const updatedSelections = selections.map(sel => {
+ if (sel.label === targetLabel) {
+ const total = value * (sel.numbers?.length || 0) * 10;
+ return { ...sel, value, total };
+ }
+ return sel;
+ });
+
+ this.selectionService.setSelections(updatedSelections);
+ // Only increment stage if not at the last stage (PLC)
+
+ if (this.wspTicketStage < 2) {
+ this.wspTicketStage++;
+ // Update selectedNumbers for the next stage
+ const nextLabel = labels[this.wspTicketStage];
+ const nextWSPRow = updatedSelections.find(sel => sel.label === nextLabel);
+ if (nextWSPRow) this.selectedNumbers = [...nextWSPRow.numbers];
+ }
+ this.padValue = '';
+ this.updateCanPrint();
+ return;
+ }
- if (!this.canPrint) {
this.print();
}
- const value = parseFloat(this.padValue) || 0;
-
- if (this.selectedLabel === 'WSP') {
- const labels = ['WIN', 'SHP', 'PLC'];
- const targetLabel = labels[this.wspTicketStage];
- const selections = this.selectionService.getSelections();
-
- // Find the current WSP row to ensure numbers are synchronized
- const currentWSPRow = selections.find(sel => sel.label === targetLabel);
- if (currentWSPRow) {
- this.selectedNumbers = [...currentWSPRow.numbers]; // Synchronize selectedNumbers
- }
-
- const updatedSelections = selections.map(sel => {
- if (sel.label === targetLabel) {
- const total = value * (sel.numbers?.length || 0) * 10;
- return {
- ...sel,
- value,
- total
- };
- }
- return sel;
- });
-
- this.selectionService.setSelections(updatedSelections);
-
- // Only increment stage if not at the last stage (PLC)
- if (this.wspTicketStage < 2) {
- this.wspTicketStage++;
- // Update selectedNumbers for the next stage
- const nextLabel = labels[this.wspTicketStage];
- const nextWSPRow = updatedSelections.find(sel => sel.label === nextLabel);
- if (nextWSPRow) {
- this.selectedNumbers = [...nextWSPRow.numbers];
- }
- }
-
- this.padValue = '';
- this.updateCanPrint();
- return;
- }
-
- this.print();
-}
-
-
onShashEnter() {
- if (this.selectedLabel === 'TAN' && this.isBoxed) {
- return;
- }
+ if (this.selectedLabel === 'TAN' && this.isBoxed) return;
if (this.selectedLabel === 'TAN') {
if (this.tanGroupStage < 2 && this.tanGroups[this.tanGroupStage].length > 0) {
@@ -700,9 +713,9 @@ if (this.twoGroupLabels.includes(this.selectedLabel || '')) {
}
}
}
-
//-----------------------------ENTER PAD VALUE-------------------------------------
-enterPadVal(key: string) {
+
+ enterPadVal(key: string) {
if (!this.numericPadEnabled || this.totalAmountLimitReached) return;
if (key === 'X') {
@@ -735,15 +748,10 @@ enterPadVal(key: string) {
if (this.selectedLabel === 'WSP') {
const labels = ['WIN', 'SHP', 'PLC'];
const targetLabel = labels[this.wspTicketStage];
-
const updatedSelections = this.selectionService.getSelections().map(sel => {
if (sel.label === targetLabel && JSON.stringify(sel.numbers) === JSON.stringify(this.selectedNumbers)) {
const total = value * (sel.numbers?.length || 0) * 10;
- return {
- ...sel,
- value,
- total
- };
+ return { ...sel, value, total };
}
return sel;
});
@@ -860,14 +868,9 @@ enterPadVal(key: string) {
this.updateCanPrint();
}
}
-//---------------------------------------------------------------------------------------------
updateCanPrint() {
- // Disable Enter if maxRowsReached
- if (this.maxRowsReached) {
- this.canPrint = false;
- return;
- }
+ if (this.maxRowsReached) { this.canPrint = false; return; }
this.canPrint = this.padValue.trim().length > 0 && /^[0-9]+$/.test(this.padValue);
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
const maxLegs = this.getMaxLegs(this.currentPool || '');
@@ -880,23 +883,15 @@ enterPadVal(key: string) {
// At least one valid row in finalized selections or current row
const selections = this.selectionService.getSelections();
const currentRow = this.selectionService.getCurrentRow();
- if (this.selectedLabel === 'WSP') {
+ if (this.selectedLabel === 'WSP') {
// For WSP, require all three rows (WIN, SHP, PLC) to have valid numbers and values >= 1
const wspLabels = ['WIN', 'SHP', 'PLC'];
const wspSelections = selections.filter(sel => wspLabels.includes(sel.label));
-
- // Check if we are at the last stage (PLC) and all rows are valid
- const allWSPRowsValid = wspSelections.length === 3 && wspSelections.every(row =>
- row.label &&
- row.numbers &&
- row.numbers.length > 0 &&
- row.value >= 1 &&
- row.total > 0
+ const allWSPRowsValid = wspSelections.length === 3 && wspSelections.every(row =>
+ row.label && row.numbers && row.numbers.length > 0 && row.value >= 1 && row.total > 0
);
-
return this.wspTicketStage === 2 && allWSPRowsValid;
}
-
// For non-WSP, keep existing logic: any valid row enables printing
const hasValidRow = selections.some(
row => !!row.label && !!row.numbers && row.numbers.length > 0 && row.value > 0 && row.total > 0
@@ -907,7 +902,7 @@ enterPadVal(key: string) {
}
print() {
- const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + sel.total, 0);
+ const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + (sel.total || 0), 0);
let currentRowAmount = 0;
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
const maxLegs = this.getMaxLegs(this.currentPool || '');
@@ -918,20 +913,14 @@ enterPadVal(key: string) {
return group.length;
}).slice(0, maxLegs);
const units = parseFloat(this.padValue) || 0;
- currentRowAmount = this.calculateMultiLegAmount(
- this.selectedLabel as 'TRE' | 'MJP' | 'JKP',
- horsesPerLeg,
- units
- );
+ currentRowAmount = this.calculateMultiLegAmount(this.selectedLabel as 'TRE' | 'MJP' | 'JKP', horsesPerLeg, units);
if (currentRowAmount > 5000 || selectionsTotal + currentRowAmount > 5000) {
this.totalAmountLimitReached = true;
this.showLimitPopup = true;
return;
}
// Ensure all legs have selections
- if (horsesPerLeg.some(count => count === 0)) {
- return;
- }
+ if (horsesPerLeg.some(count => count === 0)) return;
} else {
currentRowAmount = this.currentTotal;
if (selectionsTotal + currentRowAmount > 5000) {
@@ -940,96 +929,46 @@ enterPadVal(key: string) {
return;
}
}
+
if (this.selectedLabel === 'WSP') {
const virtualRows = this.createVirtualRowsFromWSP();
const currentSelections = this.selectionService.getSelections();
const nonWSPSelections = currentSelections.filter(sel => !['WIN', 'SHP', 'PLC'].includes(sel.label));
this.selectionService.setSelections([...nonWSPSelections, ...virtualRows]);
}
+
this.selectionService.finalizeCurrentRow();
this.resetSelections();
}
-
-//---------Helper Function-----------
-getHorseNumbersForSelectedRace(): number[] {
- try {
- const raceCardDataStr = localStorage.getItem('raceCardData');
-
- console.log('[DEBUG] raceCardDataStr:', raceCardDataStr);
-
- if (!raceCardDataStr) {
- console.warn('[DEBUG] No raceCardData found in localStorage');
- return [];
- }
-
- const raceCardData = JSON.parse(raceCardDataStr);
-
- console.log('[DEBUG] Parsed raceCardData:', raceCardData);
-
- const selectedRaceIdx = parseInt(this.selectedRaceNumber, 10) - 1; // Convert '1' β 0
-
- console.log('[DEBUG] selectedRaceNumber:', this.selectedRaceNumber);
- console.log('[DEBUG] selectedRaceIdx:', selectedRaceIdx);
-
- const races = raceCardData.raceVenueRaces?.races || [];
-
- console.log('[DEBUG] races array:', races);
-
- if (races[selectedRaceIdx]) {
- console.log('[DEBUG] Horse numbers for selected race:', races[selectedRaceIdx]);
- return races[selectedRaceIdx];
- } else {
- console.warn('[DEBUG] No horses found for selectedRaceIdx:', selectedRaceIdx);
- return [];
- }
- } catch (err) {
- console.error('[DEBUG] Error parsing raceCardData:', err);
+ // 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 [];
}
-}
-// Helper: Get horse numbers for a specific race index (0-based)
-getHorseNumbersForRaceIdx(raceIdx: number): number[] {
- try {
- const raceCardDataStr = localStorage.getItem('raceCardData');
- if (!raceCardDataStr) return [];
- const raceCardData = JSON.parse(raceCardDataStr);
- const races = raceCardData.raceVenueRaces?.races || [];
+ getHorseNumbersForRaceIdx(raceIdx: number): number[] {
+ const races = this.structuredRaceCard?.raceVenueRaces?.races || [];
const race = races[raceIdx];
- if (Array.isArray(race)) {
- // If race is array of runners, extract their numbers
- return race
- .map((runner: any) => {
- if (typeof runner === 'number') return runner;
- if (typeof runner === 'object' && runner.number) return Number(runner.number);
- if (typeof runner === 'object' && runner.horseNumber) return Number(runner.horseNumber);
- return null;
- })
- .filter((n: any): n is number => typeof n === 'number' && n > 0);
- }
- return [];
- } catch {
+ 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');
+ clearLocalTickets() {
+ localStorage.removeItem('localTickets');
+ console.log('π§Ή localTickets cleared from localStorage');
// Clear the print count
- localStorage.removeItem('printClickCount');
- console.log('π§Ό printClickCount cleared from localStorage');
+ localStorage.removeItem('printClickCount');
+ console.log('π§Ό printClickCount cleared from localStorage');
// Reset via shared state
- this.sharedStateService.setSalesTotal(0);
- this.sharedStateService.setReceiveTotal(0);
- // window.location.reload();
- this.selectionService.clearSelections();
- this.resetSelections();
-
- // Optionally clear print clicks
- // localStorage.removeItem('printClickCount');
-}
+ this.sharedStateService.setSalesTotal(0);
+ this.sharedStateService.setReceiveTotal(0);
+ this.selectionService.clearSelections();
+ this.resetSelections();
+ }
//-------------------PRINT LOGIC----------------------------------------
@@ -1389,12 +1328,9 @@ try {
this.fieldInput = '';
this.fieldFEntered = false;
this.wspTicketStage = 0;
+ // Clear multi-leg display in Navbar
- // Clear multi-leg display in Navbar
- this.sharedStateService.updateSharedData({
- type: 'multiLegPoolEnd',
- value: null
- });
+ this.sharedStateService.updateSharedData({ type: 'multiLegPoolEnd', value: null });
}
toggleBoxMode() {
@@ -1420,11 +1356,7 @@ try {
if (this.selectedNumbers.includes('F') && this.allowedFieldLabels.includes(this.selectedLabel || '')) {
this.selectedNumbers = [];
- this.selectionService.updatePartial({
- numbers: [],
- isBoxed: false,
- label: this.selectedLabel || ''
- });
+ this.selectionService.updatePartial({ numbers: [], isBoxed: false, label: this.selectedLabel || '' });
return;
}
@@ -1440,11 +1372,7 @@ try {
if (group2.length) combined.push('-', ...group2);
if (group3.length) combined.push('-', ...group3);
this.selectedNumbers = combined;
- this.selectionService.updatePartial({
- numbers: [...this.selectedNumbers],
- isBoxed: true,
- label: 'TAN'
- });
+ this.selectionService.updatePartial({ numbers: [...this.selectedNumbers], isBoxed: true, label: 'TAN' });
}
return;
}
@@ -1488,62 +1416,43 @@ try {
}
private getMaxLegs(poolName: string): number {
- switch (poolName) {
- case 'mjp1':
- return 4;
- case 'jkp1':
- return 5;
- case 'trb1':
- case 'trb2':
- case 'TRE':
- return 3;
- default:
- return 5; // Default to 5 for unspecified pools
+ 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 getRaceForLeg(poolName: string, leg: number): number {
- const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
- const poolRaces = raceCardData?.raceVenueRaces?.pools?.[poolName] || [];
- if (poolRaces.length > leg) {
- return poolRaces[leg];
- }
- // Fallback to default race mapping
- const raceMap: { [key: string]: number[] } = {
- 'mjp1': [1, 2, 3, 4],
- 'jkp1': [3, 4, 5, 6, 7],
- 'trb1': [2, 3, 4],
- 'trb2': [5, 6, 7]
- };
- return raceMap[poolName]?.[leg] || (this.multiLegBaseRaceIdx + leg);
+ 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;
}
private updateLegRaceDisplay(poolName: string) {
- if (!['mjp1', 'jkp1', 'trb1', 'trb2'].includes(poolName)) {
+ const poolKey = this.normalizePoolName(poolName) || '';
+ if (!['mjp1', 'jkp1', 'trb1', 'trb2', 'mjp2', 'jkp2'].includes(poolKey)) {
this.currentLegRaceDisplay = '';
this.currentPool = null;
return;
}
- const raceIdx = this.getRaceForLeg(poolName, this.multiLegStage);
+ 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.sharedStateService.updateSharedData({ type: 'currentLegRace', value: raceIdx });
}
private getRunnerCountForLeg(baseIdx: number, leg: number): number {
- const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
+ const races = this.structuredRaceCard?.raceVenueRaces?.races || [];
const raceIdx = this.getRaceForLeg(this.currentPool || '', leg) - 1;
- const race = raceCardData?.raceVenueRaces?.races?.[raceIdx] || [];
- if (race?.runners && Array.isArray(race.runners)) {
- return race.runners.length;
- }
- if (Array.isArray(race)) {
- return race.length;
- }
+ const race = races[raceIdx];
+ if (race?.horses && Array.isArray(race.horses)) return race.horses.length;
return 12;
}
@@ -1554,48 +1463,28 @@ try {
if (!this.isFirstGroupComplete) {
this.firstGroup = ['F'];
this.selectedNumbers = ['F'];
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: [...this.selectedNumbers],
- isBoxed: false
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
} else {
this.secondGroup = ['F'];
this.selectedNumbers = [...this.firstGroup, '-', 'F'];
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: [...this.selectedNumbers],
- isBoxed: false
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
}
} else if (this.selectedLabel === 'TAN') {
if (this.isBoxed) {
this.selectedNumbers = ['F'];
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: ['F'],
- isBoxed: true
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: true });
} else {
this.tanGroups[this.tanGroupStage] = ['F'];
const combined: (number | string)[] = [...this.tanGroups[0]];
if (this.tanGroupStage > 0) combined.push('-', ...this.tanGroups[1]);
if (this.tanGroupStage > 1) combined.push('-', ...this.tanGroups[2]);
this.selectedNumbers = combined;
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: [...this.selectedNumbers],
- isBoxed: false
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
}
} else if (this.multiLegLabels.includes(this.selectedLabel)) {
this.multiLegGroups[this.multiLegStage] = ['F'];
this.updateMultiLegSelection();
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: [...this.selectedNumbers],
- isBoxed: false
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
}
}
@@ -1612,28 +1501,16 @@ try {
canUseField(): boolean {
if (this.totalAmountLimitReached) return false;
if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') {
- if (!this.isFirstGroupComplete && this.firstGroup.length === 0) {
- return true;
- }
- if (this.isFirstGroupComplete && this.secondGroup.length === 0) {
- return true;
- }
+ if (!this.isFirstGroupComplete && this.firstGroup.length === 0) return true;
+ if (this.isFirstGroupComplete && this.secondGroup.length === 0) return true;
return false;
}
if (this.selectedLabel === 'TAN' || this.multiLegLabels.includes(this.selectedLabel || '')) {
- if (this.selectedLabel === 'TAN' && this.tanGroups[this.tanGroupStage].length === 0) {
- return true;
- }
- if (this.multiLegLabels.includes(this.selectedLabel || '') && this.multiLegGroups[this.multiLegStage].length === 0) {
- return true;
- }
+ if (this.selectedLabel === 'TAN' && this.tanGroups[this.tanGroupStage].length === 0) return true;
+ if (this.multiLegLabels.includes(this.selectedLabel || '') && this.multiLegGroups[this.multiLegStage].length === 0) return true;
return false;
}
- return (
- this.selectedLabel !== null &&
- this.allowedFieldLabels.includes(this.selectedLabel) &&
- this.selectedNumbers.length === 0
- );
+ return this.selectedLabel !== null && this.allowedFieldLabels.includes(this.selectedLabel) && this.selectedNumbers.length === 0;
}
openFieldModal() {
@@ -1647,9 +1524,7 @@ try {
}
}
- closeFieldModal() {
- this.fieldModalOpen = false;
- }
+ closeFieldModal() { this.fieldModalOpen = false; }
handleFieldKey(key: string) {
if (key === 'BACK') {
@@ -1678,62 +1553,37 @@ try {
this.secondGroup = ['F'];
this.selectedNumbers = [...this.firstGroup, '-', 'F'];
}
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: [...this.selectedNumbers],
- isBoxed: false
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
} else if (this.selectedLabel === 'TAN') {
if (this.isBoxed) {
this.selectedNumbers = ['F'];
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: ['F'],
- isBoxed: true
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: true });
} else {
this.tanGroups[this.tanGroupStage] = ['F'];
const combined: (number | string)[] = [...this.tanGroups[0]];
if (this.tanGroupStage > 0) combined.push('-', ...this.tanGroups[1]);
if (this.tanGroupStage > 1) combined.push('-', ...this.tanGroups[2]);
this.selectedNumbers = combined;
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: [...this.selectedNumbers],
- isBoxed: false
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
}
} else if (this.multiLegLabels.includes(this.selectedLabel)) {
this.multiLegGroups[this.multiLegStage] = ['F'];
this.updateMultiLegSelection();
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: [...this.selectedNumbers],
- isBoxed: false
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: [...this.selectedNumbers], isBoxed: false });
} else {
this.selectedNumbers = ['F'];
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: ['F'],
- isBoxed: false
- });
+ this.selectionService.updatePartial({ label: this.selectedLabel, numbers: ['F'], isBoxed: false });
}
this.closeFieldModal();
}
openPoolReplaceModal() {
if (this.totalAmountLimitReached) return;
- // Determine allowed group based on current selection
const groupA = ['WIN', 'SHP', 'THP', 'PLC', 'SHW'];
const groupB = ['FOR', 'QUI', 'TAN'];
- if (groupA.includes(this.selectedLabel || '')) {
- this.poolReplaceOptions = groupA;
- } else if (groupB.includes(this.selectedLabel || '')) {
- this.poolReplaceOptions = groupB;
- } else {
- this.poolReplaceOptions = [];
- }
+ if (groupA.includes(this.selectedLabel || '')) this.poolReplaceOptions = groupA;
+ else if (groupB.includes(this.selectedLabel || '')) this.poolReplaceOptions = groupB;
+ else this.poolReplaceOptions = [];
this.poolReplaceOpen = true;
}
@@ -1762,9 +1612,7 @@ try {
this.selectionService.updatePartial({ label });
}
- closePoolReplaceModal() {
- this.poolReplaceOpen = false;
- }
+ closePoolReplaceModal() { this.poolReplaceOpen = false; }
treButtonClick(btnNum: number) {
this.trePopupVisible = false;
@@ -1785,80 +1633,59 @@ try {
this.multiLegStage = 0;
this.multiLegGroups = [[], [], [], [], []];
- // Map TRE button to specific pool name and base race dynamically
- const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
- const trePoolMap: { [key: number]: { name: string } } = {
- 1: { name: 'trb1' },
- 2: { name: 'trb2' }
- };
- const poolInfo = trePoolMap[btnNum] || { name: 'trb1' };
+ // Map button to pool
+ const poolInfo = btnNum === 2 ? { name: 'trb2' } : { name: 'trb1' };
this.currentPool = poolInfo.name;
this.multiLegBaseRaceIdx = this.getBaseRaceIndexForPool(poolInfo.name);
- // Broadcast TRE selection
- this.sharedStateService.updateSharedData({
- type: 'multiLegPoolStart',
- value: { label: poolInfo.name, baseRaceIdx: this.multiLegBaseRaceIdx }
+ this.sharedStateService.updateSharedData({
+ type: 'multiLegPoolStart',
+ value: { label: poolInfo.name, baseRaceIdx: this.multiLegBaseRaceIdx }
});
+
this.updateLegRaceDisplay(poolInfo.name);
this.selectionService.updatePartial({ label: 'TRE' });
- // --- NEW: Update runners and number pad immediately ---
+
this.actualRunners = this.getActualRunnersForCurrentPoolLeg();
this.runnerCount = this.getRunnerCountForLeg(this.multiLegBaseRaceIdx, 0) || 12;
this.numbers = Array.from({ length: 30 }, (_, i) => i + 1);
this.numbersFlat = this.numberRows.flat();
}
- closeTrePopup() {
- this.trePopupVisible = false;
- }
-
- closeLimitPopup() {
- this.showLimitPopup = false;
- }
+ closeTrePopup() { this.trePopupVisible = false; }
+ closeLimitPopup() { this.showLimitPopup = false; }
copyNumbers() {
- if (!this.selectedLabel) {
- console.warn('Please select a label before copying numbers.');
- return;
+ if (!this.selectedLabel) { console.warn('Please select a label before copying numbers.'); return; }
+ const selections = this.selectionService.getSelections();
+ if (selections.length === 0) { console.warn('No previous rows to copy from.'); return; }
+
+ // Copy numbers from the most recent finalized row
+ const lastRow = selections[selections.length - 1];
+ const numbersToCopy = [...lastRow.numbers];
+ // Apply the copied numbers directly
+ this.selectedNumbers = numbersToCopy;
+
+ // Validate against actual runners
+ const validNumbers = this.selectedNumbers.filter(num => {
+ if (typeof num === 'string') return num === 'F' || num === '-';
+ return this.actualRunners.has(num as number);
+ });
+
+ this.selectedNumbers = validNumbers;
+ // Update the current row in the selection service
+ this.selectionService.updatePartial({
+ label: this.selectedLabel,
+ numbers: [...this.selectedNumbers],
+ isBoxed: this.isBoxed,
+ value: parseFloat(this.padValue) || 0
+ });
+
+ this.updateCanPrint();
}
- const selections = this.selectionService.getSelections();
- if (selections.length === 0) {
- console.warn('No previous rows to copy from.');
- return;
- }
-
- // Copy numbers from the most recent finalized row
- const lastRow = selections[selections.length - 1];
- const numbersToCopy = [...lastRow.numbers];
-
- // Apply the copied numbers directly
- this.selectedNumbers = numbersToCopy;
-
- // Validate against actual runners
- const validNumbers = this.selectedNumbers.filter(num => {
- if (typeof num === 'string') return num === 'F' || num === '-';
- return this.actualRunners.has(num);
- });
-
- this.selectedNumbers = validNumbers;
-
- // Update the current row in the selection service
- this.selectionService.updatePartial({
- label: this.selectedLabel,
- numbers: [...this.selectedNumbers],
- isBoxed: this.isBoxed,
- value: parseFloat(this.padValue) || 0
- });
-
- this.updateCanPrint();
-}
-
// Add trackByHorse for use in *ngFor
- trackByHorse(index: number, item: number): number {
- return item;
- }
+ trackByHorse(index: number, item: number): number { return item; }
// Example usage of _.uniq for enabledHorseNumbers (if you ever set it)
setEnabledHorseNumbers(numbers: number[]) {
@@ -1869,43 +1696,22 @@ try {
get dedupedEnabledHorseNumbers(): number[] {
return _.uniq(this.enabledHorseNumbers);
}
-// Update canEnterRow to require value between 1 and 100
-get canEnterRow(): boolean {
- if (this.maxRowsReached) {
- console.log('[DEBUG] canEnterRow: maxRowsReached is true, disabling Enter');
- return false;
- }
-
- // Special handling for WSP
+ // Update canEnterRow to require value between 1 and 100
+ get canEnterRow(): boolean {
+ if (this.maxRowsReached) return false;
if (this.selectedLabel === 'WSP') {
const isValidPadValue = this.padValue.trim().length > 0 && /^[0-9]+$/.test(this.padValue);
const hasNumbers = this.selectedNumbers.length > 0;
- console.log('[DEBUG] canEnterRow (WSP):', {
- isValidPadValue,
- hasNumbers,
- padValue: this.padValue,
- selectedNumbers: this.selectedNumbers,
- wspTicketStage: this.wspTicketStage
- });
return isValidPadValue && hasNumbers;
}
-
- // Default logic for non-WSP
- const currentRow = this.selectionService.getCurrentRow();
- const result = !!currentRow.label &&
+ // Default logic for non-WSP
+ const currentRow = this.selectionService.getCurrentRow();
+ return !!currentRow.label &&
!!currentRow.numbers &&
currentRow.numbers.length > 0 &&
typeof currentRow.value === 'number' &&
currentRow.value >= 1 &&
currentRow.value <= 100 &&
currentRow.total > 0;
- console.log('[DEBUG] canEnterRow (non-WSP):', {
- label: currentRow.label,
- numbers: currentRow.numbers,
- value: currentRow.value,
- total: currentRow.total,
- result
- });
- return result;
}
-}
\ No newline at end of file
+}
diff --git a/btc-UI/src/app/home/home.component.ts b/btc-UI/src/app/home/home.component.ts
index c760191..3b874ba 100755
--- a/btc-UI/src/app/home/home.component.ts
+++ b/btc-UI/src/app/home/home.component.ts
@@ -49,6 +49,8 @@ export class HomeComponent implements OnInit, OnDestroy {
console.log('π HomeComponent loaded');
+
+
this.btcService.getAllRaceEventsToday().subscribe({
next: (response: HttpResponse) => {
const horseRaceData = response.body;
@@ -64,13 +66,13 @@ export class HomeComponent implements OnInit, OnDestroy {
},
});
- const raceCardCached = localStorage.getItem('raceCardData');
+ const raceCardCached = localStorage.getItem('rpinfo');
if (!raceCardCached) {
this.btcService.getRaceCard().subscribe({
next: (res) => {
const raceCardData = res.body;
console.log('π¦ Race card preloaded:', raceCardData);
- localStorage.setItem('raceCardData', JSON.stringify(raceCardData));
+ localStorage.setItem('rpinfo', JSON.stringify(raceCardData));
this.updateRunnerCount(0);
},
error: (err) => {
@@ -118,7 +120,7 @@ export class HomeComponent implements OnInit, OnDestroy {
}
private updateRunnerCount(raceIdx: number) {
- const raceCardData = JSON.parse(localStorage.getItem('raceCardData') || '{}');
+ const raceCardData = JSON.parse(localStorage.getItem('rpinfo') || '{}');
const race = raceCardData?.raceVenueRaces?.races?.[raceIdx] || [];
const runnerCount = Array.isArray(race) ? race.length : 12;
if (!raceCardData?.raceVenueRaces?.races?.[raceIdx]) {
diff --git a/btc-UI/src/app/login/login.component.ts b/btc-UI/src/app/login/login.component.ts
index 1d45343..8355fb8 100755
--- a/btc-UI/src/app/login/login.component.ts
+++ b/btc-UI/src/app/login/login.component.ts
@@ -213,6 +213,7 @@ export class LoginComponent implements OnInit, OnDestroy {
this.passwordStatus = true;
}
//--------------------------------------NEW LOGIN LOGIC ----------------------------------------//
+//--------------------------------------NEW LOGIN LOGIC ----------------------------------------//
async onSubmit(): Promise {
if (this.loginForm.invalid) {
this.loginForm.markAllAsTouched();
@@ -239,103 +240,37 @@ async onSubmit(): Promise {
const btid = this.btcService.btid;
console.log("π¦ BTID from file (via service):", btid);
- // β
Prepare print data
- const printData = {
- name: userName,
- employeeId: employeeId,
- // btid: btid || "unknown",
- action: 'login',
- type: 'login'
- };
-
- console.log(printData.name);
- console.log(printData.employeeId);
- //console.log(printData.btid);
- console.log(btid);
-
// β
Store in localStorage
localStorage.setItem('userName', userName);
localStorage.setItem('employeeId', employeeId);
localStorage.setItem('password', password);
localStorage.setItem('btid', btid || "unknown");
- // β
Print first β login only if printing succeeds
- // fetch('http://localhost:9100/print', {
- // method: 'POST',
- // headers: { 'Content-Type': 'application/json' },
- // body: JSON.stringify(printData),
- // })
- // .then((res) => {
- // if (!res.ok) throw new Error('Print failed');
- // console.log('π¨οΈ Print successful');
+ // β
Fetch race card after login success
+ this.btcService
+ .fetchRaceCard("021804111066", password, btid || "0483") // pass correct payload here
+ .subscribe({
+ next: (rpinfo) => {
+ console.log("π¦ Race Card:", rpinfo);
- // β
Only here we allow login
- (window as any).electronAPI?.openSecondScreen?.();
- this.router.navigate(['/home']);
- // })
- // .catch((err) => {
- // console.error('βΌοΈ Print failed', err);
- // this.loginError = 'Login failed: printing service unavailable.';
- // this.passwordStatus = false; // reset status
- // });
+ // Save in localStorage for later use
+ localStorage.setItem('rpinfo', JSON.stringify(rpinfo));
+
+ // β
Navigate once race card stored
+ (window as any).electronAPI?.openSecondScreen?.();
+ this.router.navigate(['/home']);
+ },
+ error: (err) => {
+ console.error("βΌοΈ Failed to fetch race card", err);
+ this.loginError = "Could not load race card.";
+ }
+ });
},
error: () => {
this.loginError = 'Invalid login credentials';
}
});
}
-//-------------------------NEW LOGIN ENDS HERE -------------------------------------------------//
-
-
-// async onSubmit(): Promise {
-// if (this.loginForm.invalid) {
-// this.loginForm.markAllAsTouched();
-// return;
-// }
-
-// const { email, password } = this.loginForm.value;
-
-// // β
Await service (since itβs async)
-// (await this.btcService.userLogin(email, password)).subscribe({
-// next: (response) => {
-// const employee = response.body;
-// console.log('π§ Raw employee response:', employee);
-
-// const userName = employee?.userName || 'Unknown User';
-// const employeeId = employee?.userIdNumber || email;
-
-// console.log('β
Parsed name:', userName);
-// console.log('β
Parsed ID:', employeeId);
-
-// this.passwordStatus = true;
-
-// // β
Get the BTID the service fetched
-// const btid = this.btcService.btid;
-// console.log("π¦ BTID from file (via service):", btid);
-
-// // Prepare print data
-// const printData = {
-// name: userName,
-// employeeId: employeeId,
-// action: 'login',
-// type: 'login'
-// };
-
-// // β
Store in localStorage
-// localStorage.setItem('userName', userName);
-// localStorage.setItem('employeeId', employeeId);
-// localStorage.setItem('password', password);
-// localStorage.setItem('btid', btid || "unknown");
-
-// // β
Open second screen + navigate
-// (window as any).electronAPI?.openSecondScreen?.();
-// this.router.navigate(['/home']);
-// },
-// error: () => {
-// this.loginError = 'Invalid login credentials';
-// }
-// });
-// }
showConfirmModal : boolean = false;
diff --git a/btc-UI/src/app/service/btc.service.ts b/btc-UI/src/app/service/btc.service.ts
index c4b0398..ff98076 100755
--- a/btc-UI/src/app/service/btc.service.ts
+++ b/btc-UI/src/app/service/btc.service.ts
@@ -1,70 +1,154 @@
-import { HttpClient, HttpHeaders } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { ApplicationHttpRouts } from '../constants/http-routs';
+// import { HttpClient, HttpHeaders } from '@angular/common/http';
+// import { Injectable } from '@angular/core';
+// import { ApplicationHttpRouts } from '../constants/http-routs';
-@Injectable({
- providedIn: 'root',
-})
+// @Injectable({
+// providedIn: 'root',
+// })
+
+// // export class BtcService {
+// // constructor(private http: HttpClient) {}
+// // btid: string | null = null;
+// // //user login
+// // public userLogin(employeeId: string, password: string) {
+// // console.log('Login route ' + ApplicationHttpRouts.LOG_IN);
+// // this.btid = localStorage.getItem('btid');
+// // if (this.btid) {
+// // employeeId += "," + this.btid;
+// // } else {
+// // employeeId += ",null"; // or just "," if you donβt want "null"
+// // }
+// // return this.http.get(ApplicationHttpRouts.LOG_IN, {
+// // headers: this.basicAuthCredentialsBuilder(employeeId, password),
+// // withCredentials: true,
+// // observe: 'response',
+// // });
+// // }
// export class BtcService {
// constructor(private http: HttpClient) {}
// btid: string | null = null;
-// //user login
-// public userLogin(employeeId: string, password: string) {
-// console.log('Login route ' + ApplicationHttpRouts.LOG_IN);
-// this.btid = localStorage.getItem('btid');
-// if (this.btid) {
-// employeeId += "," + this.btid;
-// } else {
-// employeeId += ",null"; // or just "," if you donβt want "null"
+
+// // user login
+// public async userLogin(employeeId: string, password: string) {
+// console.log('Login route ' + ApplicationHttpRouts.LOG_IN);
+
+// try {
+// if ((window as any).electronAPI?.getBtid) {
+// this.btid = await (window as any).electronAPI.getBtid();
+// } else {
+// console.warn('Electron API not available β fallback to null');
+// this.btid = null;
+// }
+// } catch (err) {
+// console.error('Error fetching BTID:', err);
+// this.btid = null;
+// }
+
+// // Append BTID after comma
+// if (this.btid) {
+// employeeId += ',' + this.btid;
+// } else {
+// employeeId += ',null';
+// }
+
+// return this.http.get(ApplicationHttpRouts.LOG_IN, {
+// headers: this.basicAuthCredentialsBuilder(employeeId, password),
+// withCredentials: true,
+// observe: 'response',
+// });
// }
-// return this.http.get(ApplicationHttpRouts.LOG_IN, {
-// headers: this.basicAuthCredentialsBuilder(employeeId, password),
+
+// // what goes to the backend for auth ... is the same as postman's basic auth
+// private basicAuthCredentialsBuilder(
+// employeeOrUserId: string,
+// password: string
+// ): HttpHeaders {
+// console.log(`username and password${employeeOrUserId} p = ${password}`);
+
+// return new HttpHeaders().set(
+// 'Authorization',
+// 'basic ' + window.btoa(employeeOrUserId + ':' + password)
+// );
+// }
+
+// public pingLiveStatus() {
+// return this.http.get(ApplicationHttpRouts.PING, {
// withCredentials: true,
// observe: 'response',
+// responseType: 'text' as 'json',
// });
// }
+// // Fetch all race events today
+
+// public getAllRaceEventsToday() {
+// return this.http.get(ApplicationHttpRouts.RACE_EVENTS_TODAY, {
+// withCredentials: true,
+// observe: 'response',
+// responseType: 'json',
+// });
+// }
+
+// public getRaceCard(){
+// return this.http.get(ApplicationHttpRouts.RACE_CARD, {
+// withCredentials: true,
+// observe: 'response',
+// responseType: 'json',
+// })
+// }
+// }
+
+
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { ApplicationHttpRouts } from '../constants/http-routs';
+import { of,Observable, tap } from 'rxjs';
+
+@Injectable({
+ providedIn: 'root',
+})
export class BtcService {
constructor(private http: HttpClient) {}
btid: string | null = null;
+ private raceCard: any = null; // cache race card in memory
// user login
public async userLogin(employeeId: string, password: string) {
- console.log('Login route ' + ApplicationHttpRouts.LOG_IN);
+ console.log('Login route ' + ApplicationHttpRouts.LOG_IN);
- try {
- if ((window as any).electronAPI?.getBtid) {
- this.btid = await (window as any).electronAPI.getBtid();
- } else {
- console.warn('Electron API not available β fallback to null');
+ try {
+ if ((window as any).electronAPI?.getBtid) {
+ this.btid = await (window as any).electronAPI.getBtid();
+ } else {
+ console.warn('Electron API not available β fallback to null');
+ this.btid = null;
+ }
+ } catch (err) {
+ console.error('Error fetching BTID:', err);
this.btid = null;
}
- } catch (err) {
- console.error('Error fetching BTID:', err);
- this.btid = null;
+
+ // Append BTID after comma
+ if (this.btid) {
+ employeeId += ',' + this.btid;
+ } else {
+ employeeId += ',null';
+ }
+
+ return this.http.get(ApplicationHttpRouts.LOG_IN, {
+ headers: this.basicAuthCredentialsBuilder(employeeId, password),
+ withCredentials: true,
+ observe: 'response',
+ });
}
- // Append BTID after comma
- if (this.btid) {
- employeeId += ',' + this.btid;
- } else {
- employeeId += ',null';
- }
-
- return this.http.get(ApplicationHttpRouts.LOG_IN, {
- headers: this.basicAuthCredentialsBuilder(employeeId, password),
- withCredentials: true,
- observe: 'response',
- });
-}
-
- // what goes to the backend for auth ... is the same as postman's basic auth
+ // basic auth header
private basicAuthCredentialsBuilder(
employeeOrUserId: string,
password: string
): HttpHeaders {
- console.log(`username and password${employeeOrUserId} p = ${password}`);
+ console.log(`username and password = ${employeeOrUserId} p = ${password}`);
return new HttpHeaders().set(
'Authorization',
@@ -72,6 +156,7 @@ export class BtcService {
);
}
+ // Ping backend
public pingLiveStatus() {
return this.http.get(ApplicationHttpRouts.PING, {
withCredentials: true,
@@ -81,7 +166,6 @@ export class BtcService {
}
// Fetch all race events today
-
public getAllRaceEventsToday() {
return this.http.get(ApplicationHttpRouts.RACE_EVENTS_TODAY, {
withCredentials: true,
@@ -90,11 +174,75 @@ export class BtcService {
});
}
- public getRaceCard(){
- return this.http.get(ApplicationHttpRouts.RACE_CARD, {
- withCredentials: true,
- observe: 'response',
- responseType: 'json',
- })
+ /**
+ * Fetch Race Card from backend (POST /download/rpinfo)
+ * Stores result in memory for reuse
+ // */
+ // public fetchRaceCard(
+ // opCard: string,
+ // password: string,
+ // btId: string,
+ // usrId: string = '',
+ // btMake: number = 0
+ // ): Observable {
+ // const payload = { opCard, password, btId, usrId, btMake };
+
+ // return this.http
+ // .post('http://localhost:8082/download/rpinfo', payload)
+ // .pipe(
+ // tap((data) => {
+ // console.log('π¦ Race Card fetched:', data);
+ // this.raceCard = data; // cache it
+ // })
+ // );
+ // }
+
+ // /**
+ // * Return cached race card (if available)
+ // */
+ // public getRaceCard(): any {
+ // return this.raceCard;
+ // }
+
+
+ public fetchRaceCard(
+ opCard: string,
+ password: string,
+ btId: string,
+ usrId: string = '',
+ btMake: number = 0
+ ): Observable {
+ const payload = { opCard, password, btId, usrId, btMake };
+
+ return this.http
+ .post('http://localhost:8082/download/rpinfo', payload)
+ .pipe(
+ tap((data) => {
+ console.log('π¦ Race Card fetched:', data);
+ this.raceCard = data; // store in memory
+ localStorage.setItem('rpinfo', JSON.stringify(data)); // store in localStorage
+ })
+ );
+ }
+
+ /**
+ * Return cached race card:
+ * - from memory (fastest)
+ * - fallback to localStorage (if reloaded)
+ */
+ public getRaceCard(): Observable {
+ if (this.raceCard) {
+ return of(this.raceCard);
+ }
+
+ const cached = localStorage.getItem('rpinfo');
+ if (cached) {
+ this.raceCard = JSON.parse(cached);
+ return of(this.raceCard);
+ }
+
+ // Nothing available, return empty object
+ return of(null);
}
}
+
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 243038e..f95d127 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -3,50 +3,23 @@ version: '3.8'
# Define the services for our application stack
services:
- # PostgreSQL database service
- postgres:
- # Using the official PostgreSQL base image.
- # 'postgres:16' is recommended for a stable, recent version.
- # You can use 'postgres:latest' for the very newest, but versions like 13, 14, 15, 16 are common.
- image: postgres:16 # Or postgres:latest, postgres:13-alpine, etc.
- container_name: postgres_db # Assign a friendly name to the container
- environment:
- # --- CRITICAL: These settings will ONLY take effect if ./dbData directory is EMPTY on first run ---
- POSTGRES_DB: horse # Your database name
- POSTGRES_USER: postgres # Set to 'postgres', the default superuser for the official image
- POSTGRES_PASSWORD: root # Your desired password for the 'postgres' user
- # --------------------------------------------------------------------------------------------------
- ports:
- - "5434:5432" # Map host port 5434 to container port 5432
- volumes:
- # Using a bind mount. You MUST delete the ./dbData directory manually if you change user/pass/db.
- # This directory MUST be empty when the container first starts to trigger initialization.
- - ./dbData:/var/lib/postgresql/data # Persist PostgreSQL data to a local directory
- # Added command for more verbose logging during startup (optional, but highly recommended here)
- command: postgres -c log_statement=all
- networks:
- - app_network # Connect to our custom application network
- # networks:
- # - app_network # Connect to our custom application network
+
spring:
#image: mathewfrancisv/spring_back_postgres:v1.0.0
- image: mathewfrancisv/btc_cezen_backend:v1.1.6
+ image: mathewfrancisv/btc_cezen_backend:v2.3.0
container_name: spring_app
ports:
- "8083:8080"
environment:
# jdbc:postgresql://localhost:5432/horse
# SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/horse
- SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/horse
- SPRING_DATASOURCE_USERNAME: postgres # Ensure this matches POSTGRES_USER
- SPRING_DATASOURCE_PASSWORD: root # Ensure this matches POSTGRES_PASSWORD
- SPRING_DATASOURCE_CORSIP: http://10.74.231.61:4200
+ SPRING_DATASOURCE_CORSIP: http://10.236.119.124:4200
#network_mode: host
networks:
- app_network
- depends_on:
- - postgres
+ # depends_on:
+ # - postgres
# Angular frontend application service
angular-dev: