Adding Alarm management

This commit is contained in:
LAHAY Damien 2025-07-03 15:34:51 +02:00
parent 1f42ce3ad2
commit 2590675d9c
3 changed files with 367 additions and 201 deletions

View File

@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- <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>
body { body {
@ -116,173 +117,284 @@
margin-top: 2px; margin-top: 2px;
line-height: 1.3; line-height: 1.3;
} }
.tab-nav {
border-bottom: 2px solid #D4D9F0;
display: flex;
}
.tab-nav button {
background: none;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 1em;
color: #6c757d;
border-bottom: 3px solid transparent;
}
.tab-nav button.active {
color: #2649B2;
border-bottom-color: #2649B2;
font-weight: bold;
}
.tab-content {
display: none;
padding-top: 20px;
}
.tab-content.active {
display: block;
}
.dynamic-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
.dynamic-table th, .dynamic-table td {
border: 1px solid #D4D9F0;
padding: 8px;
text-align: left;
vertical-align: top;
}
.dynamic-table th {
background-color: #f4f4f9;
}
.dynamic-table input, .dynamic-table textarea {
padding: 5px;
}
.dynamic-table .col-id {
width: 10%;
}
.dynamic-table .col-name {
width: 25%;
}
.dynamic-table .col-actions {
width: 15%;
text-align: center;
}
.add-row-btn {
margin-top: 15px;
padding: 8px 12px;
background-color: #6C8BE0;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.add-row-btn:hover {
background-color: #5a7dc2;
}
.remove-row-btn {
padding: 5px 10px;
background-color: #e06c6c;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.remove-row-btn:hover {
background-color: #c55a5a;
}
</style> </style>
</head> </head>
<body> <body>
<script src="renderer.js"></script>
<div class="container"> <div class="container">
<h2>Machine Description</h2> <h2>Machine Description</h2>
<nav class="tab-nav">
<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>
</nav>
<form id="machine-form"> <form id="machine-form">
<fieldset> <div class="tab-content" id="machine-tab" active>
<legend>Machine Details</legend> <fieldset>
<div class="form-group"> <legend>Machine Details</legend>
<label for="machineType">MachineType</label> <div class="form-group">
<select id="machineType" name="machineType"> <label for="machineType">MachineType</label>
<option value="Unspecified">Unspecified</option> <select id="machineType" name="machineType">
<option value="Accumulation">Accumulation</option> <option value="Unspecified">Unspecified</option>
<option value="BulkFeedingSystem">BulkFeedingSystem</option> <option value="Accumulation">Accumulation</option>
<option value="Bundler">Bundler</option> <option value="BulkFeedingSystem">BulkFeedingSystem</option>
<option value="Capper">Capper</option> <option value="Bundler">Bundler</option>
<option value="Cartoner">Cartoner</option> <option value="Capper">Capper</option>
<option value="CasePacker">CasePacker</option> <option value="Cartoner">Cartoner</option>
<option value="CaseSealer">CaseSealer</option> <option value="CasePacker">CasePacker</option>
<option value="CheckWeigher">CheckWeigher</option> <option value="CaseSealer">CaseSealer</option>
<option value="DePalletizer">DePalletizer</option> <option value="CheckWeigher">CheckWeigher</option>
<option value="Feeder">Feeder</option> <option value="DePalletizer">DePalletizer</option>
<option value="Filler">Filler</option> <option value="Feeder">Feeder</option>
<option value="FillerCapper">FillerCapper</option> <option value="Filler">Filler</option>
<option value="FillSeal">FillSeal</option> <option value="FillerCapper">FillerCapper</option>
<option value="FormFillSeal">FormFillSeal</option> <option value="FillSeal">FillSeal</option>
<option value="Labeller">Labeller</option> <option value="FormFillSeal">FormFillSeal</option>
<option value="LineManagementController">LineManagementController</option> <option value="Labeller">Labeller</option>
<option value="OverWrapper">OverWrapper</option> <option value="LineManagementController">LineManagementController</option>
<option value="Palletizer">Palletizer</option> <option value="OverWrapper">OverWrapper</option>
<option value="PickAndPlace">PickAndPlace</option> <option value="Palletizer">Palletizer</option>
<option value="ShrinkWrapper">ShrinkWrapper</option> <option value="PickAndPlace">PickAndPlace</option>
</select> <option value="ShrinkWrapper">ShrinkWrapper</option>
</div> </select>
<div class="form-group"> </div>
<label for="vendor">Vendor</label> <div class="form-group">
<input type="text" id="vendor" name="vendor"> <label for="vendor">Vendor</label>
<small>Company name of the machine vendor. Avoid whitespaces or special characters.</small> <input type="text" id="vendor" name="vendor">
</div> <small>Company name of the machine vendor. Avoid whitespaces or special characters.</small>
<div class="form-group"> </div>
<label for="model">Model</label> <div class="form-group">
<input type="text" id="model" name="model"> <label for="model">Model</label>
</div> <input type="text" id="model" name="model">
<div class="form-group"> </div>
<label for="serialNo">SerialNo</label> <div class="form-group">
<input type="text" id="serialNo" name="serialNo"> <label for="serialNo">SerialNo</label>
</div> <input type="text" id="serialNo" name="serialNo">
<div class="form-group"> </div>
<label for="buildYear">Build Year</label> <div class="form-group">
<input type="number" id="buildYear" name="buildYear" placeholder="Ex: 2024" min="1980" max="2100"> <label for="buildYear">Build Year</label>
</div> <input type="number" id="buildYear" name="buildYear" placeholder="Ex: 2024" min="1980" max="2100">
<div class="form-group"> </div>
<label for="browseName">BrowseName (OPC UA)</label> <div class="form-group">
<input type="text" id="browseName" name="browseName"> <label for="browseName">BrowseName (OPC UA)</label>
<small>This is the name of the machine which appears in the OPC UA server. Recommendation: MachineType + Numeric Suffix (e.g., Labeller1).</small> <input type="text" id="browseName" name="browseName">
</div> <small>This is the name of the machine which appears in the OPC UA server. Recommendation: MachineType + Numeric Suffix (e.g., Labeller1).</small>
</fieldset> </div>
</fieldset>
<fieldset> <fieldset>
<legend>Publication & Versioning</legend> <legend>Publication & Versioning</legend>
<div class="form-group"> <div class="form-group">
<label for="publicationDate">Publication Date</label> <label for="publicationDate">Publication Date</label>
<input type="date" id="publicationDate" name="publicationDate"> <input type="date" id="publicationDate" name="publicationDate">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="authorEmail">Author Email</label> <label for="authorEmail">Author Email</label>
<input type="email" id="authorEmail" name="authorEmail" placeholder="firstname.lastname@loreal.com"> <input type="email" id="authorEmail" name="authorEmail" placeholder="firstname.lastname@loreal.com">
<small>Email of the person to contact about this spreadsheet.</small> <small>Email of the person to contact about this spreadsheet.</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="version">Version</label> <label for="version">Version</label>
<input type="text" id="version" name="version" placeholder="Ex: 1.0.0"> <input type="text" id="version" name="version" placeholder="Ex: 1.0.0">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Output Filename</label> <label>Output Filename</label>
<!-- This is where the generated filename will be displayed --> <!-- This is where the generated filename will be displayed -->
<p id="output-filename-display" style="font-family: monospace; background-color: #e9ecef; padding: 10px; border-radius: 5px;"></p> <p id="output-filename-display" style="font-family: monospace; background-color: #e9ecef; padding: 10px; border-radius: 5px;"></p>
<small>The filename is automatically generated from the BrowseName above.</small> <small>The filename is automatically generated from the BrowseName above.</small>
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>Alarm Configuration</legend> <legend>Alarm Configuration</legend>
<div class="form-group"> <div class="form-group">
<label for="alarmRepresentation">AlarmRepresentation</label> <label for="alarmRepresentation">AlarmRepresentation</label>
<select id="alarmRepresentation" name="alarmRepresentation"> <select id="alarmRepresentation" name="alarmRepresentation">
<option value="DistinctAlarms">DistinctAlarms (Each alarm is a separate OPC UA variable)</option> <option value="DistinctAlarms">DistinctAlarms (Each alarm is a separate OPC UA variable)</option>
<option value="SingleAlarmsArray">SingleAlarmsArray (Alarms are packed in a Boolean array)</option> <option value="SingleAlarmsArray">SingleAlarmsArray (Alarms are packed in a Boolean array)</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="alarmBehavior">AlarmBehavior</label> <label for="alarmBehavior">AlarmBehavior</label>
<select id="alarmBehavior" name="alarmBehavior"> <select id="alarmBehavior" name="alarmBehavior">
<option value="STATIC">STATIC (Static allocation, recommended)</option> <option value="STATIC">STATIC (Static allocation, recommended)</option>
<option value="DYNAMIC">DYNAMIC (Alarms are allocated in a dynamic table)</option> <option value="DYNAMIC">DYNAMIC (Alarms are allocated in a dynamic table)</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="alarmCount">AlarmCount</label> <label for="alarmCount">AlarmCount</label>
<input type="number" id="alarmCount" name="alarmCount" min="0"> <input type="number" id="alarmCount" name="alarmCount" min="0">
<small>Used as the size of the array for DYNAMIC alarm behavior.</small> <small>Used as the size of the array for DYNAMIC alarm behavior.</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="messagesLanguage">MessagesLanguage</label> <label for="messagesLanguage">MessagesLanguage</label>
<select id="messagesLanguage" name="messagesLanguage"> <select id="messagesLanguage" name="messagesLanguage">
<option value="EN">EN (English)</option> <option value="EN">EN (English)</option>
<option value="FR">FR (Français)</option> <option value="FR">FR (Français)</option>
<option value="DE">DE (Deutsch)</option> <option value="DE">DE (Deutsch)</option>
<option value="ES">ES (Español)</option> <option value="ES">ES (Español)</option>
<option value="IT">IT (Italiano)</option> <option value="IT">IT (Italiano)</option>
</select> </select>
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>Optional Features</legend> <legend>Optional Features</legend>
<div class="form-group"> <div class="form-group">
<label>StopReason Generation</label> <label>StopReason Generation</label>
<div class="radio-group"> <div class="radio-group">
<input type="radio" id="stopReasonYes" name="stopReason" value="Yes" checked> <input type="radio" id="stopReasonYes" name="stopReason" value="Yes" checked>
<label for="stopReasonYes">Yes</label> <label for="stopReasonYes">Yes</label>
<input type="radio" id="stopReasonNo" name="stopReason" value="No"> <input type="radio" id="stopReasonNo" name="stopReason" value="No">
<label for="stopReasonNo">No</label> <label for="stopReasonNo">No</label>
</div>
<small>Generate the special variable for the root cause of the machine stop. Boolean variable to trigger the read of properties alarmCode and alarmMessage. The StopReason is defined by PackML.</small>
</div> </div>
<small>Generate the special variable for the root cause of the machine stop. Boolean variable to trigger the read of properties alarmCode and alarmMessage. The StopReason is defined by PackML.</small> </fieldset>
</div>
</fieldset>
<fieldset> <fieldset>
<legend>ADD-INs</legend> <legend>ADD-INs</legend>
<div class="form-group"> <div class="form-group">
<div class="checkbox-container"> <div class="checkbox-container">
<input type="checkbox" id="addins-acpower" name="addins" value="AcPower"> <input type="checkbox" id="addins-acpower" name="addins" value="AcPower">
<label for="addins-acpower"> <label for="addins-acpower">
<strong>ACPower ADDIN</strong> <strong>ACPower ADDIN</strong>
<small>Provides information about electrical consumption.</small> <small>Provides information about electrical consumption.</small>
</label> </label>
</div>
<div class="checkbox-container">
<input type="checkbox" id="addins-utilities" name="addins" value="Utilities">
<label for="addins-utilities">
<strong>Utilities ADDIN</strong>
<small>Used for machines which consume or produce different sources of energy.</small>
</label>
</div>
<div class="checkbox-container">
<input type="checkbox" id="addins-trackadvance" name="addins" value="TrackAdvance">
<label for="addins-trackadvance">
<strong>TrackAdvance ADDIN</strong>
<small>Applies to filling machines supporting a cleaning in place (CIP) process.</small>
</label>
</div>
<div class="checkbox-container">
<input type="checkbox" id="addins-agv" name="addins" value="AGV">
<label for="addins-agv">
<strong>AGV ADDIN</strong>
<small>Applies to Palletizers which are interfaced with AGVs.</small>
</label>
</div>
</div> </div>
<div class="checkbox-container"> </fieldset>
<input type="checkbox" id="addins-utilities" name="addins" value="Utilities"> </div>
<label for="addins-utilities"> <div id="alarms-tab" class="tab-content">
<strong>Utilities ADDIN</strong> <fieldset>
<small>Used for machines which consume or produce different sources of energy.</small> <legend>Alarms Exposed by the Machine</legend>
</label> <p>Define all alarms for the machine. If the message is empty, the alarm will not be generated.</p>
</div> <table class="dynamic-table">
<div class="checkbox-container"> <thead>
<input type="checkbox" id="addins-trackadvance" name="addins" value="TrackAdvance"> <tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Message</th><th class="col-actions">Actions</th></tr>
<label for="addins-trackadvance"> </thead>
<strong>TrackAdvance ADDIN</strong> <tbody id="alarms-table-body"></tbody>
<small>Applies to filling machines supporting a cleaning in place (CIP) process.</small> </table>
</label> <button type="button" class="add-row-btn" id="add-alarm-btn">Add Alarm</button>
</div> </fieldset>
<div class="checkbox-container"> </div>
<input type="checkbox" id="addins-agv" name="addins" value="AGV">
<label for="addins-agv">
<strong>AGV ADDIN</strong>
<small>Applies to Palletizers which are interfaced with AGVs.</small>
</label>
</div>
</div>
</fieldset>
<input type="submit" value="Validate and Submit Description"> <input type="submit" value="Validate and Submit Description">
</form> </form>
</div> </div>
<template id="alarm-row-template">
<tr>
<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><textarea name="alarm_message" rows="1" cols="50" placeholder="Message displayed on dashboard"></textarea></td>
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
</tr>
</template>
<script src="renderer.js"></script>
</body> </body>
</html> </html>

