feat : electron sync working

This commit is contained in:
karthik 2025-08-17 12:33:05 +05:30
parent 594bd4ae77
commit 8e9f84a2cf
9 changed files with 326 additions and 225 deletions

View File

@ -1,9 +1,11 @@
// main.js
const { app, BrowserWindow, screen, ipcMain } = require('electron'); const { app, BrowserWindow, screen, ipcMain } = require('electron');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
let mainWindow; let mainWindow;
let screenWindow; let screenWindow;
let currentSharedData = null; // Store latest data for initial sync
function createWindows() { function createWindows() {
const displays = screen.getAllDisplays(); const displays = screen.getAllDisplays();
@ -18,7 +20,7 @@ function createWindows() {
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'preload.js'), preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, contextIsolation: true,
} },
}); });
mainWindow.loadURL('http://10.74.231.124:4200/login'); mainWindow.loadURL('http://10.74.231.124:4200/login');
@ -33,15 +35,30 @@ function createWindows() {
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'preload.js'), preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, contextIsolation: true,
} },
}); });
screenWindow.loadURL('http://10.74.231.124:4200/shared-display'); screenWindow.loadURL('http://10.74.231.124:4200/shared-display');
ipcMain.on('open-second-screen', () => screenWindow.show()); // Handle opening second screen and send initial data
ipcMain.on('open-second-screen', () => {
screenWindow.show();
if (currentSharedData && screenWindow.webContents) {
screenWindow.webContents.send('update-shared-data', currentSharedData);
}
});
ipcMain.on('close-second-screen', () => screenWindow.hide()); ipcMain.on('close-second-screen', () => screenWindow.hide());
// ✅ IPC to return BTID // Handle syncing data
ipcMain.on('sync-shared-data', (event, data) => {
currentSharedData = data; // Store latest data
if (screenWindow && screenWindow.webContents) {
screenWindow.webContents.send('update-shared-data', data);
}
});
// Handle BTID request
ipcMain.handle('get-btid', () => { ipcMain.handle('get-btid', () => {
try { try {
const filePath = path.join(process.env.HOME || process.env.USERPROFILE, 'BTID', 'betting.txt'); const filePath = path.join(process.env.HOME || process.env.USERPROFILE, 'BTID', 'betting.txt');
@ -56,4 +73,4 @@ function createWindows() {
} }
app.whenReady().then(createWindows); app.whenReady().then(createWindows);
app.on('window-all-closed', () => app.quit()); app.on('window-all-closed', () => app.quit());

View File

@ -1,9 +1,12 @@
// preload.js
const { contextBridge, ipcRenderer } = require('electron'); const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
openSecondScreen: () => ipcRenderer.send('open-second-screen'), openSecondScreen: () => ipcRenderer.send('open-second-screen'),
closeSecondScreen: () => ipcRenderer.send('close-second-screen'), closeSecondScreen: () => ipcRenderer.send('close-second-screen'),
getBtid: () => ipcRenderer.invoke('get-btid'),
// ✅ Ask main process for Btid // New: Send data to main process
getBtid: () => ipcRenderer.invoke('get-btid') syncSharedData: (data) => ipcRenderer.send('sync-shared-data', data),
// New: Receive data in second window
onUpdateSharedData: (callback) => ipcRenderer.on('update-shared-data', (event, data) => callback(data)),
}); });

View File

@ -1,4 +1,4 @@
import { Component, Input, OnInit, OnDestroy, Output, EventEmitter } from '@angular/core'; import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { SelectionService, SelectionData } from '../selection.service/selection.service'; import { SelectionService, SelectionData } from '../selection.service/selection.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@ -8,17 +8,16 @@ import { Subscription } from 'rxjs';
standalone: true, standalone: true,
imports: [CommonModule], imports: [CommonModule],
templateUrl: './middle-section.component.html', templateUrl: './middle-section.component.html',
styleUrls: ['./middle-section.component.css'] styleUrls: ['./middle-section.component.css'],
}) })
export class MiddleSectionComponent implements OnInit, OnDestroy { export class MiddleSectionComponent implements OnInit, OnDestroy {
@Input() containerHeight: string = '50vh'; @Input() containerHeight: string = '50vh';
@Input() eraseTrigger: any; @Input() eraseTrigger: any;
summaryRows = Array.from({ length: 4 }); summaryRows = Array.from({ length: 4 });
filledRows: SelectionData[] = []; filledRows: SelectionData[] = [];
currentRow: SelectionData = { label: '', numbers: [], value: 0, total: 0 }; currentRow: SelectionData = { label: '', numbers: [], value: 0, total: 0 };
grandTotal: number = 0; grandTotal: number = 0;
salesTotal: number = 0; salesTotal: number = 0;
receiveTotal: number = 0; receiveTotal: number = 0;
totalClicks: number = 0; totalClicks: number = 0;
showConfirmButton: boolean = false; showConfirmButton: boolean = false;
@ -26,7 +25,6 @@ export class MiddleSectionComponent implements OnInit, OnDestroy {
showRepeatTicket: boolean = false; showRepeatTicket: boolean = false;
confirmRepeat: boolean = false; confirmRepeat: boolean = false;
showPrintButton: boolean = false; showPrintButton: boolean = false;
private selections: SelectionData[] = []; private selections: SelectionData[] = [];
private sub1!: Subscription; private sub1!: Subscription;
private sub2!: Subscription; private sub2!: Subscription;
@ -34,12 +32,12 @@ export class MiddleSectionComponent implements OnInit, OnDestroy {
constructor(private selectionService: SelectionService) {} constructor(private selectionService: SelectionService) {}
ngOnInit() { ngOnInit() {
this.sub1 = this.selectionService.selections$.subscribe(data => { this.sub1 = this.selectionService.selections$.subscribe((data) => {
this.selections = data; this.selections = data;
this.updateFilledRows(this.selections, this.currentRow); this.updateFilledRows(this.selections, this.currentRow);
}); });
this.sub2 = this.selectionService.currentRow$.subscribe(row => { this.sub2 = this.selectionService.currentRow$.subscribe((row) => {
this.currentRow = row; this.currentRow = row;
this.updateFilledRows(this.selections, row); this.updateFilledRows(this.selections, row);
}); });
@ -51,130 +49,133 @@ export class MiddleSectionComponent implements OnInit, OnDestroy {
} }
} }
// Helper to sync data to second screen
private syncToSecondScreen() {
if (window.electronAPI && window.electronAPI.syncSharedData) {
window.electronAPI.syncSharedData({
filledRows: this.filledRows,
summaryRows: [
{ col1: 'Sales', col2: this.totalClicks, col3: this.salesTotal },
{ col1: 'Cancel', col2: 0, col3: 0 },
{ col1: 'Payout', col2: 0, col3: 0 },
{ col1: 'Receive', col2: this.totalClicks, col3: this.receiveTotal },
],
grandTotal: this.grandTotal,
salesTotal: this.salesTotal,
receiveTotal: this.receiveTotal,
totalClicks: this.totalClicks,
});
}
}
updateFilledRows(saved: SelectionData[], current: SelectionData) { updateFilledRows(saved: SelectionData[], current: SelectionData) {
const rows: SelectionData[] = [...saved]; const rows: SelectionData[] = [...saved];
if (rows.length < 5 && current.label) rows.push(current); // Include current row if label is selected if (rows.length < 5 && current.label) rows.push(current);
const emptyCount = Math.max(5 - rows.length, 0); const emptyCount = Math.max(5 - rows.length, 0);
const emptyRows = Array.from({ length: emptyCount }, () => ({ const emptyRows = Array.from({ length: emptyCount }, () => ({
label: '', label: '',
numbers: [], numbers: [],
value: 0, value: 0,
total: 0 total: 0,
})); }));
this.filledRows = [...rows, ...emptyRows].slice(0, 5); this.filledRows = [...rows, ...emptyRows].slice(0, 5);
this.calculateTotal(); this.calculateTotal();
// Show Confirm button only if at least one row has data (label and numbers) // Show Confirm button only if at least one row has data (label and numbers)
// and only after repeat is clicked (showRepeatTicket flag) // and only after repeat is clicked (showRepeatTicket flag)
this.showConfirmButton = this.showRepeatTicket && this.filledRows.some( this.showConfirmButton =
row => row.label && row.numbers && row.numbers.length > 0 this.showRepeatTicket &&
); this.filledRows.some((row) => row.label && row.numbers && row.numbers.length > 0);
// Hide Print button if Confirm is hidden // Hide Print button if Confirm is hidden
if (!this.showConfirmButton) { if (!this.showConfirmButton) {
this.showPrintButton = false; this.showPrintButton = false;
} }
this.syncToSecondScreen(); // Sync after updating rows
} }
showSalesTotal: boolean = false; showSalesTotal: boolean = false;
calculateTotal() { calculateTotal() {
this.grandTotal = this.filledRows.reduce((sum, row) => sum + (row.total || 0), 0); this.grandTotal = this.filledRows.reduce((sum, row) => sum + (row.total || 0), 0);
let storedTotal = 0;
let storedTotal = 0; try {
try { const storedTickets = JSON.parse(localStorage.getItem('localTickets') || '[]');
const storedTickets = JSON.parse(localStorage.getItem('localTickets') || '[]'); if (Array.isArray(storedTickets)) {
if (Array.isArray(storedTickets)) { storedTotal = storedTickets
storedTotal = storedTickets .filter((ticket) => ticket.type === 'ticket')
.filter(ticket => ticket.type === 'ticket') .reduce((sum: number, ticket: any) => sum + (ticket.totalAmount || 0), 0);
.reduce((sum: number, ticket: any) => sum + (ticket.totalAmount || 0), 0); }
} catch (e) {
console.error('❌ Failed to parse localTickets from localStorage:', e);
} }
} catch (e) {
console.error('❌ Failed to parse localTickets from localStorage:', e);
}
// 👇 Only show printed totals until print happens // 👇 Only show printed totals until print happens
const hasPrinted = localStorage.getItem('hasPrinted') === 'true'; const hasPrinted = localStorage.getItem('hasPrinted') === 'true';
if (hasPrinted) {
if (hasPrinted) { this.salesTotal = storedTotal;
this.salesTotal = storedTotal; this.receiveTotal = storedTotal;
this.receiveTotal = storedTotal; } else {
} else { this.salesTotal = storedTotal;
this.salesTotal = storedTotal; // do NOT include grandTotal yet this.receiveTotal = storedTotal;
this.receiveTotal = storedTotal; }
this.totalClicks = Number(localStorage.getItem('printClickCount') || '0');
this.showSalesTotal = hasPrinted;
this.syncToSecondScreen(); // Sync after calculating totals
} }
this.totalClicks = Number(localStorage.getItem('printClickCount') || '0');
// 👇 Toggle visibility based on localStorage flag
this.showSalesTotal = localStorage.getItem('hasPrinted') === 'true';
}
repeat() { repeat() {
try { try {
const storedTickets = localStorage.getItem('localTicketsnew'); const storedTickets = localStorage.getItem('localTicketsnew');
if (storedTickets) { if (storedTickets) {
const tickets = JSON.parse(storedTickets); const tickets = JSON.parse(storedTickets);
const latestTicket = Array.isArray(tickets) const latestTicket = Array.isArray(tickets)
? (tickets.length > 0 ? tickets[tickets.length - 1] : null) ? tickets.length > 0
: tickets; ? tickets[tickets.length - 1]
: null
if (latestTicket && latestTicket.winLabels) { : tickets;
// Pass totalAmount as a fallback for missing totals if (latestTicket && latestTicket.winLabels) {
const parsedRows = this.parseWinLabelsToRows( const parsedRows = this.parseWinLabelsToRows(latestTicket.winLabels, latestTicket.totalAmount);
latestTicket.winLabels, // ✅ Always make sure we have exactly 5 rows
latestTicket.totalAmount this.updateFilledRows(parsedRows, { label: '', numbers: [], value: 0, total: 0 });
); console.log('📜 Updated Filled Rows:', parsedRows);
this.showConfirmButton = true;
// ✅ Always make sure we have exactly 5 rows this.showPrintButton = false;
this.updateFilledRows(parsedRows, { label: '', numbers: [], value: 0, total: 0 }); this.lastTicket = latestTicket;
this.showRepeatTicket = true;
console.log('📜 Updated Filled Rows:', parsedRows); this.syncToSecondScreen(); // Sync after repeat
this.showConfirmButton = true; } else {
this.showPrintButton = false; console.warn('⚠️ No valid ticket data found in localStorage.');
this.lastTicket = latestTicket; }
} else { } else {
console.warn('⚠️ No valid ticket data found in localStorage.'); console.warn('⚠️ No tickets found in localStorage.');
} }
} else { } catch (error) {
console.warn('⚠️ No tickets found in localStorage.'); console.error('❌ Failed to load ticket from localStorage:', error);
} }
} catch (error) {
console.error('❌ Failed to load ticket from localStorage:', error);
} }
}
parseWinLabelsToRows(winLabels: string, fallbackTotal?: number): SelectionData[] { parseWinLabelsToRows(winLabels: string, fallbackTotal?: number): SelectionData[] {
return (winLabels.split('\n') as string[]) return (winLabels.split('\n') as string[])
.map(line => { .map((line) => {
// Match: LABEL NUMBERS *VALUE [Rs TOTAL optional] const match = line.match(/^(\S+)\s+(.+?)\s+\*([\d.]+)(?:\s*(?:Rs)?\s*(\d+))?/);
const match = line.match(/^(\S+)\s+(.+?)\s+\*([\d.]+)(?:\s*(?:Rs)?\s*(\d+))?/);
if (!match) return null; if (!match) return null;
const label = match[1].trim();
const label = match[1].trim(); const numbersRaw = match[2].trim();
const numbersRaw = match[2].trim(); const value = Number(match[3]);
const value = Number(match[3]); const total = match[4] ? Number(match[4]) : fallbackTotal || 0;
const total = match[4] ? Number(match[4]) : (fallbackTotal || 0);
// If multi-leg ticket, split each part separately // If multi-leg ticket, split each part separately
let numbers: string[] | string[][]; let numbers: string[] | string[][];
if (numbersRaw.includes('/')) { if (numbersRaw.includes('/')) {
numbers = numbersRaw numbers = numbersRaw
.split('/') .split('/')
.map(part => part.split(',').map(s => s.trim()).filter(Boolean)); .map((part) => part.split(',').map((s) => s.trim()).filter(Boolean));
} else { } else {
numbers = numbersRaw.split(',').map(s => s.trim()).filter(Boolean); numbers = numbersRaw.split(',').map((s) => s.trim()).filter(Boolean);
} }
return { return {
label, label,
numbers, numbers,
value, value,
total, total,
isBoxed: numbersRaw.startsWith('#') isBoxed: numbersRaw.startsWith('#'),
}; };
}) })
.filter(Boolean) as SelectionData[]; .filter(Boolean) as SelectionData[];
@ -183,29 +184,26 @@ parseWinLabelsToRows(winLabels: string, fallbackTotal?: number): SelectionData[]
confirm() { confirm() {
this.showPrintButton = true; // Show Print button after confirming this.showPrintButton = true; // Show Print button after confirming
console.log('✅ [DEBUG] Ticket confirmed.'); console.log('✅ [DEBUG] Ticket confirmed.');
this.syncToSecondScreen(); // Sync after confirm
} }
//-----------------------REPEAT PRINT LOGIC ----------------------------------
printRepeat() { printRepeat() {
console.log('🖨️ [DEBUG] Printing ticket...'); console.log('🖨️ [DEBUG] Printing ticket...');
const userName = localStorage.getItem('userName') || 'Unknown';
const userName = localStorage.getItem('userName') || 'Unknown'; const now = new Date();
const day = String(now.getDate()).padStart(2, '0');
// Keep currentDate as a Date object const month = String(now.getMonth() + 1).padStart(2, '0');
const now = new Date(); const year = String(now.getFullYear()).slice(-2);
const timeStr = `${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(
// 🆕 Create Barcode ID (same combo format) 2,
const day = String(now.getDate()).padStart(2, '0'); '0'
const month = String(now.getMonth() + 1).padStart(2, '0'); )}${String(now.getSeconds()).padStart(2, '0')}`;
const year = String(now.getFullYear()).slice(-2); const millis = String(now.getMilliseconds()).padStart(3, '0');
const timeStr = `${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`; const barcodeId = `1111${day}${month}${year}${timeStr}${millis}`;
const millis = String(now.getMilliseconds()).padStart(3, '0');
const barcodeId = `1111${day}${month}${year}${timeStr}${millis}`;
const printableRows = this.filledRows const printableRows = this.filledRows
.filter(row => row.label && row.numbers.length > 0 && row.total > 0) .filter((row) => row.label && row.numbers.length > 0 && row.total > 0)
.map(row => { .map((row) => {
const horses = Array.isArray(row.numbers) ? row.numbers.join(',') : row.numbers; const horses = Array.isArray(row.numbers) ? row.numbers.join(',') : row.numbers;
return `${row.label.padEnd(6)} ${horses.padEnd(15)} * ${String(row.value).padEnd(3)}${row.total}`; return `${row.label.padEnd(6)} ${horses.padEnd(15)} * ${String(row.value).padEnd(3)}${row.total}`;
}) })
@ -214,7 +212,6 @@ parseWinLabelsToRows(winLabels: string, fallbackTotal?: number): SelectionData[]
const ticketContent = ` const ticketContent = `
BTC Race Ticket BTC Race Ticket
${printableRows} ${printableRows}
Barcode ID : ${barcodeId} Barcode ID : ${barcodeId}
Printed by : ${userName} Printed by : ${userName}
Date : ${now.toLocaleString()}`; Date : ${now.toLocaleString()}`;
@ -224,27 +221,27 @@ Date : ${now.toLocaleString()}`;
// ✅ Send to print server // ✅ Send to print server
const payload = { const payload = {
type: 'repeat', type: 'repeat',
printedBy: userName, printedBy: userName,
barcodeId, // send barcode separately barcodeId,
content: ticketContent, content: ticketContent,
timestamp: now.toLocaleString() timestamp: now.toLocaleString(),
}; };
fetch('http://localhost:9100/print', { fetch('http://localhost:9100/print', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload) body: JSON.stringify(payload),
}) })
.then(response => { .then((response) => {
if (!response.ok) throw new Error(`Printer error: ${response.status}`); if (!response.ok) throw new Error(`Printer error: ${response.status}`);
return response.text(); return response.text();
}) })
.then(result => { .then((result) => {
console.log("✅ Repeat ticket print successful:", result); console.log('✅ Repeat ticket print successful:', result);
}) })
.catch(error => { .catch((error) => {
console.error("❌ Repeat ticket print failed:", error); console.error('❌ Repeat ticket print failed:', error);
}); });
// --- Update localStorage for transaction summary like normal print --- // --- Update localStorage for transaction summary like normal print ---
// 1. Increment printClickCount // 1. Increment printClickCount
@ -258,14 +255,13 @@ Date : ${now.toLocaleString()}`;
const existingTickets = existingTicketsStr ? JSON.parse(existingTicketsStr) : []; const existingTickets = existingTicketsStr ? JSON.parse(existingTicketsStr) : [];
// Calculate totalAmount for this repeat print // Calculate totalAmount for this repeat print
const totalAmount = this.filledRows const totalAmount = this.filledRows
.filter(row => row.label && row.numbers.length > 0 && row.total > 0) .filter((row) => row.label && row.numbers.length > 0 && row.total > 0)
.reduce((sum, row) => sum + (row.total || 0), 0); .reduce((sum, row) => sum + (row.total || 0), 0);
const ticketEntry = { const ticketEntry = {
type: 'ticket', type: 'ticket',
printedBy: userName, printedBy: userName,
totalAmount, totalAmount,
content: ticketContent, content: ticketContent,
}; };
existingTickets.push(ticketEntry); existingTickets.push(ticketEntry);
localStorage.setItem('localTickets', JSON.stringify(existingTickets)); localStorage.setItem('localTickets', JSON.stringify(existingTickets));
@ -276,25 +272,24 @@ Date : ${now.toLocaleString()}`;
// Hide Confirm and Print buttons before clearing selections // Hide Confirm and Print buttons before clearing selections
this.showConfirmButton = false; this.showConfirmButton = false;
this.showPrintButton = false; this.showPrintButton = false;
this.erase();
this.erase(); // Clear selections after hiding buttons this.syncToSecondScreen(); // Sync after print
}
// 👉 Recalculate totals after erase to update transaction summary
this.calculateTotal();
}
//------------------------------PRINT REPEAT ENDED HERE-----------------------------------------------------S //------------------------------PRINT REPEAT ENDED HERE-----------------------------------------------------S
erase() { erase() {
this.selectionService.clearSelections(); this.selectionService.clearSelections();
this.resetSelections(); this.resetSelections();
// 👉 Also recalculate totals after erase to keep summary in sync // 👉 Also recalculate totals after erase to keep summary in sync
this.calculateTotal(); this.calculateTotal();
this.syncToSecondScreen(); // Sync after erase
} }
resetSelections() { resetSelections() {
// No-op: Prevent error and allow summary to update // No-op: Prevent error and allow summary to update
// (If you want to reset any local state, do it here) // (If you want to reset any local state, do it here)
} }
ngOnDestroy() { ngOnDestroy() {
this.sub1.unsubscribe(); this.sub1.unsubscribe();
this.sub2.unsubscribe(); this.sub2.unsubscribe();

View File

@ -8,8 +8,11 @@
<!-- Tables for second screen --> <!-- Tables for second screen -->
<app-shared-table <!-- shared-display.component.html -->
[summaryRows]="summaryRows" <div class="wrapper">
[rows]="rows" <app-shared-table
[totalAmount]="totalAmount" [summaryRows]="summaryRows"
></app-shared-table> [rows]="filledRows"
[totalAmount]="grandTotal.toString()"
></app-shared-table>
</div>

View File

@ -1,45 +1,84 @@
// shared-display.component.ts
import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { SharedStateService } from '../../service/shared-state.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { SharedStateService } from '../../service/shared-state.service';
import { SharedTableComponent } from '../shared-table/shared-table.component'; import { SharedTableComponent } from '../shared-table/shared-table.component';
import { SelectionData } from '../selection.service/selection.service';
import { FormatNumbersPipe } from '../../../format-numbers.pipe';
interface SharedData {
filledRows: SelectionData[];
summaryRows: { col1: string; col2: number; col3: number }[];
grandTotal: number;
salesTotal: number;
receiveTotal: number;
totalClicks: number;
}
@Component({ @Component({
selector: 'app-shared-display', selector: 'app-shared-display',
standalone: true, standalone: true,
imports: [CommonModule, SharedTableComponent], imports: [CommonModule, SharedTableComponent, FormatNumbersPipe],
templateUrl: './shared-display.component.html', templateUrl: './shared-display.component.html',
styleUrls: ['./shared-display.component.css'], styleUrls: ['./shared-display.component.css'],
}) })
export class SharedDisplayComponent implements OnInit { export class SharedDisplayComponent implements OnInit {
selectedVenue: string = 'Select Venue'; selectedVenue: string = 'Select Venue';
selectedRace: string = '1'; selectedRace: string = '1';
summaryRows = Array.from({ length: 4 }, () => ({ col1: '', col2: '', col3: '' })); summaryRows: { col1: string; col2: number; col3: number }[] = [
rows = Array.from({ length: 5 }, () => ({ col1: '', col2: '', col3: '', col4: '' })); { col1: 'Sales', col2: 0, col3: 0 },
totalAmount = ''; { col1: 'Cancel', col2: 0, col3: 0 },
{ col1: 'Payout', col2: 0, col3: 0 },
{ col1: 'Receive', col2: 0, col3: 0 },
];
filledRows: SelectionData[] = Array.from({ length: 5 }, () => ({
label: '',
numbers: [],
value: 0,
total: 0,
}));
grandTotal: number = 0;
salesTotal: number = 0;
receiveTotal: number = 0;
totalClicks: number = 0;
constructor( constructor(
private sharedStateService: SharedStateService, private sharedStateService: SharedStateService,
private cdRef: ChangeDetectorRef private cdRef: ChangeDetectorRef
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
console.log('[SHARED DISPLAY] Initializing, electronAPI available:', !!window.electronAPI);
this.sharedStateService.sharedData$.subscribe((data) => { this.sharedStateService.sharedData$.subscribe((data) => {
console.log('[SHARED DISPLAY] Received shared data:', data); console.log('[SHARED DISPLAY] Received shared data:', data);
if (!data) return; if (!data) return;
if (data.type === 'selectedVenue') { if (data.type === 'selectedVenue') {
this.selectedVenue = data.value; this.selectedVenue = data.value;
console.log('[SHARED DISPLAY] Venue updated to:', this.selectedVenue); console.log('[SHARED DISPLAY] Venue updated to:', this.selectedVenue);
} }
if (data.type === 'selectedRace') { if (data.type === 'selectedRace') {
this.selectedRace = data.value; this.selectedRace = data.value;
console.log('[SHARED DISPLAY] Race updated to:', this.selectedRace); console.log('[SHARED DISPLAY] Race updated to:', this.selectedRace);
} }
this.cdRef.detectChanges();
this.cdRef.detectChanges(); // Force UI update
}); });
}
}
if (window.electronAPI && window.electronAPI.onUpdateSharedData) {
window.electronAPI.onUpdateSharedData((data: SharedData) => {
console.log('[SHARED DISPLAY] Received IPC data:', data);
if (!data) return;
this.filledRows = data.filledRows || this.filledRows;
this.summaryRows = data.summaryRows || this.summaryRows;
this.grandTotal = data.grandTotal || 0;
this.salesTotal = data.salesTotal || 0;
this.receiveTotal = data.receiveTotal || 0;
this.totalClicks = data.totalClicks || 0;
console.log('[SHARED DISPLAY] Updated filledRows:', this.filledRows.map(row => ({ ...row, numbers: JSON.stringify(row.numbers) })));
this.cdRef.detectChanges();
});
} else {
console.error('[SHARED DISPLAY] electronAPI or onUpdateSharedData not available');
}
}
}

View File

@ -1,51 +1,59 @@
<!-- shared-table.component.html -->
<div class="wrapper"> <div class="wrapper">
<div class="middle-section-container container-fluid mt-3 px-4"> <div class="middle-section-container container-fluid mt-3 px-4">
<div class="transaction-summary mb-3"> <!-- Transaction Summary -->
<div class="p-3 rounded text-white h-100 d-flex flex-column" style="background-color: #546c98"> <div class="transaction-summary mb-3">
<h5 class="text-center mb-3">Transaction Summary</h5> <div class="p-3 rounded text-white h-100 d-flex flex-column" style="background-color: #546c98">
<h5 class="text-center mb-3">Transaction Summary</h5>
<div class="rounded flex-grow-1" style="background-color: #f1f1f1df; padding: 1rem; overflow: hidden">
<table class="table borderless-custom w-100 text-dark mb-0 transaction_tb">
<colgroup>
<col style="width: 20%" />
<col style="width: 15%" />
<col style="width: 65%" />
</colgroup>
<tbody>
<tr *ngFor="let row of summaryRows">
<td class="custom-cell pure-white col-20">{{ row.col1 }}</td>
<td class="custom-cell pure-white col-15">{{ row.col2 }}</td>
<td class="custom-cell pure-white col-65">{{ row.col3 }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="rounded flex-grow-1" style="background-color: #f1f1f1df; padding: 1rem; overflow: hidden"> <!-- Main Table -->
<table class="table borderless-custom w-100 text-dark mb-0 transaction_tb"> <div class="main-table">
<tbody> <div class="p-2 rounded h-100 d-flex flex-column justify-content-between" style="background-color: #f1f1f1df">
<tr *ngFor="let row of summaryRows"> <table class="table borderless-custom w-100 mb-2 table-main">
<td class="custom-cell pure-white col-20">{{ row.col1 }}</td> <colgroup>
<td class="custom-cell pure-white col-15">{{ row.col2 }}</td> <col style="width: 12%" />
<td class="custom-cell pure-white col-65">{{ row.col3 }}</td> <col style="width: 60%" />
</tr> <col style="width: 10%" />
</tbody> <col style="width: 18%" />
</table> </colgroup>
<tbody>
<tr *ngFor="let row of rows">
<td class="custom-cell-new">{{ row.label }}</td>
<td class="custom-cell-new">
{{ row.isBoxed ? '# ' : '' }}{{ $any(row.numbers) | formatNumbers }}
</td>
<td class="custom-cell-new">{{ row.value || '' }}</td>
<td class="custom-cell-new">{{ row.total || '' }}</td>
</tr>
</tbody>
</table>
<div class="buttons-custom d-flex justify-content-between align-items-center px-3">
<div></div>
<div class="fw-bold fs-5">Amount : ₹ {{ totalAmount }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="main-table"> <footer class="footer" [ngStyle]="{ 'background-color': isConnected ? '#d1ffd1' : '#ffcccc' }">
<div class="p-2 rounded h-100 d-flex flex-column justify-content-between" style="background-color: #f1f1f1df"> <div class="live-data-text">Live Data: {{ message || 'Disconnected' }}</div>
<table class="table borderless-custom w-100 mb-2 table-main">
<tbody>
<tr *ngFor="let row of rows">
<td class="custom-cell-new">{{ row.col1 }}</td>
<td class="custom-cell-new">{{ row.col2 }}</td>
<td class="custom-cell-new">{{ row.col3 }}</td>
<td class="custom-cell-new">{{ row.col4 }}</td>
</tr>
</tbody>
</table>
<div class="buttons-custom d-flex justify-content-between align-items-center px-3">
<div class="fw-bold">Amount : {{ totalAmount }}</div>
</div>
</div>
</div>
</div>
<!-- <footer class="footer">
<div class="live-data-text">Live Data: </div>
</footer> </footer>
</div>
</div> -->
<footer class="footer" [ngStyle]="{ 'background-color': isConnected ? '#d1ffd1' : '#ffcccc' }">
<div class="live-data-text">
Live Data: {{ message || 'Disconnected' }}
</div>
</footer>

View File

@ -1,22 +1,20 @@
import { Component, Input } from '@angular/core'; // shared-table.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { WebsocketService } from '../../service/websocket.service'; // adjust path if needed import { SelectionData } from '../selection.service/selection.service';
import { FormatNumbersPipe } from '../../../format-numbers.pipe';
import { WebsocketService } from '../../service/websocket.service'; // Adjust path as needed
@Component({ @Component({
selector: 'app-shared-table', selector: 'app-shared-table',
standalone: true, standalone: true,
imports: [CommonModule], imports: [CommonModule, FormatNumbersPipe],
templateUrl: './shared-table.component.html', templateUrl: './shared-table.component.html',
styleUrls: ['./shared-table.component.css'] styleUrls: ['./shared-table.component.css'],
}) })
// export class SharedTableComponent { export class SharedTableComponent implements OnInit {
// @Input() summaryRows: any[] = []; @Input() summaryRows: { col1: string; col2: number; col3: number }[] = [];
// @Input() rows: any[] = []; @Input() rows: SelectionData[] = [];
// @Input() totalAmount: string = '';
// }
export class SharedTableComponent {
@Input() summaryRows: any[] = [];
@Input() rows: any[] = [];
@Input() totalAmount: string = ''; @Input() totalAmount: string = '';
message = ''; message = '';
@ -25,12 +23,14 @@ export class SharedTableComponent {
constructor(private websocketService: WebsocketService) {} constructor(private websocketService: WebsocketService) {}
ngOnInit() { ngOnInit() {
this.websocketService.message$.subscribe(msg => { this.websocketService.message$.subscribe((msg) => {
this.message = msg; this.message = msg;
console.log('[SHARED TABLE] WebSocket message:', msg);
}); });
this.websocketService.isConnected$.subscribe(status => { this.websocketService.isConnected$.subscribe((status) => {
this.isConnected = status; this.isConnected = status;
console.log('[SHARED TABLE] WebSocket connection status:', status);
}); });
} }
} }

21
btc-UI/src/electron.d.ts vendored Normal file
View File

@ -0,0 +1,21 @@
// src/electron.d.ts
interface ElectronAPI {
openSecondScreen: () => void;
closeSecondScreen: () => void;
getBtid: () => Promise<string | null>;
syncSharedData: (data: SharedData) => void;
onUpdateSharedData: (callback: (data: SharedData) => void) => void;
}
interface SharedData {
filledRows: SelectionData[];
summaryRows: { col1: string; col2: number; col3: number }[];
grandTotal: number;
salesTotal: number;
receiveTotal: number;
totalClicks: number;
}
interface Window {
electronAPI: ElectronAPI;
}

View File

@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'formatNumbers',
standalone: true,
})
export class FormatNumbersPipe implements PipeTransform {
transform(values: (string | number)[]): string {
if (!values || !Array.isArray(values)) {
return '';
}
return values.map((val) => val.toString()).join(',');
}
}