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 path = require('path');
const fs = require('fs');
let mainWindow;
let screenWindow;
let currentSharedData = null; // Store latest data for initial sync
function createWindows() {
const displays = screen.getAllDisplays();
@ -18,7 +20,7 @@ function createWindows() {
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
}
},
});
mainWindow.loadURL('http://10.74.231.124:4200/login');
@ -33,15 +35,30 @@ function createWindows() {
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
}
},
});
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());
// ✅ 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', () => {
try {
const filePath = path.join(process.env.HOME || process.env.USERPROFILE, 'BTID', 'betting.txt');
@ -56,4 +73,4 @@ function 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');
contextBridge.exposeInMainWorld('electronAPI', {
openSecondScreen: () => ipcRenderer.send('open-second-screen'),
closeSecondScreen: () => ipcRenderer.send('close-second-screen'),
// ✅ Ask main process for Btid
getBtid: () => ipcRenderer.invoke('get-btid')
getBtid: () => ipcRenderer.invoke('get-btid'),
// New: Send data to main process
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 { SelectionService, SelectionData } from '../selection.service/selection.service';
import { Subscription } from 'rxjs';
@ -8,17 +8,16 @@ import { Subscription } from 'rxjs';
standalone: true,
imports: [CommonModule],
templateUrl: './middle-section.component.html',
styleUrls: ['./middle-section.component.css']
styleUrls: ['./middle-section.component.css'],
})
export class MiddleSectionComponent implements OnInit, OnDestroy {
@Input() containerHeight: string = '50vh';
@Input() eraseTrigger: any;
summaryRows = Array.from({ length: 4 });
filledRows: SelectionData[] = [];
currentRow: SelectionData = { label: '', numbers: [], value: 0, total: 0 };
grandTotal: number = 0;
salesTotal: number = 0;
salesTotal: number = 0;
receiveTotal: number = 0;
totalClicks: number = 0;
showConfirmButton: boolean = false;
@ -26,7 +25,6 @@ export class MiddleSectionComponent implements OnInit, OnDestroy {
showRepeatTicket: boolean = false;
confirmRepeat: boolean = false;
showPrintButton: boolean = false;
private selections: SelectionData[] = [];
private sub1!: Subscription;
private sub2!: Subscription;
@ -34,12 +32,12 @@ export class MiddleSectionComponent implements OnInit, OnDestroy {
constructor(private selectionService: SelectionService) {}
ngOnInit() {
this.sub1 = this.selectionService.selections$.subscribe(data => {
this.sub1 = this.selectionService.selections$.subscribe((data) => {
this.selections = data;
this.updateFilledRows(this.selections, this.currentRow);
});
this.sub2 = this.selectionService.currentRow$.subscribe(row => {
this.sub2 = this.selectionService.currentRow$.subscribe((row) => {
this.currentRow = 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) {
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 emptyRows = Array.from({ length: emptyCount }, () => ({
label: '',
numbers: [],
value: 0,
total: 0
total: 0,
}));
this.filledRows = [...rows, ...emptyRows].slice(0, 5);
this.calculateTotal();
// Show Confirm button only if at least one row has data (label and numbers)
// and only after repeat is clicked (showRepeatTicket flag)
this.showConfirmButton = this.showRepeatTicket && this.filledRows.some(
row => row.label && row.numbers && row.numbers.length > 0
);
// and only after repeat is clicked (showRepeatTicket flag)
this.showConfirmButton =
this.showRepeatTicket &&
this.filledRows.some((row) => row.label && row.numbers && row.numbers.length > 0);
// Hide Print button if Confirm is hidden
if (!this.showConfirmButton) {
this.showPrintButton = false;
}
this.syncToSecondScreen(); // Sync after updating rows
}
showSalesTotal: boolean = false;
calculateTotal() {
this.grandTotal = this.filledRows.reduce((sum, row) => sum + (row.total || 0), 0);
let storedTotal = 0;
try {
const storedTickets = JSON.parse(localStorage.getItem('localTickets') || '[]');
if (Array.isArray(storedTickets)) {
storedTotal = storedTickets
.filter(ticket => ticket.type === 'ticket')
.reduce((sum: number, ticket: any) => sum + (ticket.totalAmount || 0), 0);
this.grandTotal = this.filledRows.reduce((sum, row) => sum + (row.total || 0), 0);
let storedTotal = 0;
try {
const storedTickets = JSON.parse(localStorage.getItem('localTickets') || '[]');
if (Array.isArray(storedTickets)) {
storedTotal = storedTickets
.filter((ticket) => ticket.type === 'ticket')
.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
const hasPrinted = localStorage.getItem('hasPrinted') === 'true';
if (hasPrinted) {
this.salesTotal = storedTotal;
this.receiveTotal = storedTotal;
} else {
this.salesTotal = storedTotal; // do NOT include grandTotal yet
this.receiveTotal = storedTotal;
const hasPrinted = localStorage.getItem('hasPrinted') === 'true';
if (hasPrinted) {
this.salesTotal = storedTotal;
this.receiveTotal = storedTotal;
} else {
this.salesTotal = 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() {
try {
const storedTickets = localStorage.getItem('localTicketsnew');
if (storedTickets) {
const tickets = JSON.parse(storedTickets);
const latestTicket = Array.isArray(tickets)
? (tickets.length > 0 ? tickets[tickets.length - 1] : null)
: tickets;
if (latestTicket && latestTicket.winLabels) {
// Pass totalAmount as a fallback for missing totals
const parsedRows = this.parseWinLabelsToRows(
latestTicket.winLabels,
latestTicket.totalAmount
);
// ✅ Always make sure we have exactly 5 rows
this.updateFilledRows(parsedRows, { label: '', numbers: [], value: 0, total: 0 });
console.log('📜 Updated Filled Rows:', parsedRows);
this.showConfirmButton = true;
this.showPrintButton = false;
this.lastTicket = latestTicket;
try {
const storedTickets = localStorage.getItem('localTicketsnew');
if (storedTickets) {
const tickets = JSON.parse(storedTickets);
const latestTicket = Array.isArray(tickets)
? tickets.length > 0
? tickets[tickets.length - 1]
: null
: tickets;
if (latestTicket && latestTicket.winLabels) {
const parsedRows = this.parseWinLabelsToRows(latestTicket.winLabels, latestTicket.totalAmount);
// ✅ Always make sure we have exactly 5 rows
this.updateFilledRows(parsedRows, { label: '', numbers: [], value: 0, total: 0 });
console.log('📜 Updated Filled Rows:', parsedRows);
this.showConfirmButton = true;
this.showPrintButton = false;
this.lastTicket = latestTicket;
this.showRepeatTicket = true;
this.syncToSecondScreen(); // Sync after repeat
} else {
console.warn('⚠️ No valid ticket data found in localStorage.');
}
} else {
console.warn('⚠️ No valid ticket data found in localStorage.');
console.warn('⚠️ No tickets found in localStorage.');
}
} else {
console.warn('⚠️ No tickets found in localStorage.');
} catch (error) {
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[])
.map(line => {
// Match: LABEL NUMBERS *VALUE [Rs TOTAL optional]
const match = line.match(/^(\S+)\s+(.+?)\s+\*([\d.]+)(?:\s*(?:Rs)?\s*(\d+))?/);
.map((line) => {
const match = line.match(/^(\S+)\s+(.+?)\s+\*([\d.]+)(?:\s*(?:Rs)?\s*(\d+))?/);
if (!match) return null;
const label = match[1].trim();
const numbersRaw = match[2].trim();
const value = Number(match[3]);
const total = match[4] ? Number(match[4]) : (fallbackTotal || 0);
const label = match[1].trim();
const numbersRaw = match[2].trim();
const value = Number(match[3]);
const total = match[4] ? Number(match[4]) : fallbackTotal || 0;
// If multi-leg ticket, split each part separately
let numbers: string[] | string[][];
if (numbersRaw.includes('/')) {
numbers = numbersRaw
.split('/')
.map(part => part.split(',').map(s => s.trim()).filter(Boolean));
} else {
numbers = numbersRaw.split(',').map(s => s.trim()).filter(Boolean);
}
let numbers: string[] | string[][];
if (numbersRaw.includes('/')) {
numbers = numbersRaw
.split('/')
.map((part) => part.split(',').map((s) => s.trim()).filter(Boolean));
} else {
numbers = numbersRaw.split(',').map((s) => s.trim()).filter(Boolean);
}
return {
label,
numbers,
value,
total,
isBoxed: numbersRaw.startsWith('#')
label,
numbers,
value,
total,
isBoxed: numbersRaw.startsWith('#'),
};
})
.filter(Boolean) as SelectionData[];
@ -183,29 +184,26 @@ parseWinLabelsToRows(winLabels: string, fallbackTotal?: number): SelectionData[]
confirm() {
this.showPrintButton = true; // Show Print button after confirming
console.log('✅ [DEBUG] Ticket confirmed.');
this.syncToSecondScreen(); // Sync after confirm
}
//-----------------------REPEAT PRINT LOGIC ----------------------------------
printRepeat() {
console.log('🖨️ [DEBUG] Printing ticket...');
const userName = localStorage.getItem('userName') || 'Unknown';
// Keep currentDate as a Date object
const now = new Date();
// 🆕 Create Barcode ID (same combo format)
const day = String(now.getDate()).padStart(2, '0');
const month = String(now.getMonth() + 1).padStart(2, '0');
const year = String(now.getFullYear()).slice(-2);
const timeStr = `${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
const millis = String(now.getMilliseconds()).padStart(3, '0');
const barcodeId = `1111${day}${month}${year}${timeStr}${millis}`;
const userName = localStorage.getItem('userName') || 'Unknown';
const now = new Date();
const day = String(now.getDate()).padStart(2, '0');
const month = String(now.getMonth() + 1).padStart(2, '0');
const year = String(now.getFullYear()).slice(-2);
const timeStr = `${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(
2,
'0'
)}${String(now.getSeconds()).padStart(2, '0')}`;
const millis = String(now.getMilliseconds()).padStart(3, '0');
const barcodeId = `1111${day}${month}${year}${timeStr}${millis}`;
const printableRows = this.filledRows
.filter(row => row.label && row.numbers.length > 0 && row.total > 0)
.map(row => {
.filter((row) => row.label && row.numbers.length > 0 && row.total > 0)
.map((row) => {
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}`;
})
@ -214,7 +212,6 @@ parseWinLabelsToRows(winLabels: string, fallbackTotal?: number): SelectionData[]
const ticketContent = `
BTC Race Ticket
${printableRows}
Barcode ID : ${barcodeId}
Printed by : ${userName}
Date : ${now.toLocaleString()}`;
@ -224,27 +221,27 @@ Date : ${now.toLocaleString()}`;
// ✅ Send to print server
const payload = {
type: 'repeat',
printedBy: userName,
barcodeId, // send barcode separately
printedBy: userName,
barcodeId,
content: ticketContent,
timestamp: now.toLocaleString()
timestamp: now.toLocaleString(),
};
fetch('http://localhost:9100/print', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
body: JSON.stringify(payload),
})
.then(response => {
if (!response.ok) throw new Error(`Printer error: ${response.status}`);
return response.text();
})
.then(result => {
console.log("✅ Repeat ticket print successful:", result);
})
.catch(error => {
console.error("❌ Repeat ticket print failed:", error);
});
.then((response) => {
if (!response.ok) throw new Error(`Printer error: ${response.status}`);
return response.text();
})
.then((result) => {
console.log('✅ Repeat ticket print successful:', result);
})
.catch((error) => {
console.error('❌ Repeat ticket print failed:', error);
});
// --- Update localStorage for transaction summary like normal print ---
// 1. Increment printClickCount
@ -258,14 +255,13 @@ Date : ${now.toLocaleString()}`;
const existingTickets = existingTicketsStr ? JSON.parse(existingTicketsStr) : [];
// Calculate totalAmount for this repeat print
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);
const ticketEntry = {
type: 'ticket',
printedBy: userName,
totalAmount,
content: ticketContent,
};
existingTickets.push(ticketEntry);
localStorage.setItem('localTickets', JSON.stringify(existingTickets));
@ -276,25 +272,24 @@ Date : ${now.toLocaleString()}`;
// Hide Confirm and Print buttons before clearing selections
this.showConfirmButton = false;
this.showPrintButton = false;
this.erase(); // Clear selections after hiding buttons
// 👉 Recalculate totals after erase to update transaction summary
this.calculateTotal();
}
this.erase();
this.syncToSecondScreen(); // Sync after print
}
//------------------------------PRINT REPEAT ENDED HERE-----------------------------------------------------S
erase() {
erase() {
this.selectionService.clearSelections();
this.resetSelections();
// 👉 Also recalculate totals after erase to keep summary in sync
this.calculateTotal();
this.syncToSecondScreen(); // Sync after erase
}
resetSelections() {
// No-op: Prevent error and allow summary to update
// (If you want to reset any local state, do it here)
}
ngOnDestroy() {
this.sub1.unsubscribe();
this.sub2.unsubscribe();

View File

@ -8,8 +8,11 @@
<!-- Tables for second screen -->
<app-shared-table
[summaryRows]="summaryRows"
[rows]="rows"
[totalAmount]="totalAmount"
></app-shared-table>
<!-- shared-display.component.html -->
<div class="wrapper">
<app-shared-table
[summaryRows]="summaryRows"
[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 { SharedStateService } from '../../service/shared-state.service';
import { CommonModule } from '@angular/common';
import { SharedStateService } from '../../service/shared-state.service';
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({
selector: 'app-shared-display',
standalone: true,
imports: [CommonModule, SharedTableComponent],
imports: [CommonModule, SharedTableComponent, FormatNumbersPipe],
templateUrl: './shared-display.component.html',
styleUrls: ['./shared-display.component.css'],
})
export class SharedDisplayComponent implements OnInit {
selectedVenue: string = 'Select Venue';
selectedRace: string = '1';
summaryRows = Array.from({ length: 4 }, () => ({ col1: '', col2: '', col3: '' }));
rows = Array.from({ length: 5 }, () => ({ col1: '', col2: '', col3: '', col4: '' }));
totalAmount = '';
summaryRows: { col1: string; col2: number; col3: number }[] = [
{ col1: 'Sales', col2: 0, col3: 0 },
{ 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(
private sharedStateService: SharedStateService,
private cdRef: ChangeDetectorRef
) {}
ngOnInit(): void {
console.log('[SHARED DISPLAY] Initializing, electronAPI available:', !!window.electronAPI);
this.sharedStateService.sharedData$.subscribe((data) => {
console.log('[SHARED DISPLAY] Received shared data:', data);
if (!data) return;
if (data.type === 'selectedVenue') {
this.selectedVenue = data.value;
console.log('[SHARED DISPLAY] Venue updated to:', this.selectedVenue);
}
if (data.type === 'selectedRace') {
this.selectedRace = data.value;
console.log('[SHARED DISPLAY] Race updated to:', this.selectedRace);
}
this.cdRef.detectChanges(); // Force UI update
this.cdRef.detectChanges();
});
}
}
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="middle-section-container container-fluid mt-3 px-4">
<div class="transaction-summary mb-3">
<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="middle-section-container container-fluid mt-3 px-4">
<!-- Transaction Summary -->
<div class="transaction-summary mb-3">
<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">
<table class="table borderless-custom w-100 text-dark mb-0 transaction_tb">
<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>
<!-- Main Table -->
<div class="main-table">
<div class="p-2 rounded h-100 d-flex flex-column justify-content-between" style="background-color: #f1f1f1df">
<table class="table borderless-custom w-100 mb-2 table-main">
<colgroup>
<col style="width: 12%" />
<col style="width: 60%" />
<col style="width: 10%" />
<col style="width: 18%" />
</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 class="main-table">
<div class="p-2 rounded h-100 d-flex flex-column justify-content-between" style="background-color: #f1f1f1df">
<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 class="footer" [ngStyle]="{ 'background-color': isConnected ? '#d1ffd1' : '#ffcccc' }">
<div class="live-data-text">Live Data: {{ message || 'Disconnected' }}</div>
</footer>
</div> -->
<footer class="footer" [ngStyle]="{ 'background-color': isConnected ? '#d1ffd1' : '#ffcccc' }">
<div class="live-data-text">
Live Data: {{ message || 'Disconnected' }}
</div>
</footer>
</div>

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 { 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({
selector: 'app-shared-table',
standalone: true,
imports: [CommonModule],
imports: [CommonModule, FormatNumbersPipe],
templateUrl: './shared-table.component.html',
styleUrls: ['./shared-table.component.css']
styleUrls: ['./shared-table.component.css'],
})
// export class SharedTableComponent {
// @Input() summaryRows: any[] = [];
// @Input() rows: any[] = [];
// @Input() totalAmount: string = '';
// }
export class SharedTableComponent {
@Input() summaryRows: any[] = [];
@Input() rows: any[] = [];
export class SharedTableComponent implements OnInit {
@Input() summaryRows: { col1: string; col2: number; col3: number }[] = [];
@Input() rows: SelectionData[] = [];
@Input() totalAmount: string = '';
message = '';
@ -25,12 +23,14 @@ export class SharedTableComponent {
constructor(private websocketService: WebsocketService) {}
ngOnInit() {
this.websocketService.message$.subscribe(msg => {
this.websocketService.message$.subscribe((msg) => {
this.message = msg;
console.log('[SHARED TABLE] WebSocket message:', msg);
});
this.websocketService.isConnected$.subscribe(status => {
this.websocketService.isConnected$.subscribe((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(',');
}
}