/** * @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 })); } } /** * 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; 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); if (Array.isArray(okdata)) { logdata = 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 * @param {Object} queryParams additional query parameters as object */ function DoExport(APIURL, filename, queryParams = {}) { // 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 let url = "/api/" + APIURL + "ExportXLSX"; if (queryParams && Object.keys(queryParams).length > 0) { url += "?" + new URLSearchParams(queryParams).toString(); } fetch(url, { 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) { selectedsoundrow = null; 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) => { reloadSoundBank(APIURL); alert("Success clear soundbank : " + okdata.message); }, (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) => { reloadSoundBank(APIURL); alert("Success delete soundbank : " + okdata.message); }, (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); alert("Success import soundbank : " + okdata.message); }, (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/"; let $findmessage = $('#findmessage'); $findmessage.on('input', function () { let searchTerm = $findmessage.val().toLowerCase(); if (searchTerm.length > 0) { selectedmessagerow = null; let filtered = messagebankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.message_Detail.toLowerCase().includes(searchTerm) || item.message_TAGS.toLowerCase().includes(searchTerm)); fill_messagebanktablebody(filtered); } else { selectedmessagerow = null; fill_messagebanktablebody(messagebankdata); } }); reloadMessageBank(APIURL); $btnClear.click(() => { DoClear(APIURL, "Messagebank", (okdata) => { reloadMessageBank(APIURL); alert("Success clear messagebank : " + okdata.message); }, (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) => { reloadMessageBank(APIURL); alert("Success delete messagebank : " + okdata.message); }, (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); alert("Success import messagebank : " + okdata.message); }, (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/"; let $findlanguage = $('#findlanguage'); let $modal = $('#languagemodal'); let $langid = $modal.find('#languagelinkindex'); let $langtag = $modal.find('#languagelinktag'); let $cbInd = $modal.find('#langId'); let $cbLocal = $modal.find('#langLocal'); let $cbEn = $modal.find('#langEn'); let $cbArb = $modal.find('#langArb'); let $cbJap = $modal.find('#langJap'); let $cbChi = $modal.find('#langChi'); function clearLanguageModal() { $langid.prop('disabled', true).val(''); $langtag.val(''); $cbInd.prop('checked', false); $cbLocal.prop('checked', false); $cbEn.prop('checked', false); $cbArb.prop('checked', false); $cbJap.prop('checked', false); $cbChi.prop('checked', false); } $findlanguage.on('input', function () { let searchTerm = $findlanguage.val().toLowerCase(); if (searchTerm.length > 0) { selectedlanguagerow = null; let filtered = languagebankdata.filter(item => item.tag.toLowerCase().includes(searchTerm) || item.language.toLowerCase().includes(searchTerm)); fill_languagebanktablebody(filtered); } else { selectedlanguagerow = null; fill_languagebanktablebody(languagebankdata); } }); reloadLanguageBank(APIURL); $btnClear.click(() => { DoClear(APIURL, "LanguageLink", (okdata) => { reloadLanguageBank(APIURL); alert("Success clear languageLink : " + okdata.message); }, (errdata) => { alert("Error clear languageLink : " + errdata.message); }); }); $btnAdd.click(() => { // show modal with id 'languagemodal' $modal.modal('show'); clearLanguageModal(); // save button click event $modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () { const tag = $langtag.val(); const langs = []; if ($cbInd.is(':checked')) langs.push('INDONESIA'); if ($cbLocal.is(':checked')) langs.push('LOCAL'); if ($cbEn.is(':checked')) langs.push('ENGLISH'); if ($cbArb.is(':checked')) langs.push('ARABIC'); if ($cbJap.is(':checked')) langs.push('JAPANESE'); if ($cbChi.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) => { reloadLanguageBank(APIURL); alert("Success delete language : " + okdata.message); }, (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}?`)) { clearLanguageModal(); $langid.val(ll.index); $langtag.val(ll.tag); let langs = ll.language.toUpperCase().split(';'); $cbInd.prop('checked', langs.includes('INDONESIA')); $cbLocal.prop('checked', langs.includes('LOCAL')); $cbEn.prop('checked', langs.includes('ENGLISH')); $cbArb.prop('checked', langs.includes('ARABIC')); $cbJap.prop('checked', langs.includes('JAPANESE')); $cbChi.prop('checked', langs.includes('CHINESE')); $modal.modal('show'); // save button click event $modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () { const tag = $langtag.val(); const langs = []; if ($cbInd.is(':checked')) langs.push('INDONESIA'); if ($cbLocal.is(':checked')) langs.push('LOCAL'); if ($cbEn.is(':checked')) langs.push('ENGLISH'); if ($cbArb.is(':checked')) langs.push('ARABIC'); if ($cbJap.is(':checked')) langs.push('JAPANESE'); if ($cbChi.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) => { reloadLanguageBank(APIURL); alert("Success edit language : " + okdata.message); }, (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); alert("Success import languagebank : " + okdata.message); }, (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/"; let $schedulemodal = $('#schedulemodal'); // text input let $scheduleid = $schedulemodal.find('#scheduleid'); // text input let $scheduledescription = $schedulemodal.find('#scheduledescription'); // number input 0-23 let $schedulehour = $schedulemodal.find('#schedulehour'); // number input 0-59 let $scheduleminute = $schedulemodal.find('#scheduleminute'); // text input let $schedulesoundpath = $schedulemodal.find('#schedulesoundpath'); // number input 0-5 let $schedulerepeat = $schedulemodal.find('#schedulerepeat'); // checkbox let $scheduleenable = $schedulemodal.find('#scheduleenable'); // div for list of checkboxes let $schedulezones = $schedulemodal.find('#schedulezones'); // radio button for everyday let $scheduleeveryday = $schedulemodal.find('#scheduleeveryday'); // radio button for weekdays let $schedulesunday = $schedulemodal.find('#schedulesunday'); let $schedulemonday = $schedulemodal.find('#schedulemonday'); let $scheduletuesday = $schedulemodal.find('#scheduletuesday'); let $schedulewednesday = $schedulemodal.find('#schedulewednesday'); let $schedulethursday = $schedulemodal.find('#schedulethursday'); let $schedulefriday = $schedulemodal.find('#schedulefriday'); let $schedulesaturday = $schedulemodal.find('#schedulesaturday'); // radio button for specific date let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate'); // date input let $scheduledate = $schedulemodal.find('#scheduledate'); $schedulespecialdate.on('change', function () { if ($(this).is(':checked')) { $scheduledate.prop('disabled', false); } else { $scheduledate.prop('disabled', true); } }); function clearScheduleModal() { $scheduleid.prop('disabled', true).val(''); $scheduledescription.val(''); $schedulehour.val('0'); $scheduleminute.val('0'); $schedulesoundpath.val(''); $schedulerepeat.val('0'); $scheduleenable.prop('checked', true); $scheduleeveryday.prop('checked', false); $schedulesunday.prop('checked', false); $schedulemonday.prop('checked', false); $scheduletuesday.prop('checked', false); $schedulewednesday.prop('checked', false); $schedulethursday.prop('checked', false); $schedulefriday.prop('checked', false); $schedulesaturday.prop('checked', false); $schedulespecialdate.prop('checked', false); $scheduledate.prop('disabled', true).val(''); } let $findschedule = $('#findschedule'); $findschedule.on('input', function () { let searchTerm = $findschedule.val().toLowerCase(); if (searchTerm.length > 0) { selectedtimerow = null; let filtered = schedulebankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.soundpath.toLowerCase().includes(searchTerm) || item.broadcastZones.toLowerCase().includes(searchTerm)); fill_schedulebanktablebody(filtered); } else { selectedtimerow = null; fill_schedulebanktablebody(schedulebankdata); } }); reloadTimerBank(APIURL); $btnClear.click(() => { DoClear(APIURL, "Timerbank", (okdata) => { reloadTimerBank(APIURL); alert("Success clear schedulebank : " + okdata.message); }, (errdata) => { alert("Error clear schedulebank : " + errdata.message); }); }); $btnAdd.click(() => { //TODO form add timer $schedulemodal.modal('show'); clearScheduleModal(); $schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () { $schedulemodal.modal('hide'); }); $schedulemodal.off('click.schedulesave').on('click.schedulesave', '#schedulesave', function () { // Gather form values const Description = $scheduledescription.val(); const Soundpath = $schedulesoundpath.val(); const Repeat = parseInt($schedulerepeat.val(), 10); const Enable = $scheduleenable.is(':checked'); // Collect selected days let Day = ""; if ($scheduleeveryday.is(':checked')) { Day = "Everyday"; } else if ($schedulespecialdate.is(':checked')) { Day = $scheduledate.val(); } else { if ($schedulesunday.is(':checked')) Day = "Sunday"; if ($schedulemonday.is(':checked')) Day = "Monday"; if ($scheduletuesday.is(':checked')) Day = "Tuesday"; if ($schedulewednesday.is(':checked')) Day = "Wednesday"; if ($schedulethursday.is(':checked')) Day = "Thursday"; if ($schedulefriday.is(':checked')) Day = "Friday"; if ($schedulesaturday.is(':checked')) Day = "Saturday"; } // Broadcast zones (assuming comma-separated string) const BroadcastZones = $schedulezones.val(); // Validate required fields if (!Description || !Soundpath || Day === "") { alert("Description, sound path, and day are required."); return; } // Format time as HH:mm const hour = parseInt($schedulehour.val(), 10); const minute = parseInt($scheduleminute.val(), 10); const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; // Prepare object const scheduleObj = { Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones }; fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => { alert("Success add schedule: " + okdata.message); reloadTimerBank(APIURL); }, (errdata) => { alert("Error add schedule: " + errdata.message); }); $schedulemodal.modal('hide'); }); }); $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) => { reloadTimerBank(APIURL); alert("Success delete schedule : " + okdata.message); }, (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}?`)) { $schedulemodal.modal('show'); // fill the form with existing data $scheduleid.val(sr.index); $scheduledescription.val(sr.description); let [hour, minute] = sr.time.split(':').map(num => parseInt(num, 10)); $schedulehour.val(hour.toString()); $scheduleminute.val(minute.toString()); $schedulesoundpath.val(sr.soundpath); $schedulerepeat.val(sr.repeat.toString()); $scheduleenable.prop('checked', sr.enable.toLowerCase() === 'true'); switch (sr.day) { case 'Everyday': $scheduleeveryday.click(); break; case 'Sunday': $schedulesunday.click(); break; case 'Monday': $schedulemonday.click(); break; case 'Tuesday': $scheduletuesday.click(); break; case 'Wednesday': $schedulewednesday.click(); break; case 'Thursday': $schedulethursday.click(); break; case 'Friday': $schedulefriday.click(); break; case 'Saturday': $schedulesaturday.click(); break; default: // check if the day is in format dd/mm/yyyy // and set the special date radio button and date input if (/^\d{2}\/\d{2}\/\d{4}$/.test(sr.day)) { $schedulespecialdate.click(); $scheduledate.val(sr.day); } } $schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () { $schedulemodal.modal('hide'); }); $schedulemodal.off('click.schedulesave').on('click.schedulesave', '#schedulesave', function () { // Gather form values const Description = $scheduledescription.val(); const Soundpath = $schedulesoundpath.val(); const Repeat = parseInt($schedulerepeat.val(), 10); const Enable = $scheduleenable.is(':checked'); // Collect selected days let Day = ""; if ($scheduleeveryday.is(':checked')) { Day = "Everyday"; } else if ($schedulespecialdate.is(':checked')) { Day = $scheduledate.val(); } else { if ($schedulesunday.is(':checked')) Day = "Sunday"; if ($schedulemonday.is(':checked')) Day = "Monday"; if ($scheduletuesday.is(':checked')) Day = "Tuesday"; if ($schedulewednesday.is(':checked')) Day = "Wednesday"; if ($schedulethursday.is(':checked')) Day = "Thursday"; if ($schedulefriday.is(':checked')) Day = "Friday"; if ($schedulesaturday.is(':checked')) Day = "Saturday"; } // Broadcast zones (assuming comma-separated string) const BroadcastZones = $schedulezones.val(); // Validate required fields if (!Description || !Soundpath || Day === "") { alert("Description, sound path, and day are required."); return; } // Format time as HH:mm const hour = parseInt($schedulehour.val(), 10); const minute = parseInt($scheduleminute.val(), 10); const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; // Prepare object const scheduleObj = { Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones }; fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => { alert("Success edit schedule: " + okdata.message); reloadTimerBank(APIURL); }, (errdata) => { alert("Error edit schedule: " + errdata.message); }); $schedulemodal.modal('hide'); }); } } }); $btnExport.click(() => { DoExport(APIURL, "schedulebank.xlsx", {}); }); $btnImport.click(() => { DoImport(APIURL, (okdata) => { reloadTimerBank(APIURL); alert("Success import schedulebank from XLSX : " + okdata.message); }, (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') const $btnExport = $('#btnExport'); 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); }); $btnExport.off('click').on('click', function () { DoExport(APIURL, "log.xlsx", { date: selectedlogdate, filter: 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" }) });