feat : electron sync working
This commit is contained in:
parent
594bd4ae77
commit
8e9f84a2cf
@ -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');
|
||||||
|
|||||||
@ -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)),
|
||||||
});
|
});
|
||||||
@ -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,13 +8,12 @@ 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;
|
||||||
@ -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,67 +49,77 @@ 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) {
|
} catch (e) {
|
||||||
console.error('❌ Failed to parse localTickets from localStorage:', 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; // do NOT include grandTotal yet
|
this.salesTotal = storedTotal;
|
||||||
this.receiveTotal = storedTotal;
|
this.receiveTotal = storedTotal;
|
||||||
}
|
}
|
||||||
this.totalClicks = Number(localStorage.getItem('printClickCount') || '0');
|
this.totalClicks = Number(localStorage.getItem('printClickCount') || '0');
|
||||||
|
this.showSalesTotal = hasPrinted;
|
||||||
// 👇 Toggle visibility based on localStorage flag
|
this.syncToSecondScreen(); // Sync after calculating totals
|
||||||
this.showSalesTotal = localStorage.getItem('hasPrinted') === 'true';
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
repeat() {
|
repeat() {
|
||||||
try {
|
try {
|
||||||
@ -119,23 +127,20 @@ export class MiddleSectionComponent implements OnInit, OnDestroy {
|
|||||||
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.length - 1]
|
||||||
|
: null
|
||||||
: tickets;
|
: tickets;
|
||||||
|
|
||||||
if (latestTicket && latestTicket.winLabels) {
|
if (latestTicket && latestTicket.winLabels) {
|
||||||
// Pass totalAmount as a fallback for missing totals
|
const parsedRows = this.parseWinLabelsToRows(latestTicket.winLabels, latestTicket.totalAmount);
|
||||||
const parsedRows = this.parseWinLabelsToRows(
|
|
||||||
latestTicket.winLabels,
|
|
||||||
latestTicket.totalAmount
|
|
||||||
);
|
|
||||||
|
|
||||||
// ✅ Always make sure we have exactly 5 rows
|
// ✅ Always make sure we have exactly 5 rows
|
||||||
this.updateFilledRows(parsedRows, { label: '', numbers: [], value: 0, total: 0 });
|
this.updateFilledRows(parsedRows, { label: '', numbers: [], value: 0, total: 0 });
|
||||||
|
|
||||||
console.log('📜 Updated Filled Rows:', parsedRows);
|
console.log('📜 Updated Filled Rows:', parsedRows);
|
||||||
this.showConfirmButton = true;
|
this.showConfirmButton = true;
|
||||||
this.showPrintButton = false;
|
this.showPrintButton = false;
|
||||||
this.lastTicket = latestTicket;
|
this.lastTicket = latestTicket;
|
||||||
|
this.showRepeatTicket = true;
|
||||||
|
this.syncToSecondScreen(); // Sync after repeat
|
||||||
} else {
|
} else {
|
||||||
console.warn('⚠️ No valid ticket data found in localStorage.');
|
console.warn('⚠️ No valid ticket data found in localStorage.');
|
||||||
}
|
}
|
||||||
@ -145,36 +150,32 @@ export class MiddleSectionComponent implements OnInit, OnDestroy {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to load ticket from localStorage:', 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';
|
||||||
|
|
||||||
// Keep currentDate as a Date object
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
// 🆕 Create Barcode ID (same combo format)
|
|
||||||
const day = String(now.getDate()).padStart(2, '0');
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
const year = String(now.getFullYear()).slice(-2);
|
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 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 millis = String(now.getMilliseconds()).padStart(3, '0');
|
||||||
const barcodeId = `1111${day}${month}${year}${timeStr}${millis}`;
|
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()}`;
|
||||||
@ -225,25 +222,25 @@ Date : ${now.toLocaleString()}`;
|
|||||||
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 ---
|
||||||
@ -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,13 +272,9 @@ 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() {
|
||||||
@ -290,11 +282,14 @@ Date : ${now.toLocaleString()}`;
|
|||||||
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();
|
||||||
|
|||||||
@ -8,8 +8,11 @@
|
|||||||
<!-- Tables for second screen -->
|
<!-- Tables for second screen -->
|
||||||
|
|
||||||
|
|
||||||
<app-shared-table
|
<!-- shared-display.component.html -->
|
||||||
|
<div class="wrapper">
|
||||||
|
<app-shared-table
|
||||||
[summaryRows]="summaryRows"
|
[summaryRows]="summaryRows"
|
||||||
[rows]="rows"
|
[rows]="filledRows"
|
||||||
[totalAmount]="totalAmount"
|
[totalAmount]="grandTotal.toString()"
|
||||||
></app-shared-table>
|
></app-shared-table>
|
||||||
|
</div>
|
||||||
@ -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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
|
<!-- 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">
|
||||||
|
<!-- Transaction Summary -->
|
||||||
<div class="transaction-summary mb-3">
|
<div class="transaction-summary mb-3">
|
||||||
<div class="p-3 rounded text-white h-100 d-flex flex-column" style="background-color: #546c98">
|
<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>
|
<h5 class="text-center mb-3">Transaction Summary</h5>
|
||||||
|
|
||||||
<div class="rounded flex-grow-1" style="background-color: #f1f1f1df; padding: 1rem; overflow: hidden">
|
<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">
|
<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>
|
<tbody>
|
||||||
<tr *ngFor="let row of summaryRows">
|
<tr *ngFor="let row of summaryRows">
|
||||||
<td class="custom-cell pure-white col-20">{{ row.col1 }}</td>
|
<td class="custom-cell pure-white col-20">{{ row.col1 }}</td>
|
||||||
@ -18,34 +24,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Table -->
|
||||||
<div class="main-table">
|
<div class="main-table">
|
||||||
<div class="p-2 rounded h-100 d-flex flex-column justify-content-between" style="background-color: #f1f1f1df">
|
<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">
|
<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>
|
<tbody>
|
||||||
<tr *ngFor="let row of rows">
|
<tr *ngFor="let row of rows">
|
||||||
<td class="custom-cell-new">{{ row.col1 }}</td>
|
<td class="custom-cell-new">{{ row.label }}</td>
|
||||||
<td class="custom-cell-new">{{ row.col2 }}</td>
|
<td class="custom-cell-new">
|
||||||
<td class="custom-cell-new">{{ row.col3 }}</td>
|
{{ row.isBoxed ? '# ' : '' }}{{ $any(row.numbers) | formatNumbers }}
|
||||||
<td class="custom-cell-new">{{ row.col4 }}</td>
|
</td>
|
||||||
|
<td class="custom-cell-new">{{ row.value || '' }}</td>
|
||||||
|
<td class="custom-cell-new">{{ row.total || '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="buttons-custom d-flex justify-content-between align-items-center px-3">
|
<div class="buttons-custom d-flex justify-content-between align-items-center px-3">
|
||||||
<div class="fw-bold">Amount : {{ totalAmount }}</div>
|
<div></div>
|
||||||
|
<div class="fw-bold fs-5">Amount : ₹ {{ totalAmount }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <footer class="footer">
|
<footer class="footer" [ngStyle]="{ 'background-color': isConnected ? '#d1ffd1' : '#ffcccc' }">
|
||||||
<div class="live-data-text">Live Data: </div>
|
<div class="live-data-text">Live Data: {{ message || 'Disconnected' }}</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>
|
|
||||||
|
|||||||
@ -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
21
btc-UI/src/electron.d.ts
vendored
Normal 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;
|
||||||
|
}
|
||||||
15
btc-UI/src/format-numbers.pipe.ts
Normal file
15
btc-UI/src/format-numbers.pipe.ts
Normal 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(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user