Compare commits

...

2 Commits

3 changed files with 458 additions and 42 deletions

View File

@ -6,6 +6,10 @@
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> --> <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> -->
<title>Machine Description Form</title> <title>Machine Description Form</title>
<style> <style>
html {
scroll-behavior: smooth; /* Enables smooth scrolling */
scroll-padding-top: 80px; /* Prevents section titles from hiding under the sticky nav */
}
body { body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f4f4f9; background-color: #f4f4f9;
@ -15,7 +19,7 @@
padding: 20px; padding: 20px;
} }
.container { .container {
max-width: 800px; max-width: 1000px;
margin: auto; margin: auto;
background: #fff; background: #fff;
padding: 25px; padding: 25px;
@ -91,9 +95,29 @@
margin-top: 20px; margin-top: 20px;
transition: background-color 0.3s; transition: background-color 0.3s;
} }
input[type="submit"]:hover { input[type="submit"]:hover {
background-color: #2649B2; background-color: #2649B2;
} }
.next-tab-btn {
background-color: #4A74F3;
color: white;
padding: 12px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 18px;
font-weight: bold;
width: 100%;
margin-top: 20px;
transition: background-color 0.3s;
}
.next-tab-btn:hover {
background-color: #2649B2;
}
.checkbox-container { .checkbox-container {
display: grid; display: grid;
grid-template-columns: auto 1fr; /* Checkbox column, then text takes remaining space */ grid-template-columns: auto 1fr; /* Checkbox column, then text takes remaining space */
@ -119,7 +143,13 @@
} }
.tab-nav { .tab-nav {
border-bottom: 2px solid #D4D9F0; border-bottom: 2px solid #D4D9F0;
display: flex; display: flex;
position: sticky;
top: 0;
background-color: white;
padding: 10px 0;
border-bottom: 2px solid #D4D9F0;
z-index: 1000;
} }
.tab-nav button { .tab-nav button {
background: none; background: none;
@ -205,6 +235,10 @@
<nav class="tab-nav"> <nav class="tab-nav">
<button class="tab-btn active" data-tab="machine" id="machine-tab-btn">Machine</button> <button class="tab-btn active" data-tab="machine" id="machine-tab-btn">Machine</button>
<button class="tab-btn" data-tab="alarms" id="alarms-tab-btn">Alarms</button> <button class="tab-btn" data-tab="alarms" id="alarms-tab-btn">Alarms</button>
<button class="tab-btn" data-tab="warnings" id="warnings-tab-btn">Warnings</button>
<button class="tab-btn" data-tab="counters" id="counters-tab-btn">Counters</button>
<button class="tab-btn" data-tab="setpoints" id="setpoints-tab-btn">Setpoints</button>
<button class="tab-btn" data-tab="realtime" id="realtime-tab-btn">Real Time Variables</button>
</nav> </nav>
<form id="machine-form"> <form id="machine-form">
@ -365,35 +399,181 @@
</div> </div>
</div> </div>
</fieldset> </fieldset>
<button type="button" class="next-tab-btn">Next</button>
</div> </div>
<div id="alarms-tab" class="tab-content"> <div id="alarms-tab" class="tab-content">
<fieldset> <fieldset>
<legend>Alarms Exposed by the Machine</legend> <legend>Alarms Exposed by the Machine</legend>
<p>Define all alarms for the machine. If the message is empty, the alarm will not be generated.</p> <p>Define all alarms for the machine. If the message is empty, the alarm will not be generated.</p>
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</p>
<table class="dynamic-table"> <table class="dynamic-table">
<thead> <thead>
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Message</th><th class="col-actions">Actions</th></tr> <tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Message</th><th class="col-actions">Actions</th></tr>
</thead> </thead>
<tbody id="alarms-table-body"></tbody> <tbody id="alarms-table-body"></tbody>
</table> </table>
<button type="button" class="add-row-btn" id="add-alarm-btn">Add Alarm</button> <input type="number" id="bulk-alarm-count" min="1" placeholder="Number of alarms to add" style="width: 180px;">
<button type="button" id="add-multiple-alarms-btn" class="add-row-btn" style="width:auto;">Add Alarms</button>
</fieldset> </fieldset>
<button type="button" class="next-tab-btn">Next</button>
</div> </div>
<div id="warnings-tab" class="tab-content">
<fieldset>
<legend>Warnings Exposed by the Machine</legend>
<p>Define all warnings for the machine. If the message is empty, the warning will not be generated.</p>
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</p>
<table class="dynamic-table">
<thead>
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Message</th><th class="col-actions">Actions</th></tr>
</thead>
<tbody id="warnings-table-body"></tbody>
</table>
<input type="number" id="bulk-warning-count" min="1" placeholder="Number of warnings to add">
<button type="button" id="add-multiple-warnings-btn" class="add-row-btn">Add Warnings</button>
</fieldset>
<button type="button" class="next-tab-btn">Next</button>
</div>
<div id="counters-tab" class="tab-content">
<fieldset>
<legend>Counters</legend>
<p>
Define all counters for the machine. If the description is empty, the counter will not be generated.
<br>
</p>
<p>
<strong>Note:</strong> The first three counters are predefined by the PackML standard
and are automatically included in the generated nodeset.
<br>PackML counters described by default contain only the first entry of each array.
</p>
<p>
You can add more counters as needed. Additional defective unit counter should be provided per type of defect (bad open flap, underfill, not printed, bad pump orientation, ...etc.) and station (head 1, nozzleX,....etc.).
</p>
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</p>
<table class="dynamic-table">
<thead>
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Type</th><th class="col-description">Description</th><th class="col-actions">Actions</th></tr>
</thead>
<tbody>
<tr>
<td><input type="text" value="-" disabled></td>
<td><input type="text" value="ProdProcessedCount" disabled></td>
<td><input type="text" value="ProductionCounter" disabled></td>
<td><textarea rows="2" cols="30" disabled>Total products processed (PackML standard)</textarea></td>
<td><span style="color: #999;">Predefined</span></td>
</tr>
<tr>
<td><input type="text" value="-" disabled></td>
<td><input type="text" value="ProdDefectiveCount" disabled></td>
<td><input type="text" value="DefectiveCounter" disabled></td>
<td><textarea rows="2" cols="30" disabled>Total defective products (PackML standard)</textarea></td>
<td><span style="color: #999;">Predefined</span></td>
</tr>
<tr>
<td><input type="text" value="-" disabled></td>
<td><input type="text" value="CycleCount" disabled></td>
<td><input type="text" value="CycleCounter" disabled></td>
<td><textarea rows="2" cols="30" disabled>Total machine cycles (PackML standard)</textarea></td>
<td><span style="color: #999;">Predefined</span></td>
</tr>
</tbody>
<tbody id="counters-table-body"></tbody>
</table>
<button type="button" class="add-row-btn" id="add-counter-btn">Add Counter</button>
</fieldset>
<button type="button" class="next-tab-btn">Next</button>
</div>
<div id="setpoints-tab" class="tab-content">
<fieldset>
<legend>Setpoints</legend>
<p>Define all setpoints for the machine. If the description is empty, the setpoint will not be generated.</p>
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</p>
<table class="dynamic-table">
<thead>
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Description</th><th class="col-actions">Actions</th></tr>
</thead>
<tbody id="setpoints-table-body"></tbody>
</table>
<button type="button" class="add-row-btn" id="add-setpoint-btn">Add Setpoint</button>
</fieldset>
<button type="button" class="next-tab-btn">Next</button>
</div>
<div id="realtime-tab" class="tab-content">
<fieldset>
<legend>Real Time Variables</legend>
<p>Define all real time variables for the machine. If the description is empty, the variable will not be generated.</p>
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</p>
<table class="dynamic-table">
<thead>
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Description</th><th class="col-actions">Actions</th></tr>
</thead>
<tbody id="realtime-table-body"></tbody>
</table>
<button type="button" class="add-row-btn" id="add-realtime-btn">Add Real Time Variable</button>
</fieldset>
<input type="submit" value="Validate and Submit Description">
</div>
<input type="submit" value="Validate and Submit Description">
</form> </form>
</div> </div>
<template id="alarm-row-template"> <template id="alarm-row-template">
<tr> <tr>
<td><input type="number" name="alarm_id" min="1" placeholder="e.g., 1"></td> <td><input type="number" name="alarm_id" min="1" placeholder="e.g., 1"></td>
<td><input type="text" name="alarm_name" placeholder="e.g., ALARM_1"></td> <td><input type="text" name="alarm_name" placeholder="e.g., ALARM_001"></td>
<td><textarea name="alarm_message" rows="1" cols="50" placeholder="Message displayed on dashboard"></textarea></td> <td><textarea name="alarm_message" rows="1" cols="50" placeholder="Message displayed to the operator"></textarea></td>
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td> <td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
</tr> </tr>
</template> </template>
<template id="warning-row-template">
<tr>
<td><input type="number" name="warning_id" min="1" placeholder="e.g., 1"></td>
<td><input type="text" name="warning_name" placeholder="e.g., WARNING_001"></td>
<td><textarea name="warning_message" rows="1" cols="50" placeholder="Message displayed to the operator"></textarea></td>
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
</tr>
</template>
<template id="realtime-row-template">
<tr>
<td><input type="number" name="realtime_id" min="1" placeholder="e.g., 1"></td>
<td><input type="text" name="realtime_name" placeholder="e.g., REALTIME_001"></td>
<td><textarea name="realtime_message" rows="1" cols="50" placeholder="Message displayed to the operator"></textarea></td>
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
</tr>
</template>
<template id="counter-row-template">
<tr>
<td><input type="number" name="counter_id" min="1" placeholder="e.g., 1"></td>
<td><input type="text" name="counter_name" placeholder="e.g., COUNTER_001"></td>
<td><select name="counter_type">
<option value="productionCounter">Production Counter</option>
<option value="defectiveCounter">Defective Counter</option>
<option value="cycleCounter">Cycle Counter</option>
<option value="otherCounter">Other</option>
</select></td>
<td><textarea name="counter_description" rows="1" cols="30" placeholder="Description of the counter"></textarea></td>
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
</tr>
</template>
<template id="setpoint-row-template">
<tr>
<td><input type="number" name="setpoint_id" min="1" placeholder="e.g., 1"></td>
<td><input type="text" name="setpoint_name" placeholder="e.g., SETPOINT_001"></td>
<td><textarea name="setpoint_message" rows="1" cols="30" placeholder="Message displayed to the operator"></textarea></td>
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
</tr>
</template>
<script src="renderer.js"></script> <script src="renderer.js"></script>
</body> </body>

55
main.js
View File

@ -53,25 +53,62 @@ app.whenReady().then(() => {
yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`); yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`);
// 2. Build the 'alarms' section from the alarms data // 2. Build the 'alarms' section from the alarms data
if (data.alarms && data.alarms.length > 0) { if (data.alarms) {
yamlLines.push(''); // Blank line for separation yamlLines.push(''); // Blank line for separation
yamlLines.push(`${sp2}alarms:`); yamlLines.push(`${sp2}alarms:`);
yamlLines.push(`${sp4}packing: OBJECTS`); // As per PowerShell script logic yamlLines.push(`${sp4}packing: OBJECTS`); // As per PowerShell script logic
yamlLines.push(`${sp4}behavior: ${data.alarmBehavior || 'STATIC'}`); yamlLines.push(`${sp4}behavior: ${data.alarmBehavior || 'STATIC'}`);
yamlLines.push(`${sp4}count: ${data.alarms.length}`); yamlLines.push(`${sp4}count: ${data.alarmCount}`);
yamlLines.push(`${sp4}alarms: `); // Note the trailing space if (data.alarms.length > 0) {
yamlLines.push(`${sp4}alarms: `); // Note the trailing space
data.alarms.forEach(alarm => { data.alarms.forEach(alarm => {
const id = alarm.id || 0; const id = alarm.id || 0;
const name = alarm.name || `Alarm_${id}`; const name = alarm.name || `Alarm_${id}`;
const message = alarm.message.replace(/:/g, ';'); // Sanitize message as in PowerShell const message = alarm.message.replace(/:/g, ';'); // Sanitize message as in PowerShell
yamlLines.push(`${sp6}- ID: ${id}`);
yamlLines.push(`${sp8}name: ${name}`);
yamlLines.push(`${sp8}message: ${message}`);
});
}
}
//3. Build the 'warning' section from the warnings data
if (data.warnings && data.warnings.length > 0) {
yamlLines.push(''); // Blank line for separation
yamlLines.push(`${sp2}warnings:`);
yamlLines.push(`${sp4}count: ${data.warnings.length}`);
yamlLines.push(`${sp4}warnings: `); // Note the trailing space
data.warnings.forEach(warning => {
const id = warning.id || 0;
const name = warning.name || `Warning_${id}`;
const message = warning.message.replace(/:/g, ';'); // Sanitize message as in PowerShell
yamlLines.push(`${sp6}- ID: ${id}`); yamlLines.push(`${sp6}- ID: ${id}`);
yamlLines.push(`${sp8}name: ${name}`); yamlLines.push(`${sp8}name: ${name}`);
yamlLines.push(`${sp8}message: ${message}`); yamlLines.push(`${sp8}message: ${message}`);
}); });
} }
//Build the 'counters' section from the counters data
if (data.counters && data.counters.length > 0) {
yamlLines.push(''); // Blank line for separation
yamlLines.push(`${sp2}counters:`);
data.counters.forEach(counter => {
const id = counter.id || 0;
const name = counter.name || `Counter_${id}`;
const type = counter.type || '';
const description = counter.description || '';
yamlLines.push(`${sp4}- name: ${name}`);
yamlLines.push(`${sp6}ID: ${id}`);
yamlLines.push(`${sp6}type: ${type}`);
yamlLines.push(`${sp6}description: ${description}`);
});
}
// Build and Append the 'optionals' section if needed // Build and Append the 'optionals' section if needed
if (data.stopReason === 'Yes') { if (data.stopReason === 'Yes') {
yamlLines.push(''); // Add a blank line for spacing yamlLines.push(''); // Add a blank line for spacing

View File

@ -1,7 +1,6 @@
// renderer.js // renderer.js
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
console.log('DOM fully loaded. renderer.js is now running.'); console.log('DOM fully loaded. renderer.js is now running.');
// --- 0. INITIALIZE FORM DEFAULTS --- // --- 0. INITIALIZE FORM DEFAULTS ---
@ -10,11 +9,9 @@ document.addEventListener('DOMContentLoaded', () => {
publicationDateInput.value = new Date().toISOString().split('T')[0]; publicationDateInput.value = new Date().toISOString().split('T')[0];
} }
// --- 1. TAB SWITCHING LOGIC --- // --- 1. TAB SWITCHING LOGIC ---
const tabButtons = document.querySelectorAll('.tab-btn'); const tabButtons = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content'); const tabContents = document.querySelectorAll('.tab-content');
tabButtons.forEach(button => { tabButtons.forEach(button => {
button.addEventListener('click', () => { button.addEventListener('click', () => {
tabButtons.forEach(btn => btn.classList.remove('active')); tabButtons.forEach(btn => btn.classList.remove('active'));
@ -23,36 +20,179 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById(button.dataset.tab + '-tab').classList.add('active'); document.getElementById(button.dataset.tab + '-tab').classList.add('active');
}); });
}); });
// Set the initial active tab content
document.getElementById('machine-tab').classList.add('active'); document.getElementById('machine-tab').classList.add('active');
// --- 2. DYNAMIC ALARM ROW LOGIC --- // --- 2. GENERIC DYNAMIC TABLE LOGIC ---
const addAlarmBtn = document.getElementById('add-alarm-btn'); function setupDynamicTable({
const alarmsTableBody = document.getElementById('alarms-table-body'); tableBodyId,
const alarmTemplate = document.getElementById('alarm-row-template'); templateId,
bulkInputId,
bulkBtnId,
fields, // Array of field names in order (e.g. ['alarm_id', 'alarm_name', 'alarm_message'])
prefix, // e.g. 'ALARM'
nameFormat, // e.g. (id, paddedId) => `ALARM_${paddedId}`
}) {
const tableBody = document.getElementById(tableBodyId);
const rowTemplate = document.getElementById(templateId);
const bulkInput = bulkInputId ? document.getElementById(bulkInputId) : null;
const bulkBtn = bulkBtnId ? document.getElementById(bulkBtnId) : null;
addAlarmBtn.addEventListener('click', () => { // Add initial row
const currentIdInputs = alarmsTableBody.querySelectorAll('[name="alarm_id"]'); addRow(tableBody, rowTemplate, 1, fields, prefix, nameFormat);
let maxId = 0;
currentIdInputs.forEach(input => { // Bulk add
const currentId = parseInt(input.value, 10); if (bulkBtn && bulkInput) {
if (!isNaN(currentId) && currentId > maxId) { bulkBtn.addEventListener('click', () => {
maxId = currentId; let count = parseInt(bulkInput.value, 10);
if (isNaN(count) || count < 1) count = 1;
const currentIdInputs = tableBody.querySelectorAll(`[name="${fields[0]}"]`);
let maxId = 0;
currentIdInputs.forEach(input => {
const currentId = parseInt(input.value, 10);
if (!isNaN(currentId) && currentId > maxId) maxId = currentId;
});
for (let i = 1; i <= count; i++) {
addRow(tableBody, rowTemplate, maxId + i, fields, prefix, nameFormat);
}
});
}
// Paste from Excel
tableBody.addEventListener('paste', (event) => {
const targetCell = event.target.closest('td');
const targetRow = event.target.closest('tr');
const allRows = Array.from(tableBody.querySelectorAll('tr'));
let startRowIdx = allRows.indexOf(targetRow);
if (startRowIdx === -1) startRowIdx = allRows.length;
// Find the cell index in the row
let startCellIdx = 0;
if (targetCell && targetRow) {
const cells = Array.from(targetRow.children);
startCellIdx = cells.indexOf(targetCell);
if (startCellIdx === -1) startCellIdx = 0;
} }
// Get clipboard data
const clipboardData = event.clipboardData || window.clipboardData;
const pastedData = clipboardData.getData('Text');
if (!pastedData) return;
// Find current max ID for auto-increment
const currentIdInputs = tableBody.querySelectorAll(`[name="${fields[0]}"]`);
let maxId = 0;
currentIdInputs.forEach(input => {
const currentId = parseInt(input.value, 10);
if (!isNaN(currentId) && currentId > maxId) maxId = currentId;
});
// Split rows by newline, columns by tab
const rows = pastedData.trim().split(/\r?\n/);
rows.forEach((row, rowOffset) => {
const cols = row.split('\t');
if (cols.every(col => !col.trim())) return; // Ignore empty rows
let rowToFill = allRows[startRowIdx + rowOffset];
if (!rowToFill) {
// Add new row if needed
maxId += 1;
rowToFill = addRow(tableBody, rowTemplate, maxId, fields, prefix, nameFormat);
}
// Get all editable fields in the row
const editableFields = fields.map(f => rowToFill.querySelector(`[name="${f}"]`));
// If only last field (description/message) is pasted, auto-fill ID and name
if (cols.length === 1 || (cols.length > 0 && cols.slice(0, -1).every(col => !col.trim()))) {
maxId += (!rowToFill.querySelector(`[name="${fields[0]}"]`).value ? 1 : 0);
const paddedId = maxId.toString().padStart(3, '0');
if (editableFields[0]) editableFields[0].value = maxId;
if (editableFields[1] && nameFormat) editableFields[1].value = nameFormat(maxId, paddedId);
if (editableFields[editableFields.length - 1]) editableFields[editableFields.length - 1].value = cols[cols.length - 1].trim();
} else {
// Paste values to the right, only filling available cells
for (let colOffset = 0; colOffset < cols.length; colOffset++) {
const fieldIdx = startCellIdx + colOffset;
if (fieldIdx < editableFields.length && editableFields[fieldIdx]) {
editableFields[fieldIdx].value = cols[colOffset].trim();
}
}
}
});
event.preventDefault(); // Prevent default paste
}); });
const nextId = maxId + 1; }
const paddedId = nextId.toString().padStart(3, '0');
function addRow(tableBody, rowTemplate, id, fields, prefix, nameFormat) {
const clone = alarmTemplate.content.cloneNode(true); const paddedId = id.toString().padStart(3, '0');
clone.querySelector('[name="alarm_id"]').value = nextId; const clone = rowTemplate.content.cloneNode(true);
clone.querySelector('[name="alarm_name"]').value = `ALARM_${paddedId}`; if (fields[0]) clone.querySelector(`[name="${fields[0]}"]`).value = id;
if (fields[1] && nameFormat) clone.querySelector(`[name="${fields[1]}"]`).value = nameFormat(id, paddedId);
const removeBtn = clone.querySelector('.remove-row-btn'); const removeBtn = clone.querySelector('.remove-row-btn');
removeBtn.addEventListener('click', (e) => { if (removeBtn) {
e.target.closest('tr').remove(); removeBtn.addEventListener('click', (e) => {
}); e.target.closest('tr').remove();
alarmsTableBody.appendChild(clone); });
}
tableBody.appendChild(clone);
return tableBody.querySelectorAll('tr')[tableBody.querySelectorAll('tr').length - 1];
}
// --- ALARMS ---
setupDynamicTable({
tableBodyId: 'alarms-table-body',
templateId: 'alarm-row-template',
bulkInputId: 'bulk-alarm-count',
bulkBtnId: 'add-multiple-alarms-btn',
fields: ['alarm_id', 'alarm_name', 'alarm_message'],
prefix: 'ALARM',
nameFormat: (id, paddedId) => `ALARM_${paddedId}`
}); });
// --- WARNINGS ---
setupDynamicTable({
tableBodyId: 'warnings-table-body',
templateId: 'warning-row-template',
bulkInputId: 'bulk-warning-count',
bulkBtnId: 'add-multiple-warnings-btn',
fields: ['warning_id', 'warning_name', 'warning_message'],
prefix: 'WARNING',
nameFormat: (id, paddedId) => `WARNING_${paddedId}`
});
// --- COUNTERS ---
setupDynamicTable({
tableBodyId: 'counters-table-body',
templateId: 'counter-row-template',
bulkInputId: null, // Add bulk if you want
bulkBtnId: null, // Add bulk if you want
fields: ['counter_id', 'counter_name', 'counter_type', 'counter_description'],
prefix: 'COUNTER',
nameFormat: (id, paddedId) => `COUNTER_${paddedId}`
});
// --- SETPOINTS ---
setupDynamicTable({
tableBodyId: 'setpoints-table-body',
templateId: 'setpoint-row-template',
bulkInputId: null, // Add bulk if you want
bulkBtnId: null, // Add bulk if you want
fields: ['setpoint_id', 'setpoint_name', 'setpoint_message'],
prefix: 'SETPOINT',
nameFormat: (id, paddedId) => `SETPOINT_${paddedId}`
});
// --- REALTIME VARIABLES ---
setupDynamicTable({
tableBodyId: 'realtime-table-body',
templateId: 'realtime-row-template',
bulkInputId: null, // Add bulk if you want
bulkBtnId: null, // Add bulk if you want
fields: ['realtime_id', 'realtime_name', 'realtime_message'],
prefix: 'REALTIME',
nameFormat: (id, paddedId) => `REALTIME_${paddedId}`
});
// --- 3. FORM SUBMISSION LOGIC --- // --- 3. FORM SUBMISSION LOGIC ---
const form = document.getElementById('machine-form'); const form = document.getElementById('machine-form');
@ -86,9 +226,38 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
// D. Collect all warning rows from the table
const warningRows = warningsTableBody.querySelectorAll('tr');
const warningsData = [];
warningRows.forEach(row => {
const warning = {
id: row.querySelector('[name="warning_id"]').value,
name: row.querySelector('[name="warning_name"]').value,
message: row.querySelector('[name="warning_message"]').value,
};
if (warning.message) {
warningsData.push(warning);
}
});
// E. Collect all counter rows from the table
const counterRows = countersTableBody.querySelectorAll('tr');
const countersData = [];
counterRows.forEach(row => {
const counter = {
id: row.querySelector('[name="counter_id"]').value,
name: row.querySelector('[name="counter_name"]').value,
type: row.querySelector('[name="counter_type"]').value,
description: row.querySelector('[name="counter_description"]').value,
};
if (counter.type) {
countersData.push(counter);
}
});
// C. Combine all data into a single object // C. Combine all data into a single object
const finalData = { ...mainData, alarms: alarmsData }; const finalData = { ...mainData, alarms: alarmsData, warnings: warningsData, counters: countersData };
console.log('Sending all collected data to main process:', finalData); console.log('Sending all collected data to main process:', finalData);
try { try {
@ -106,4 +275,34 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
} }
// --- 4. TAB NAVIGATION BUTTONS ---
const tabOrder = [
'machine-tab',
'alarms-tab',
'warnings-tab',
'counters-tab',
'setpoints-tab',
'realtime-tab'
];
// Add event listeners to all "Next" buttons
document.querySelectorAll('.next-tab-btn').forEach((btn, idx) => {
btn.addEventListener('click', () => {
// Find the currently active tab
const currentTab = document.querySelector('.tab-content.active');
if (!currentTab) return;
const currentTabId = currentTab.id;
const currentIdx = tabOrder.indexOf(currentTabId);
const nextTabId = tabOrder[currentIdx + 1];
if (nextTabId) {
// Switch tab button active state
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelector(`[data-tab="${nextTabId.replace('-tab','')}"]`).classList.add('active');
// Switch tab content active state
document.querySelectorAll('.tab-content').forEach(tc => tc.classList.remove('active'));
document.getElementById(nextTabId).classList.add('active');
}
});
});
}); });