43
main.js
View File

@ -29,44 +29,55 @@ app.whenReady().then(() => {
const sp2 = ' '; const sp2 = ' ';
const sp4 = ' '; const sp4 = ' ';
const sp6 = ' '; const sp6 = ' ';
const sp8 = ' ';
// Create an array to hold each line of the YAML file // Create an array to hold each line of the YAML file
const yamlLines = []; const yamlLines = [];
// 1. Build the 'machine' section line by line // Build the 'machine' section line by line
yamlLines.push('machine:');
yamlLines.push(`${sp2}profile: ${data.machineType || 'Unspecified'}`);
yamlLines.push(`${sp2}identification:`);
// Build the 'identification' sub-section, using the [VendorName] placeholder literally
const machineType = data.machineType || 'Unspecified'; const machineType = data.machineType || 'Unspecified';
const machineModel = data.model || ''; const machineModel = data.model || '';
const serialNo = data.serialNo || ''; const serialNo = data.serialNo || '';
const VendorName = data.vendor || ''; const VendorName = data.vendor || '';
yamlLines.push('machine:');
yamlLines.push(`${sp2}profile: ${machineType}`);
yamlLines.push(`${sp2}identification:`);
yamlLines.push(`${sp4}namespace: http://${VendorName}/${machineType}/${machineModel}/${serialNo}`); yamlLines.push(`${sp4}namespace: http://${VendorName}/${machineType}/${machineModel}/${serialNo}`);
yamlLines.push(`${sp4}browseName: ${data.browseName || ''}`); yamlLines.push(`${sp4}browseName: ${data.browseName || ''}`);
yamlLines.push(`${sp4}manufacturer: "[${VendorName}]"`); yamlLines.push(`${sp4}manufacturer: "[${VendorName}]"`);
// Append the user-provided vendor as a comment, if it exists
if (data.vendor) {
yamlLines.push(`${sp4}# User-provided Vendor: ${data.vendor}`);
}
yamlLines.push(`${sp4}model: "${machineModel}"`); yamlLines.push(`${sp4}model: "${machineModel}"`);
yamlLines.push(`${sp4}serialNumber: "${serialNo}"`); yamlLines.push(`${sp4}serialNumber: "${serialNo}"`);
yamlLines.push(`${sp4}yearofconstruction: "${data.buildYear || ''}"`); yamlLines.push(`${sp4}yearofconstruction: "${data.buildYear || ''}"`);
// Add the final line for the 'machine' section
yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`); yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`);
// 2. Build and Append the 'optionals' section if needed // 2. Build the 'alarms' section from the alarms data
if (data.alarms && data.alarms.length > 0) {
yamlLines.push(''); // Blank line for separation
yamlLines.push(`${sp2}alarms:`);
yamlLines.push(`${sp4}packing: OBJECTS`); // As per PowerShell script logic
yamlLines.push(`${sp4}behavior: ${data.alarmBehavior || 'STATIC'}`);
yamlLines.push(`${sp4}count: ${data.alarms.length}`);
yamlLines.push(`${sp4}alarms: `); // Note the trailing space
data.alarms.forEach(alarm => {
const id = alarm.id || 0;
const name = alarm.name || `Alarm_${id}`;
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}`);
});
}
// 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
yamlLines.push(`${sp2}optionals:`); yamlLines.push(`${sp2}optionals:`);
yamlLines.push(`${sp4}- StopReason`); yamlLines.push(`${sp4}- StopReason`);
} }
// 3. Build and Append the 'addins' section if needed // Build and Append the 'addins' section if needed
const selectedAddins = data.addins || []; // Default to an empty array const selectedAddins = data.addins || []; // Default to an empty array
if (selectedAddins.length > 0) { if (selectedAddins.length > 0) {

View File

@ -4,59 +4,102 @@ document.addEventListener('DOMContentLoaded', () => {
console.log('DOM fully loaded. renderer.js is now running.'); console.log('DOM fully loaded. renderer.js is now running.');
// --- Real-time Filename Generation --- // --- 0. INITIALIZE FORM DEFAULTS ---
const browseNameInput = document.getElementById('BrowseName');
const filenameDisplay = document.getElementById('output-filename-display');
const updateFilename = () => {
const browseName = browseNameInput.value;
if (browseName) {
// Sanitize the name for the file system
const safeBrowseName = browseName.replace(/[^a-zA-Z0-9_-]/g, '_');
filenameDisplay.textContent = `${safeBrowseName}.yml`;
} else {
filenameDisplay.textContent = ''; // Clear if BrowseName is empty
}
};
if (browseNameInput && filenameDisplay) {
// Listen for any input in the BrowseName field and update the display
browseNameInput.addEventListener('input', updateFilename);
// Initial call to set the name if the field is pre-filled
updateFilename();
}
const publicationDateInput = document.getElementById('publicationDate'); const publicationDateInput = document.getElementById('publicationDate');
if (publicationDateInput) { if (publicationDateInput) {
publicationDateInput.value = new Date().toISOString().split('T')[0]; publicationDateInput.value = new Date().toISOString().split('T')[0];
} }
// --- 1. TAB SWITCHING LOGIC ---
const tabButtons = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
tabButtons.forEach(btn => btn.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
button.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');
// --- 2. DYNAMIC ALARM ROW LOGIC ---
const addAlarmBtn = document.getElementById('add-alarm-btn');
const alarmsTableBody = document.getElementById('alarms-table-body');
const alarmTemplate = document.getElementById('alarm-row-template');
addAlarmBtn.addEventListener('click', () => {
const currentIdInputs = alarmsTableBody.querySelectorAll('[name="alarm_id"]');
let maxId = 0;
currentIdInputs.forEach(input => {
const currentId = parseInt(input.value, 10);
if (!isNaN(currentId) && currentId > maxId) {
maxId = currentId;
}
});
const nextId = maxId + 1;
const paddedId = nextId.toString().padStart(3, '0');
const clone = alarmTemplate.content.cloneNode(true);
clone.querySelector('[name="alarm_id"]').value = nextId;
clone.querySelector('[name="alarm_name"]').value = `ALARM_${paddedId}`;
const removeBtn = clone.querySelector('.remove-row-btn');
removeBtn.addEventListener('click', (e) => {
e.target.closest('tr').remove();
});
alarmsTableBody.appendChild(clone);
});
// --- 3. FORM SUBMISSION LOGIC ---
const form = document.getElementById('machine-form'); const form = document.getElementById('machine-form');
if (form) { if (form) {
form.addEventListener('submit', async (event) => { form.addEventListener('submit', async (event) => {
event.preventDefault(); event.preventDefault();
// A. Collect data from the main "Machine" tab using FormData
const formData = new FormData(form); const formData = new FormData(form);
// Convert FormData to a plain object
// The FormData API handles checkboxes and dropdowns with the same name identically. const mainData = {};
// We just need to make sure the data object is built correctly.
const data = {};
for (const [key, value] of formData.entries()) { for (const [key, value] of formData.entries()) {
// This check prevents multi-value fields from overwriting themselves // This check prevents multi-value fields from overwriting themselves
if (!data[key]) { if (!mainData[key]) {
data[key] = value; mainData[key] = value;
} }
} }
// Use .getAll('addins') to get an array of all CHECKED addins mainData.addins = formData.getAll('addins');
data.addins = formData.getAll('addins');
console.log('Collected form data:', data); // B. Collect all alarm rows from the table
const alarmRows = alarmsTableBody.querySelectorAll('tr');
const alarmsData = [];
alarmRows.forEach(row => {
const alarm = {
id: row.querySelector('[name="alarm_id"]').value,
name: row.querySelector('[name="alarm_name"]').value,
message: row.querySelector('[name="alarm_message"]').value,
};
if (alarm.message) {
alarmsData.push(alarm);
}
});
// C. Combine all data into a single object
const finalData = { ...mainData, alarms: alarmsData };
console.log('Sending all collected data to main process:', finalData);
try { try {
const result = await window.electronAPI.submitForm(data); const result = await window.electronAPI.submitForm(finalData);
if (result.success) { if (result.success) {
form.reset(); // // Reset the entire form to its initial state
// ... (reset logic remains the same) // form.reset();
// // Re-apply the defaults that are not part of the standard reset
// document.getElementById('publicationDate').value = new Date().toISOString().split('T')[0];
// document.querySelector('#stopReasonYes').checked = true; // Set StopReason back to Yes
// updateFilename(); // Clear the filename preview
} }
} catch (error) { } catch (error) {
console.error('Error during form submission process:', error); console.error('Error during form submission process:', error);