/** * @typedef {Object} SoundBank * @property {number} index * @property {string} description * @property {string} tag * @property {string} category * @property {string} language * @property {string} voiceType * @property {string} path */ /** * @typedef {Object} MessageBank * @property {number} index * @property {string} description * @property {string} language * @property {number} aNN_ID * @property {string} voice_Type * @property {string} message_Detail * @property {string} message_TAGS */ /** * @typedef {Object} LanguageBank * @property {number} index * @property {string} tag * @property {string} language * @ */ /** * @typedef {Object} ScheduleBank * @property {number} index * @property {string} description * @property {string} day * @property {string} time * @property {string} soundpath * @property {number} repeat * @property {boolean} enable * @property {string} broadcastZones * @property {string} language */ /** * @typedef {Object} Log * @property {number} index * @property {string} datenya * @property {string} timenya * @property {string} machine * @property {string} description */ /** * @typedef {Object} Select2item * @property {number} id * @property {string} text */ /** * List of Soundbank data loaded from server * @type {SoundBank[]} */ let soundbankdata = []; /** * Currently selected soundbank row in the table * @type {JQuery|null} */ let selectedsoundrow = null; /** * List of Messagebank data loaded from server * @type {MessageBank[]} */ let messagebankdata = []; /** * Currently selected messagebank row in the table * @type {JQuery|null} */ let selectedmessagerow = null; /** List of Languagebank data loaded from server * @type {LanguageBank[]} */ let languagebankdata = []; /** * Currently selected languagebank row in the table * @type {JQuery|null} */ let selectedlanguagerow = null; /** List of Schedulebank data loaded from server * @type {ScheduleBank[]} */ let schedulebankdata = []; /** * Currently selected schedulebank row in the table * @type {JQuery|null} */ let selectedschedulerow = null; /** List of Log data loaded from server * @type {Log[]} */ let logdata = []; /** * List of sound files in the soundbank directory, that ends with .wav or .mp3 * @type {string[]} */ let soundbankfiles = []; /** * Select2 data source * See https://select2.org/data-sources/formats * @type {Object} * @property {Select2item[]} results */ let select2results = { results: [] }; /** * List of voice types available * @type {string[]} */ let voiceTypes = []; /** * List of categories available * @type {string[]} */ let categories = []; /** * List of languages available * @type {string[]} */ let languages = []; /** * List of scheduled days available * @type {string[]} */ let scheduledays = [] /** * Fill soundbank table body with values * @param {SoundBank[]} vv values to fill */ function fill_soundbanktablebody(vv) { $('#soundbanktablebody').empty(); if (!Array.isArray(vv) || vv.length === 0) return; vv.forEach(item => { const row = ` ${item.index} ${item.description} ${item.tag} ${item.category} ${item.language} ${item.voiceType} ${item.path} `; $('#soundbanktablebody').append(row); let $addedrow = $('#soundbanktablebody tr:last'); $addedrow.on('click', function () { if (selectedsoundrow) { selectedsoundrow.find('td').css('background-color', ''); if (selectedsoundrow.is($(this))) { selectedsoundrow = null; $('#btnRemove').prop('disabled', true); $('#btnEdit').prop('disabled', true); return; } } $(this).find('td').css('background-color', '#ffeeba'); selectedsoundrow = $(this); $('#btnRemove').prop('disabled', false); $('#btnEdit').prop('disabled', false); }); }); $('#tablesize').text("Table Size: " + vv.length); } /** * Fill messagebank table body with values * @param {MessageBank[]} vv values to fill */ function fill_messagebanktablebody(vv) { $('#messagebanktablebody').empty(); if (!Array.isArray(vv) || vv.length === 0) return; vv.forEach(item => { const row = ` ${item.index} ${item.description} ${item.language} ${item.aNN_ID} ${item.voice_Type} ${item.message_Detail} ${item.message_TAGS} `; $('#messagebanktablebody').append(row); let $addedrow = $('#messagebanktablebody tr:last'); $addedrow.click(function () { if (selectedmessagerow) { selectedmessagerow.find('td').css('background-color', ''); if (selectedmessagerow.is($(this))) { selectedmessagerow = null; $('#btnRemove').prop('disabled', true); $('#btnEdit').prop('disabled', true); return; } } $addedrow.find('td').css('background-color', '#ffeeba'); selectedmessagerow = $addedrow; $('#btnRemove').prop('disabled', false); $('#btnEdit').prop('disabled', false); }); }); $('#tablesize').text("Table Size: " + vv.length); } /** * Fill languagebank table body with values * @param {LanguageBank[]} vv values to fill */ function fill_languagebanktablebody(vv) { $('#languagebanktablebody').empty(); if (!Array.isArray(vv) || vv.length === 0) return; vv.forEach(item => { const row = ` ${item.index} ${item.tag} ${item.language} `; $('#languagebanktablebody').append(row); let $addedrow = $('#languagebanktablebody tr:last'); $addedrow.click(function () { if (selectedlanguagerow) { selectedlanguagerow.find('td').css('background-color', ''); if (selectedlanguagerow.is($(this))) { selectedlanguagerow = null; $('#btnRemove').prop('disabled', true); $('#btnEdit').prop('disabled', true); return; } } $addedrow.find('td').css('background-color', '#ffeeba'); selectedlanguagerow = $addedrow; $('#btnRemove').prop('disabled', false); $('#btnEdit').prop('disabled', false); }); }); $('#tablesize').text("Table Size: " + vv.length); } /** * Fill schedulebank table body with values * @param {ScheduleBank[]} vv values to fill */ function fill_schedulebanktablebody(vv) { $('#schedulebanktablebody').empty(); if (!Array.isArray(vv) || vv.length === 0) return; vv.forEach(item => { const row = ` ${item.index} ${item.description} ${item.day} ${item.time} ${item.soundpath} ${item.repeat} ${item.enable} ${item.broadcastZones} ${item.language} `; $('#schedulebanktablebody').append(row); let $addedrow = $('#schedulebanktablebody tr:last'); $addedrow.click(function () { if (selectedschedulerow) { selectedschedulerow.find('td').css('background-color', ''); if (selectedschedulerow.is($(this))) { selectedschedulerow = null; $('#btnRemove').prop('disabled', true); $('#btnEdit').prop('disabled', true); return; } } $addedrow.find('td').css('background-color', '#ffeeba'); selectedschedulerow = $addedrow; $('#btnRemove').prop('disabled', false); $('#btnEdit').prop('disabled', false); }); }); $('#tablesize').text("Table Size: " + vv.length); } /** * Fill log table body with values * @param {Log[]} vv values to fill */ function fill_logtablebody(vv) { $('#logtablebody').empty(); if (!Array.isArray(vv) || vv.length === 0) return; vv.forEach(item => { const row = ` ${item.index} ${item.datenya} ${item.timenya} ${item.machine} ${item.description} `; $('#logtablebody').append(row); }); $('#tablesize').text("Table Size: " + vv.length); } /** * WebSocket connection * @type {WebSocket} */ let ws = null; /** * Send a command to the WebSocket server. * @param {String} command command to send * @param {String} data data to send */ function sendCommand(command, data) { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ command, data })); } else { console.error('WebSocket is not open'); } } /** * Fetch API helper function * @param {string} endpoint Endpoint URL * @param {string} method Method (GET, POST, etc.) * @param {Object} headers Headers to include in the request * @param {Object} body Body of the request * @param {Function} cbOK Callback function for successful response * @param {Function} cbError Callback function for error response */ function fetchAPI(endpoint, method, headers = {}, body = null, cbOK, cbError) { let url = window.location.origin + "/api/" + endpoint; let options = { method: method, headers: headers } if (body !== null) { options.body = JSON.stringify(body); if (!options.headers['Content-Type']) { options.headers['Content-Type'] = 'application/json'; } } //console.log("About to fetch from " + url + " with options", options); fetch(url, options) .then(response => { //console.log("fetchAPI: received response", response); if (!response.ok) { throw new Error('Network response was not ok ' + response.statusText); } return response.json(); }) .then(data => { //console.log("fetchAPI: received data", data); cbOK(data); }) .catch(error => { // console.error('There was a problem with the fetch operation:', error); cbError(error); }); } /** * Reload sound bank from server * @param {String} APIURL API URL endpoint */ function reloadSoundBank(APIURL) { soundbankdata = []; fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => { if (Array.isArray(okdata)) { soundbankdata = okdata; selectedsoundrow = null; fill_soundbanktablebody(soundbankdata); } }, (errdata) => { alert("Error loading soundbank: " + errdata.message); }); } /** * Reload message bank from server * @param {string} APIURL API URL endpoint */ function reloadMessageBank(APIURL) { messagebankdata = []; fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => { if (Array.isArray(okdata)) { messagebankdata = okdata; selectedmessagerow = null; fill_messagebanktablebody(messagebankdata); } }, (errdata) => { alert("Error loading messagebank: " + errdata.message); }); } /** * Reload language bank from server * @param {string} APIURL API URL endpoint */ function reloadLanguageBank(APIURL) { languagebankdata = []; fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => { if (Array.isArray(okdata)) { languagebankdata = okdata; selectedlanguagerow = null; fill_languagebanktablebody(languagebankdata); } }, (errdata) => { alert("Error loading languagebank: " + errdata.message); }); } /** * Reload timer bank from server * @param {string} APIURL API URL endpoint */ function reloadTimerBank(APIURL) { schedulebankdata = []; fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => { if (Array.isArray(okdata)) { schedulebankdata = okdata.data; selectedschedulerow = null; fill_schedulebanktablebody(schedulebankdata); } }, (errdata) => { alert("Error loading schedulebank: " + errdata.message); }); } /** * Reload logs from server with date and filter * @param {String} APIURL API URL endpoint * @param {String} date date in format dd-mm-yyyy * @param {String} filter log filter text */ function reloadLogs(APIURL, date, filter) { const params = new URLSearchParams({ date: date, filter: filter }) fetchAPI(APIURL + "List?" + params.toString(), "GET", {}, null, (okdata) => { //console.log("Logs data received", okdata); fill_logtablebody(okdata); }, (errdata) => { alert("Error loading logs: " + errdata.message); }); } /** * Reload soundbank files from server * @param {String} APIURL API URL endpoint (default "SoundBank/") */ function regetSoundbankFiles(APIURL = "SoundBank/") { soundbankfiles = []; fetchAPI(APIURL + "ListFiles", "GET", {}, null, (okdata) => { // okdata is a string contains elements separated by semicolon ; if (Array.isArray(okdata)) { soundbankfiles = okdata.filter(item => item.trim().length > 0); //console.log("Loaded " + soundbankfiles.length + " sound files : " + soundbankfiles.join(", ") ); // refill select2results select2results.results = soundbankfiles.map((item, index) => ({ id: index + 1, text: item })); } else console.log("regetSoundbankFiles: okdata is not array"); }, (errdata) => { alert("Error loading soundbank files: " + errdata.message); }); } /** * Reload voice types from server */ function getVoiceTypes() { voiceTypes = []; fetchAPI("VoiceType", "GET", {}, null, (okdata) => { // okdata is a string contains elements separated by semicolon ; if (Array.isArray(okdata)) { voiceTypes = okdata.filter(item => item.trim().length > 0); //console.log("Loaded " + voiceTypes.length + " voice types : " + voiceTypes.join(", ")); } else console.log("getVoiceTypes: okdata is not array"); }, (errdata) => { alert("Error loading voice types: " + errdata.message); }); } /** * Reload categories from server */ function getCategories() { categories = []; fetchAPI("Category", "GET", {}, null, (okdata) => { // okdata is a string contains elements separated by semicolon ; if (Array.isArray(okdata)) { categories = okdata.filter(item => item.trim().length > 0); //console.log("Loaded " + categories.length + " categories : " + categories.join(", ")); } else console.log("getCategories: okdata is not array"); }, (errdata) => { alert("Error loading categories: " + errdata.message); }); } /** * Reload languages from server */ function getLanguages() { languages = []; fetchAPI("Language", "GET", {}, null, (okdata) => { // okdata is a string contains elements separated by semicolon ; if (Array.isArray(okdata)) { languages = okdata.filter(item => item.trim().length > 0); //console.log("Loaded " + languages.length + " languages : " + languages.join(", ") ); } else console.log("getLanguages: okdata is not array"); }, (errdata) => { alert("Error loading languages: " + errdata.message); }); } /** * Reload scheduled days from server */ function getScheduledDays() { scheduledays = []; fetchAPI("ScheduleDay", "GET", {}, null, (okdata) => { // okdata is a string contains elements separated by semicolon ; if (Array.isArray(okdata)) { scheduledays = okdata.filter(item => item.trim().length > 0); //console.log("Loaded " + scheduledays.length + " scheduled days : " + scheduledays.join(", ") ); } else console.log("getScheduledDays: okdata is not array"); }, (errdata) => { alert("Error loading scheduled days: " + errdata.message); }); } /** * Clear database mechanism * @param {String} APIURL API URL endpoint * @param {String} whattoclear what to clear * @param {Function} cbOK callback function on success * @param {Function} cbError callback function on error */ function DoClear(APIURL, whattoclear, cbOK, cbError) { if (confirm(`Are you sure want to clear ${whattoclear} ? This procedure is not reversible`)) { fetchAPI(APIURL + "List", "DELETE", {}, null, (okdata) => { cbOK(okdata); }, (errdata) => { cbError(errdata); }); } } /** * Export mechanism to XLSX file * @param {String} APIURL API URL endpoint * @param {String} filename target filename */ function DoExport(APIURL, filename) { // send GET request to APIURL + "ExportXLSX" // reply Content-Type is application/vnd.openxmlformats-officedocument.spreadsheetml.sheet // reply Content-Disposition: attachment; filename=filename // Use fetch to download the XLSX file as a blob and trigger download fetch("/api/" + APIURL + "ExportXLSX", { method: "GET", headers: {} }) .then(response => { if (!response.ok) throw new Error('Network response was not ok ' + response.statusText); return response.blob(); }) .then(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); }) .catch(error => { alert("Error export to " + filename + ": " + error.message); }); return; // prevent the rest of the function from running } /** * Import mechanism from XLSX file * @param {String} APIURL API URL endpoint * @param {Function} cbOK function that accept object data * @param {Function} cbError function that accept error object */ function DoImport(APIURL, cbOK, cbError) { // Open file selection dialog that accepts only .xlsx files // then upload to server using fetchAPI at "api/ImportXLSX" with POST method let fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.xlsx'; fileInput.onchange = e => { let file = e.target.files[0]; if (file) { let formData = new FormData(); formData.append('file', file); fetch("/api/" + APIURL + "ImportXLSX", { method: 'POST', body: formData }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok ' + response.statusText); } return response.json(); }) .then(data => { cbOK(data); }) .catch(error => { cbError(error); }); } else { cbError(new Error("No file selected")); } }; fileInput.click(); fileInput.remove(); } /** * App entry point */ $(document).ready(function () { document.title = "Automatic Announcement System" $('#onlineindicator').attr('src', '/assets/img/red_circle.png'); getVoiceTypes(); getCategories(); getLanguages(); getScheduledDays(); regetSoundbankFiles(); // Initialize WebSocket connection ws = new WebSocket(window.location.pathname + '/ws'); ws.onopen = () => { console.log('WebSocket connection established'); $('#onlineindicator').attr('src', '/assets/img/green_circle.png'); }; ws.onmessage = (event) => { let rep = JSON.parse(event.data); let cmd = rep.reply let data = rep.data; if (cmd && cmd.length > 0) { switch (cmd) { case "getCPUStatus": $('#cpustatus').text("CPU Usage: " + data) break; case "getMemoryStatus": $('#ramstatus').text("Memory Usage: " + data) break; case "getDiskStatus": $('#diskstatus').text("Disk Usage: " + data) break; case "getNetworkStatus": $('#networkstatus').text("Network Usage: " + data) break; case "getSystemTime": $('#datetimetext').text(data) break; } } }; ws.onclose = () => { console.log('WebSocket connection closed'); $('#onlineindicator').attr('src', '/assets/img/red_circle.png'); }; // ws.onerror = (error) => { // console.error('WebSocket error:', error); // }; setInterval(() => { sendCommand("getCPUStatus", "") sendCommand("getMemoryStatus", "") sendCommand("getDiskStatus", "") sendCommand("getNetworkStatus", "") sendCommand("getSystemTime", "") }, 1000) let sidemenu = new bootstrap.Offcanvas('#offcanvas-menu'); $('#showmenu').click(() => { sidemenu.show(); }) $('#homelink').click(() => { sidemenu.hide(); $('#content').load('overview.html', function (response, status, xhr) { if (status === "success") { console.log("Overview content loaded successfully"); } }); }); $('#soundbanklink').click(() => { sidemenu.hide(); $('#content').load('soundbank.html', function (response, status, xhr) { if (status === "success") { console.log("Soundbank content loaded successfully"); // initialize first state of buttons and text input $('#soundbanktablebody').empty(); selectedsoundrow = null; let $btnClear = $('#btnClear'); let $btnAdd = $('#btnAdd'); let $btnRemove = $('#btnRemove'); let $btnEdit = $('#btnEdit'); let $btnExport = $('#btnExport'); let $btnImport = $('#btnImport'); let $modal = $('#soundbankmodal'); $btnRemove.prop('disabled', true); $btnEdit.prop('disabled', true); let APIURL = "SoundBank/"; reloadSoundBank(APIURL); $('#findsoundbank').on('input', function () { let searchTerm = $(this).val().trim().toLowerCase(); if (searchTerm.length>0){ let filtered = soundbankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.tag.toLowerCase().includes(searchTerm) || item.path.toLowerCase().includes(searchTerm)); fill_soundbanktablebody(filtered); } else { selectedsoundrow = null; fill_soundbanktablebody(soundbankdata); } }); $btnClear.click(() => { DoClear(APIURL, "Soundbank", (okdata) => { alert("Success clear soundbank" + okdata.message); soundbankdata = [] selectedsoundrow = null; fill_soundbanktablebody(soundbankdata); }, (errdata) => { alert("Error clear soundbank: " + errdata.message); }); }); $btnAdd.click(() => { $('.js-example-basic-single').select2({ placeholder: 'Select a sound file', closeOnSelect: true, data: select2results, }); $modal.modal('show'); // event on Click save button $modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () { $modal.modal('hide'); }); // event on Click close button $modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () { $modal.modal('hide'); }); }); $btnRemove.click(() => { if (selectedsoundrow) { let cells = selectedsoundrow.find('td'); /** @type {SoundBank} */ let sb = { index: cells.eq(0).text(), description: cells.eq(1).text(), tag: cells.eq(2).text(), category: cells.eq(3).text(), language: cells.eq(4).text(), voiceType: cells.eq(5).text(), path: cells.eq(6).text() } if (confirm(`Are you sure to delete soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) { fetchAPI(APIURL + "DeleteByIndex/" + sb.index, "DELETE", {}, null, (okdata) => { alert("Success delete soundbank" + okdata.message); soundbankdata = soundbankdata.filter(item => item.index !== sb.index); selectedsoundrow = null; fill_soundbanktablebody(soundbankdata); }, (errdata) => { alert("Error delete soundbank: " + errdata.message); }); } } }); $btnEdit.click(() => { if (selectedsoundrow) { let cells = selectedsoundrow.find('td'); /** @type {SoundBank} */ let sb = { index: cells.eq(0).text(), description: cells.eq(1).text(), tag: cells.eq(2).text(), category: cells.eq(3).text(), language: cells.eq(4).text(), voiceType: cells.eq(5).text(), path: cells.eq(6).text() } if (confirm(`Are you sure to edit soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) { $modal.modal('show'); $modal.off('hidden.bs.modal').on('hidden.bs.modal', function () { const desc = $('#description').val(); console.log('Description input value:', desc); // You can use desc here (e.g., send to server) }); } } }); $btnExport.click(() => { DoExport(APIURL, "soundbank.xlsx"); }); $btnImport.click(() => { DoImport(APIURL, (okdata) => { reloadSoundBank(APIURL); }, (errdata) => { alert("Error importing soundbank from XLSX: " + errdata.message); }); }); } else { console.error("Error loading soundbank content:", xhr.status, xhr.statusText); } }); }) $('#messagebanklink').click(() => { sidemenu.hide(); $('#content').load('messagebank.html', function (response, status, xhr) { if (status === "success") { console.log("Messagebank content loaded successfully"); // initialize first state of buttons and text input $('#messagebanktablebody').empty(); selectedmessagerow = null; let $btnClear = $('#btnClear'); let $btnAdd = $('#btnAdd'); let $btnRemove = $('#btnRemove'); let $btnEdit = $('#btnEdit'); let $btnExport = $('#btnExport'); let $btnImport = $('#btnImport'); $btnRemove.prop('disabled', true); $btnEdit.prop('disabled', true); let APIURL = "MessageBank/"; reloadMessageBank(APIURL); $btnClear.click(() => { DoClear(APIURL, "Messagebank", (okdata) => { alert("Success clear messagebank" + okdata.message); messagebankdata = [] selectedmessagerow = null; fill_messagebanktablebody(messagebankdata); }, (errdata) => { alert("Error clear messagebank: " + errdata.message); }); }); $btnAdd.click(() => { let $modal = $('#messagebankmodal'); $modal.modal('show'); $modal.off('click.messagebanksave').on('click.messagebanksave', '#messagebanksave', function () { const index = $('#messageindex').val(); const description = $('#messagedescription').val(); const language = $('#messagelanguage').val(); const ann_id = parseInt($('#messageannid').val()); const voice_type = $('#messagevoicetype').val(); $modal.modal('hide'); }); $modal.off('click.messagebankclose').on('click.messagebankclose', '#messagebankclose', function () { $modal.modal('hide'); }); }); $btnRemove.click(() => { if (selectedmessagerow) { let cells = selectedmessagerow.find('td'); /** @type {MessageBank} */ let mb = { index: cells.eq(0).text(), description: cells.eq(1).text(), language: cells.eq(2).text(), aNN_ID: parseInt(cells.eq(3).text()), voice_Type: cells.eq(4).text(), message_Detail: cells.eq(5).text(), message_TAGS: cells.eq(6).text() } if (confirm(`Are you sure to delete messagebank [${mb.index}] Description=${mb.description}? ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) { fetchAPI(APIURL + "DeleteByIndex/" + mb.index, "DELETE", {}, null, (okdata) => { alert("Success delete messagebank" + okdata.message); messagebankdata = messagebankdata.filter(item => item.index !== mb.index); selectedmessagerow = null; fill_messagebanktablebody(messagebankdata); }, (errdata) => { alert("Error delete messagebank: " + errdata.message); }); } } }); $btnEdit.click(() => { if (selectedmessagerow) { let cells = selectedmessagerow.find('td'); /** @type {MessageBank} */ let mb = { index: cells.eq(0).text(), description: cells.eq(1).text(), language: cells.eq(2).text(), aNN_ID: parseInt(cells.eq(3).text()), voice_Type: cells.eq(4).text(), message_Detail: cells.eq(5).text(), message_TAGS: cells.eq(6).text() } if (confirm(`Are you sure to edit messagebank [${mb.index}] Description=${mb.description} ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) { //TODO send edit command } } }); $btnExport.click(() => { DoExport(APIURL, "messagebank.xlsx"); }); $btnImport.click(() => { DoImport(APIURL, (okdata) => { reloadMessageBank(APIURL); }, (errdata) => { alert("Error importing messagebank from XLSX: " + errdata.message); }); }); } else { console.error("Error loading messagebank content:", xhr.status, xhr.statusText); } }); }) $('#languagelink').click(() => { sidemenu.hide(); $('#content').load('language.html', function (response, status, xhr) { if (status === "success") { console.log("Language content loaded successfully"); // initialize first state of buttons and text input $('#languagebanktablebody').empty(); selectedlanguagerow = null; let $btnClear = $('#btnClear'); let $btnAdd = $('#btnAdd'); let $btnRemove = $('#btnRemove'); let $btnEdit = $('#btnEdit'); let $btnExport = $('#btnExport'); let $btnImport = $('#btnImport'); $btnRemove.prop('disabled', true); $btnEdit.prop('disabled', true); let APIURL = "LanguageLink/"; reloadLanguageBank(APIURL); $btnClear.click(() => { DoClear(APIURL, "LanguageLink", (okdata) => { alert("Success clear languageLink" + okdata.message); languagebankdata = [] selectedlanguagerow = null; fill_languagebanktablebody(languagebankdata); }, (errdata) => { alert("Error clear languagebank: " + errdata.message); }); }); $btnAdd.click(() => { // show modal with id 'languagemodal' let $modal = $('#languagemodal'); $modal.modal('show'); // save button click event $modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () { const tag = $('#languagelinktag').val(); const langs = []; if ($('#langID').is(':checked')) langs.push('INDONESIA'); if ($('#langLocal').is(':checked')) langs.push('LOCAL'); if ($('#langEN').is(':checked')) langs.push('ENGLISH'); if ($('#langARB').is(':checked')) langs.push('ARABIC'); if ($('#langJAP').is(':checked')) langs.push('JAPANESE'); if ($('#langCHI').is(':checked')) langs.push('CHINESE'); if (tag.length === 0) { alert("Tag cannot be empty"); return; } if (langs.length === 0) { alert("At least one language must be selected"); return; } const langString = langs.join(';'); let ll = { tag: tag, language: langString } fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => { alert("Success add language" + okdata.message); reloadLanguageBank(APIURL); }, (errdata) => { alert("Error add language: " + errdata.message); }); $modal.modal('hide'); }); // close button click event $modal.off('click.languagelinkclose').on('click.languagelinkclose', '#languagelinkclose', function () { $modal.modal('hide'); }); }); $btnRemove.click(() => { if (selectedlanguagerow) { let cells = selectedlanguagerow.find('td'); /** @type {Language} */ let ll = { index: cells.eq(0).text(), tag: cells.eq(1).text(), language: cells.eq(2).text() } if (confirm(`Are you sure to delete language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) { fetchAPI(APIURL + "DeleteByIndex/" + ll.index, "DELETE", {}, null, (okdata) => { alert("Success delete language" + okdata.message); languagebankdata = languagebankdata.filter(item => item.index !== ll.index); selectedlanguagerow = null; fill_languagebanktablebody(languagebankdata); }, (errdata) => { alert("Error delete language: " + errdata.message); }); } } }); $btnEdit.click(() => { if (selectedlanguagerow) { let cells = selectedlanguagerow.find('td'); /** @type {Language} */ let ll = { index: cells.eq(0).text(), tag: cells.eq(1).text(), language: cells.eq(2).text() } if (confirm(`Are you sure to edit language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) { let $modal = $('#languagemodal'); $modal.find('#languagelinkindex').val(ll.index); $modal.find('#languagelinktag').val(ll.tag); let langs = ll.language.split(';'); $modal.find('#langID').prop('checked', langs.includes('INDONESIA')); $modal.find('#langLocal').prop('checked', langs.includes('LOCAL')); $modal.find('#langEN').prop('checked', langs.includes('ENGLISH')); $modal.find('#langARB').prop('checked', langs.includes('ARABIC')); $modal.find('#langJAP').prop('checked', langs.includes('JAPANESE')); $modal.find('#langCHI').prop('checked', langs.includes('CHINESE')); $modal.modal('show'); // save button click event $modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () { const tag = $('#languagelinktag').val(); const langs = []; if ($('#langID').is(':checked')) langs.push('INDONESIA'); if ($('#langLocal').is(':checked')) langs.push('LOCAL'); if ($('#langEN').is(':checked')) langs.push('ENGLISH'); if ($('#langARB').is(':checked')) langs.push('ARABIC'); if ($('#langJAP').is(':checked')) langs.push('JAPANESE'); if ($('#langCHI').is(':checked')) langs.push('CHINESE'); if (tag.length === 0) { alert("Tag cannot be empty"); return; } if (langs.length === 0) { alert("At least one language must be selected"); return; } const langString = langs.join(';'); if (ll.tag === tag && ll.language === langString) { alert("No changes detected"); $modal.modal('hide'); return; } ll.tag = tag; ll.language = langString; fetchAPI(APIURL + "UpdateByIndex/" + ll.index, "PATCH", {}, ll, (okdata) => { alert("Success edit language" + okdata.message); reloadLanguageBank(APIURL); }, (errdata) => { alert("Error edit language: " + errdata.message); }); $modal.modal('hide'); }); // close button click event $modal.off('click.languagelinkclose').on('click.languagelinkclose', '#languagelinkclose', function () { $modal.modal('hide'); }); } } }); $btnExport.click(() => { DoExport(APIURL, "languagebank.xlsx"); }); $btnImport.click(() => { DoImport(APIURL, (okdata) => { reloadLanguageBank(APIURL); }, (errdata) => { alert("Error importing languagebank from XLSX: " + errdata.message); }); }); } else { console.error("Error loading language content:", xhr.status, xhr.statusText); } }); }) $('#timerlink').click(() => { sidemenu.hide(); $('#content').load('timer.html', function (response, status, xhr) { if (status === "success") { console.log("Timer content loaded successfully"); // initialize first state of buttons and text input $('#schedulebanktablebody').empty(); selectedschedulerow = null; let $btnClear = $('#btnClear'); let $btnAdd = $('#btnAdd'); let $btnEdit = $('#btnEdit'); let $btnRemove = $('#btnRemove'); let $btnExport = $('#btnExport'); let $btnImport = $('#btnImport'); $btnEdit.prop('disabled', true); $btnRemove.prop('disabled', true); let APIURL = "ScheduleBank/"; reloadTimerBank(APIURL); $btnClear.click(() => { DoClear(APIURL, "Timerbank", (okdata) => { alert("Success clear schedulebank" + okdata.message); schedulebankdata = [] selectedschedulerow = null; fill_schedulebanktablebody(schedulebankdata); }, (errdata) => { alert("Error clear schedulebank: " + errdata.message); }); }); $btnAdd.click(() => { //TODO form add timer }); $btnRemove.click(() => { if (selectedtimerow) { let cells = selectedtimerow.find('td'); /** @type {ScheduleBank} */ let sr = { index: cells.eq(0).text(), description: cells.eq(1).text(), day: cells.eq(2).text(), time: cells.eq(3).text(), soundpath: cells.eq(4).text(), repeat: cells.eq(5).text(), enable: cells.eq(6).text(), broadcastZones: cells.eq(7).text(), language: cells.eq(8).text() } if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) { fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => { alert("Success delete schedule" + okdata.message); schedulebankdata = schedulebankdata.filter(item => item.index !== sr.index); selectedtimerow = null; fill_schedulebanktablebody(schedulebankdata); }, (errdata) => { alert("Error delete schedule: " + errdata.message); }); } } }); $btnEdit.click(() => { if (selectedtimerow) { let cells = selectedtimerow.find('td'); /** @type {ScheduleBank} */ let sr = { index: cells.eq(0).text(), description: cells.eq(1).text(), day: cells.eq(2).text(), time: cells.eq(3).text(), soundpath: cells.eq(4).text(), repeat: cells.eq(5).text(), enable: cells.eq(6).text(), broadcastZones: cells.eq(7).text(), language: cells.eq(8).text() } if (confirm(`Are you sure to edit schedule [${sr.index}] Description=${sr.description}?`)) { //TODO send edit command } } }); $btnExport.click(() => { DoExport(APIURL, "schedulebank.xlsx"); }); $btnImport.click(() => { DoImport(APIURL, (okdata) => { reloadTimerBank(APIURL); }, (errdata) => { alert("Error importing schedulebank from XLSX: " + errdata.message); }); }); } else { console.error("Error loading timer content:", xhr.status, xhr.statusText); } }); }) $('#loglink').click(() => { sidemenu.hide(); $('#content').load('log.html', function (response, status, xhr) { if (status === "success") { console.log("Log content loaded successfully"); const $logdate = $('#logdate'); const $searchfilter = $('#searchfilter'); const $logtable = $('#logtablebody') let selectedlogdate = ""; let logfilter = ""; let APIURL = "Log/"; $logtable.empty(); if (!$logdate.val()) { const today = new Date(); const dd = String(today.getDate()).padStart(2, '0'); const mm = String(today.getMonth() + 1).padStart(2, '0'); const yyyy = today.getFullYear(); $logdate.val(`${yyyy}-${mm}-${dd}`); selectedlogdate = `${dd}-${mm}-${yyyy}`; reloadLogs(APIURL, selectedlogdate, logfilter); } $logdate.off('change').on('change', function () { const selected = $(this).val(); if (selected) { const [year, month, day] = selected.split('-'); selectedlogdate = `${day}-${month}-${year}`; reloadLogs(APIURL, selectedlogdate, logfilter); } }); $searchfilter.off('input').on('input', function () { logfilter = $(this).val(); reloadLogs(APIURL, selectedlogdate, logfilter); }); } else { console.error("Error loading log content:", xhr.status, xhr.statusText); } }); }) $('#settinglink').click(() => { sidemenu.hide(); $('#content').load('setting.html', function (response, status, xhr) { if (status === "success") { console.log("Setting content loaded successfully"); //sendCommand("getSetting", ""); } else { console.error("Error loading setting content:", xhr.status, xhr.statusText); } }); }) $('#logoutlink').click(() => { window.location.href = "login.html" }) /** * Create log Request Data * @param {String} logdate in format dd/mm/yyyy * @param {String} logfilter * @returns JSON string of log Request data */ function logRequstData(logdate, logfilter) { if (logdate && logdate.length > 0) { const dateRegex = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/; if (dateRegex.test(logdate)) { // logdate is valid return JSON.stringify({ date: logdate, filter: logfilter }) } } return "" } });