/** * @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 */ /** * @typedef {Object} BroadcastZone * @property {number} index * @property {string} description * @property {String} SoundChannel * @property {String} Box * @property {String} Relay */ /** * 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 {Select2item[]} */ let select2data = []; /** * 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 = [] /** * List of broadcast zones available * @type {BroadcastZone[]} */ let BroadcastZoneList = []; /** * Create a list item element * @param {String} text Text Content for the list item * @param {String} className Specific class name for the list item * @returns {JQuery} */ function ListItem(text, className=""){ return $('
  • ').addClass(className).text(text); } /** * 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, default "SoundBank/" */ function reloadSoundBank(APIURL = "SoundBank/") { 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, default "MessageBank/" */ function reloadMessageBank(APIURL = "MessageBank/") { 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, default "LanguageLink/" */ function reloadLanguageBank(APIURL = "LanguageLink/") { 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, default "ScheduleBank/" */ function reloadTimerBank(APIURL = "ScheduleBank/") { 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 , default "Log/" * @param {String} date date in format dd-mm-yyyy * @param {String} filter log filter text */ function reloadLogs(APIURL = "Log/", 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 reloadSoundbankFiles(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); // refill select2data select2data = soundbankfiles.map((item, index) => ({ id: index + 1, text: item })); } else console.log("reloadSoundbankFiles: okdata is not array"); }, (errdata) => { alert("Error loading soundbank files : " + errdata.message); }); } /** * Reload broadcast zones from server * @param {String} APIURL API URL endpoint (default "BroadcastZones/") */ function reloadBroadcastZones(APIURL = "BroadcastZones/") { BroadcastZoneList = []; fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => { if (Array.isArray(okdata)) { BroadcastZoneList = okdata; fill_broadcastzonetablebody(BroadcastZoneList); } else console.log("reloadBroadcastZones: okdata is not array"); }, (errdata) => { alert("Error loading broadcast zones : " + 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(); reloadSoundbankFiles(); // 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'); $btnRemove.prop('disabled', true); $btnEdit.prop('disabled', true); let APIURL = "SoundBank/"; let $modal = $('#soundbankmodal'); let $modalindex = $modal.find('#modalindex'); let $modaldescription = $modal.find('#modaldescription'); let $modaltag = $modal.find('#modaltag'); let $modalcategory = $modal.find('#modalcategory'); let $modallanguage = $modal.find('#modallanguage'); let $modalvoicetype = $modal.find('#modalvoicetype'); let $modalpath = $modal.find('#modalpath'); /** * Clear soundbank modal inputs */ function clearSoundbankModal() { $modalindex.val('').prop('disabled', true); $modaldescription.val(''); $modaltag.val(''); // fill modalcategory options from categories[] $modalcategory.empty(); categories.forEach(cat => { $modalcategory.append(new Option(cat, cat)); }); $modalcategory.val(null); // fill modallanguage options from languages[] $modallanguage.empty(); languages.forEach(lang => { $modallanguage.append(new Option(lang, lang)); }); $modallanguage.val(null); // fill modalvoicetype options from voiceTypes[] $modalvoicetype.empty(); voiceTypes.forEach(vt => { $modalvoicetype.append(new Option(vt, vt)); }); $modalvoicetype.val(null); // fill modalpath options from soundbankfiles[] // TODO read https://jeesite.com/front/jquery-select2/4.0/index.htm console.log("select2data has " + select2data.length + " items"); $('#modalpath').select2({ data: select2data }) } 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(() => { $modal.modal('show'); clearSoundbankModal(); // event on Click save button $modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () { //TODO Add soundbank save process here $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 () { // event on Click save button $modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () { //TODO Add soundbank save process here $modal.modal('hide'); }); // event on Click close button $modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () { $modal.modal('hide'); }); }); } } }); $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'); // modal for add / edit messagebank let $modal = $('#messagebankmodal'); // text input, disabled by default let $messageindex = $modal.find('#messageindex'); // text input let $messagedescription = $modal.find('#messagedescription'); // select input, options loaded from languages[] let $messagelanguage = $modal.find('#messagelanguage'); // number input from 1 to 100 let $messageannid = $modal.find('#messageannid'); // select input, options loaded from voiceTypes[] let $messagevoicetype = $modal.find('#messagevoicetype'); // list
      of available categories and phrases let $messageavailablevariables = $modal.find('#messageavailablevariables'); // list
        of selected categories and phrases let $messageselectedvariables = $modal.find('#messageselectedvariables'); // for clearing messageselectedvariables let $btnclearlist = $modal.find('#btnclearlist'); // for removing selected item from messageselectedvariables let $btnremovefromlist = $modal.find('#btnremovefromlist'); // for adding selected item from messageavailablevariables to messageselectedvariables let $btnaddtolist = $modal.find('#btnaddtolist'); /** * Refill messageavailablevariables options from categories[] * and soundbankdata with category "Phrase" if messagelanguage and messagevoicetype are selected */ function refill_messageavailablevariables() { $messageavailablevariables.empty(); categories.forEach(cat => { $messageavailablevariables.append(ListItem(`{${cat}}`)); }); if ($messagelanguage.val() && $messagevoicetype.val()) { soundbankdata .filter(sb => sb.language.toLowerCase() === $messagelanguage.val().toLowerCase()) .filter(sb => sb.voiceType.toLowerCase() === $messagevoicetype.val().toLowerCase()) .filter(sb => sb.category.toLowerCase() === "phrase") .forEach(sb => { $messageavailablevariables.append(ListItem(`[${sb.Description}]`)); }); } } /** * Clear message modal to default state */ function clearMessageModal() { $messageindex.val('').prop('disabled', true); $messagedescription.val(''); // fill messagelanguage options from languages[] $messagelanguage.empty(); languages.forEach(lang => { $messagelanguage.append(new Option(lang, lang)); }); $messagelanguage.val(null); $messagelanguage.on('change', function () { refill_messageavailablevariables(); }); // set default annid to 1 $messageannid.val(1); // fill messagevoicetype options from voiceTypes[] $messagevoicetype.empty(); voiceTypes.forEach(vt => { $messagevoicetype.append(new Option(vt, vt)); }); $messagevoicetype.val(null); $messagevoicetype.on('change', function () { refill_messageavailablevariables(); }); refill_messageavailablevariables(); $messageselectedvariables.empty(); // event on btnclearlist $btnclearlist.off('click').on('click', function () { if ($messageselectedvariables.children().length > 0) { if (confirm("Are you sure want to clear selected variables list?")) { $messageselectedvariables.empty(); } } }); // event on btnremovefromlist $btnremovefromlist.off('click').on('click', function () { let $selected = $messageselectedvariables.find('option:selected'); if ($selected.length > 0) { $selected.remove(); } }); // event on btnaddtolist $btnaddtolist.off('click').on('click', function () { let $selected = $messageavailablevariables.find('option:selected'); if ($selected.length > 0) { $selected.each(function () { $messageselectedvariables.append($(this).clone()); }); } }); } $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(() => { $modal.modal('show'); clearMessageModal(); // event on Click save button $modal.off('click.messagebanksave').on('click.messagebanksave', '#messagebanksave', function () { let description = $messagedescription.val().trim(); let language = $messagelanguage.val(); let annid = parseInt($messageannid.val()); let voicetype = $messagevoicetype.val(); let messagedetail = ""; let messagetags = ""; // iterate messageselectedvariables children $messageselectedvariables.children().each(function () { let val = $(this).text().trim(); if (val.length > 0) { if (val.startsWith('[') && val.endsWith(']')) { // categories messagetags += (messagetags.length > 0 ? " " : "") + val; messagedetail += (messagedetail.length > 0 ? " " : "") + val; } else { // phrases // find in soundbankdata by description with specified language and voicetype let sb = soundbankdata .filter(sb => sb.language.toLowerCase() === language.toLowerCase()) .filter(sb => sb.voiceType.toLowerCase() === voicetype.toLowerCase()) .find(sb => sb.Description.toLowerCase() === val.toLowerCase()); if (sb) { messagedetail += (messagedetail.length > 0 ? " " : "") + sb.Description; messagetags += (messagetags.length > 0 ? " " : "") + sb.tag; } } } }); if (description.length === 0) { alert("Description cannot be empty"); return; } if (!language) { alert("Language cannot be empty"); return; } if (isNaN(annid) || annid < 1 || annid > 100) { alert("ANN_ID must be a number between 1 and 100"); return; } if (!voicetype) { alert("Voice Type cannot be empty"); return; } if (messagedetail.length === 0 || messagetags.length === 0) { alert("Message haven't been constructed, please add categories and phrases"); return; } let mb = { Description: description, Language: language, ANN_ID: annid, Voice_Type: voicetype, Message_Detail: messagedetail, Message_TAGS: messagetags }; // send to server using fetchAPI fetchAPI(APIURL + "Add", "POST", mb, null, (okdata) => { reloadMessageBank(APIURL); alert("Success add new messagebank : " + okdata.message); }, (errdata) => { alert("Error add new messagebank : " + errdata.message); }); $modal.modal('hide'); }); // event on Click close button $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} `)) { $modal.modal('show'); clearMessageModal(); // Fill modal fields with selected messagebank data $messageindex.val(mb.index).prop('disabled', true); $messagedescription.val(mb.description); // Fill messagelanguage options and select current $messagelanguage.empty(); languages.forEach(lang => { $messagelanguage.append(new Option(lang, lang)); }); $messagelanguage.val(mb.language); // Fill messagevoicetype options and select current $messagevoicetype.empty(); voiceTypes.forEach(vt => { $messagevoicetype.append(new Option(vt, vt)); }); $messagevoicetype.val(mb.voice_Type); // Set annid $messageannid.val(mb.aNN_ID); // Refill message available variables refill_messageavailablevariables(); // Fill messageselectedvariables from message_Detail and message_TAGS $messageselectedvariables.empty(); if (mb.message_Detail) { mb.message_Detail.split(' ').forEach(val => { $messageselectedvariables.append(ListItem(val)); }); } // Save button event $modal.off('click.messagebanksave').on('click.messagebanksave', '#messagebanksave', function () { let description = $messagedescription.val().trim(); let language = $messagelanguage.val(); let annid = parseInt($messageannid.val()); let voicetype = $messagevoicetype.val(); let messagedetail = ""; let messagetags = ""; $messageselectedvariables.children().each(function () { let val = $(this).text().trim(); if (val.length > 0) { if (val.startsWith('[') && val.endsWith(']')) { messagetags += (messagetags.length > 0 ? " " : "") + val; messagedetail += (messagedetail.length > 0 ? " " : "") + val; } else { let sb = soundbankdata .filter(sb => sb.language.toLowerCase() === language.toLowerCase()) .filter(sb => sb.voiceType.toLowerCase() === voicetype.toLowerCase()) .find(sb => sb.Description && sb.Description.toLowerCase() === val.toLowerCase()); if (sb) { messagedetail += (messagedetail.length > 0 ? " " : "") + sb.Description; messagetags += (messagetags.length > 0 ? " " : "") + sb.tag; } } } }); if (description.length === 0) { alert("Description cannot be empty"); return; } if (!language) { alert("Language cannot be empty"); return; } if (isNaN(annid) || annid < 1 || annid > 100) { alert("ANN_ID must be a number between 1 and 100"); return; } if (!voicetype) { alert("Voice Type cannot be empty"); return; } if (messagedetail.length === 0 || messagetags.length === 0) { alert("Message haven't been constructed, please add categories and phrases"); return; } let mbUpdate = { Description: description, Language: language, ANN_ID: annid, Voice_Type: voicetype, Message_Detail: messagedetail, Message_TAGS: messagetags }; fetchAPI(APIURL + "UpdateByIndex/" + mb.index, "PATCH", mbUpdate, null, (okdata) => { reloadMessageBank(APIURL); alert("Success edit messagebank : " + okdata.message); }, (errdata) => { alert("Error edit messagebank : " + errdata.message); }); $modal.modal('hide'); }); // Close button event $modal.off('click.messagebankclose').on('click.messagebankclose', '#messagebankclose', function () { $modal.modal('hide'); }); } } }); $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(() => { $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" }) });