diff --git a/html/webpage/assets/js/broadcastzones.js b/html/webpage/assets/js/broadcastzones.js new file mode 100644 index 0000000..b668f74 --- /dev/null +++ b/html/webpage/assets/js/broadcastzones.js @@ -0,0 +1,302 @@ +/** + * @typedef {Object} BroadcastZone + * @property {number} index + * @property {string} description + * @property {String} SoundChannel + * @property {String} Box + * @property {String} Relay + */ + +/** + * List of broadcast zones available + * @type {BroadcastZone[]} + */ +let BroadcastZoneList = []; + +/** + * Currently selected broadcast zone row in the table + * @type {JQuery|null} + */ +let selectedBroadcastZoneRow = null; + +/** + * Fill broadcast zone table body with values + * @param {BroadcastZone[]} vv values to fill + */ +function fill_broadcastzonetablebody(vv) { + $('#broadcastzonetablebody').empty(); + if (!Array.isArray(vv) || vv.length === 0) return; + vv.forEach(item => { + const row = ` + ${item.index} + ${item.description} + ${item.soundChannel} + ${item.box} + ${item.relay} + `; + $('#broadcastzonetablebody').append(row); + let $addedrow = $('#broadcastzonetablebody tr:last'); + $addedrow.click(function () { + if (selectedBroadcastZoneRow) { + selectedBroadcastZoneRow.find('td').css('background-color', ''); + if (selectedBroadcastZoneRow.is($(this))) { + selectedBroadcastZoneRow = null; + $('#btnRemove').prop('disabled', true); + $('#btnEdit').prop('disabled', true); + return; + } + } + $(this).find('td').css('background-color', '#ffeeba'); + selectedBroadcastZoneRow = $(this); + $('#btnRemove').prop('disabled', false); + $('#btnEdit').prop('disabled', false); + }); + }); + $('#tablesize').text("Table Size: " + vv.length); +} + +/** + * 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)) { + //console.log("reloadBroadcastZones : ", okdata) + BroadcastZoneList = okdata; + fill_broadcastzonetablebody(BroadcastZoneList); + } else console.log("reloadBroadcastZones: okdata is not array"); + }, (errdata) => { + alert("Error loading broadcast zones : " + errdata.message); + }); +} + +$(document).ready(function () { + console.log("broadcastzones.js loaded successfully"); + selectedBroadcastZoneRow = 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_BroadcastZone = "BroadcastZones/"; + + + let $broadcastzonemodal = $('#broadcastzonemodal'); + let $broadcastzoneindex = $broadcastzonemodal.find('#broadcastzoneindex'); + let $broadcastzonedescription = $broadcastzonemodal.find('#broadcastzonedescription'); + let $broadcastzonesoundchannel = $broadcastzonemodal.find('#broadcastzonesoundchannel'); + let $broadcastzonebox = $broadcastzonemodal.find('#broadcastzonebox'); + + + + let $findzone = $('#findzone'); + $findzone.on('input', function () { + let searchTerm = $findzone.val().trim().toLowerCase(); + if (searchTerm.length > 0) { + selectedBroadcastZoneRow = null; + let filtered = broadcastzonedata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.box.toLowerCase().includes(searchTerm) || item.soundChannel.toLowerCase().includes(searchTerm) || item.relay.toLowerCase().includes(searchTerm)); + fill_broadcastzonetablebody(filtered); + } else { + selectedBroadcastZoneRow = null; + fill_broadcastzonetablebody(broadcastzonedata); + } + }); + + /** + * Find Checkbox for relays 1 to 32 + * Checkbox id is 01 to 32 with leading zero for 1 to 9 + * @param {number} id 1 - 32 + * @returns JQuery + */ + function cbRelay(id) { + return $broadcastzonemodal.find('#R' + (id < 10 ? '0' : '') + id); + } + + /** + * Clear broadcast zone modal to default state + */ + function clearBroadcastZoneModal() { + $broadcastzoneindex.prop('disabled', true).val(''); + $broadcastzonedescription.val(''); + $broadcastzonesoundchannel.val(''); + $broadcastzonebox.val(''); + for (let i = 1; i <= 32; i++) { + cbRelay(i).prop('checked', false); + } + } + + reloadBroadcastZones(APIURL_BroadcastZone); + + $btnClear.click(() => { + DoClear(APIURL_BroadcastZone, "BroadcastZones", (okdata) => { + reloadBroadcastZones(APIURL_BroadcastZone); + alert("Success clear broadcast zones: " + okdata.message); + }, (errdata) => { + alert("Error clear broadcast zones: " + errdata.message); + }); + }); + + $btnAdd.click(() => { + $broadcastzonemodal.modal('show'); + clearBroadcastZoneModal(); + + $broadcastzonemodal.off('click.broadcastzonesave').on('click.broadcastzonesave', '#broadcastzonesave', function () { + + let description = $broadcastzonedescription.val().trim(); + let soundChannel = $broadcastzonesoundchannel.val().trim(); + let box = $broadcastzonebox.val().trim(); + let relayArray = []; + for (let i = 1; i <= 32; i++) { + if (cbRelay(i).is(':checked')) { + relayArray.push(i); + } + } + let relay = relayArray.join(';'); + if (description.length === 0) { + alert("Description cannot be empty"); + return; + } + if (soundChannel.length === 0) { + alert("Sound Channel cannot be empty"); + return; + } + if (box.length === 0) { + alert("Box cannot be empty"); + return; + } + if (relayArray.length === 0) { + alert("At least one relay must be selected"); + return; + } + let bz = { + description: description, + SoundChannel: soundChannel, + Box: box, + Relay: relay + }; + fetchAPI(APIURL_BroadcastZone + "Add", "POST", bz, null, (okdata) => { + reloadBroadcastZones(APIURL_BroadcastZone); + alert("Success add new broadcast zone: " + okdata.message); + }, (errdata) => { + alert("Error add new broadcast zone: " + errdata.message); + }); + + $broadcastzonemodal.modal('hide'); + }); + $broadcastzonemodal.off('click.broadcastzoneclose').on('click.broadcastzoneclose', '#broadcastzoneclose', function () { + $broadcastzonemodal.modal('hide'); + }); + }); + + $btnRemove.click(() => { + if (selectedBroadcastZoneRow) { + let cells = selectedBroadcastZoneRow.find('td'); + /** @type {BroadcastZone} */ + let bz = { + index: cells.eq(0).text(), + description: cells.eq(1).text(), + SoundChannel: cells.eq(2).text(), + Box: cells.eq(3).text(), + Relay: cells.eq(4).text() + }; + if (confirm(`Are you sure to delete broadcast zone [${bz.index}] Description=${bz.description}?`)) { + fetchAPI(APIURL_BroadcastZone + "DeleteByIndex/" + bz.index, "DELETE", {}, null, (okdata) => { + reloadBroadcastZones(APIURL_BroadcastZone); + alert("Success delete broadcast zone: " + okdata.message); + }, (errdata) => { + alert("Error delete broadcast zone: " + errdata.message); + }); + } + } + }); + + $btnEdit.click(() => { + if (selectedBroadcastZoneRow) { + let cells = selectedBroadcastZoneRow.find('td'); + /** @type {BroadcastZone} */ + let bz = { + index: cells.eq(0).text(), + description: cells.eq(1).text(), + SoundChannel: cells.eq(2).text(), + Box: cells.eq(3).text(), + Relay: cells.eq(4).text() + }; + if (confirm(`Are you sure to edit broadcast zone [${bz.index}] Description=${bz.description} SoundChannel=${bz.SoundChannel} Box=${bz.Box} Relay=${bz.Relay}?`)) { + $broadcastzonemodal.modal('show'); + clearBroadcastZoneModal(); + $broadcastzoneindex.val(bz.index); + $broadcastzonedescription.val(bz.description); + $broadcastzonesoundchannel.val(bz.SoundChannel); + $broadcastzonebox.val(bz.Box); + if (bz.Relay) { + bz.Relay.split(';').forEach(relayId => { + let id = parseInt(relayId, 10); + cbRelay(id).prop('checked', true); + }); + } + $broadcastzonemodal.off('click.broadcastzonesave').on('click.broadcastzonesave', '#broadcastzonesave', function () { + let description = $broadcastzonedescription.val().trim(); + let soundChannel = $broadcastzonesoundchannel.val().trim(); + let box = $broadcastzonebox.val().trim(); + let relayArray = []; + for (let i = 1; i <= 32; i++) { + if (cbRelay(i).is(':checked')) { + relayArray.push(i); + } + } + let relay = relayArray.join(';'); + if (description.length === 0) { + alert("Description cannot be empty"); + return; + } + if (soundChannel.length === 0) { + alert("Sound Channel cannot be empty"); + return; + } + if (box.length === 0) { + alert("Box cannot be empty"); + return; + } + if (relayArray.length === 0) { + alert("At least one relay must be selected"); + return; + } + let bzUpdate = { + description: description, + SoundChannel: soundChannel, + Box: box, + Relay: relay + }; + fetchAPI(APIURL_BroadcastZone + "UpdateByIndex/" + bz.index, "PATCH", bzUpdate, null, (okdata) => { + reloadBroadcastZones(APIURL_BroadcastZone); + alert("Success edit broadcast zone: " + okdata.message); + }, (errdata) => { + alert("Error edit broadcast zone: " + errdata.message); + }); + $broadcastzonemodal.modal('hide'); + }); + $broadcastzonemodal.off('click.broadcastzoneclose').on('click.broadcastzoneclose', '#broadcastzoneclose', function () { + $broadcastzonemodal.modal('hide'); + }); + } + } + }); + + $btnExport.click(() => { + DoExport(APIURL_BroadcastZone, "broadcastzones.xlsx", {}); + }); + + $btnImport.click(() => { + DoImport(APIURL_BroadcastZone, (okdata) => { + reloadBroadcastZones(APIURL_BroadcastZone); + alert("Success import broadcast zones: " + okdata.message); + }, (errdata) => { + alert("Error importing broadcast zones from XLSX: " + errdata.message); + }); + }); +}); \ No newline at end of file diff --git a/html/webpage/assets/js/languagelink.js b/html/webpage/assets/js/languagelink.js new file mode 100644 index 0000000..638a498 --- /dev/null +++ b/html/webpage/assets/js/languagelink.js @@ -0,0 +1,272 @@ +/** + * @typedef {Object} LanguageBank + * @property {number} index + * @property {string} tag + * @property {string} language + * + */ + +/** List of Languagebank data loaded from server + * @type {LanguageBank[]} + */ +let languagebankdata = []; +/** + * Currently selected languagebank row in the table + * @type {JQuery|null} + */ +let selectedlanguagerow = null; + +/** + * 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); +} + +/** + * 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); + }); +} + +$(document).ready(function () { + console.log('languagebank.js loaded'); + + $('#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); + }); + + }); +}); \ No newline at end of file diff --git a/html/webpage/assets/js/log.js b/html/webpage/assets/js/log.js new file mode 100644 index 0000000..8401eb6 --- /dev/null +++ b/html/webpage/assets/js/log.js @@ -0,0 +1,97 @@ +/** + * @typedef {Object} Log + * @property {number} index + * @property {string} datenya + * @property {string} timenya + * @property {string} machine + * @property {string} description + */ + +/** List of Log data loaded from server + * @type {Log[]} + */ +let logdata = []; + +/** + * Fill log table body with values + * @param {Log[]} vv values to fill + */ +function fill_logtablebody(vv) { + $('#logtablebody').empty(); + $('#btnExport').prop('disabled', true); + $('#searchfilter').prop('disabled', true); + 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); + $('#btnExport').prop('disabled', false); + $('#searchfilter').prop('disabled', false); +} + +/** + * 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) => { + if (Array.isArray(okdata)) { + logdata = okdata; + fill_logtablebody(okdata); + } + }, (errdata) => { + alert("Error loading logs : " + errdata.message); + }); +} + +$(document).ready(function () { + console.log("log.js ready"); + 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 }); + }); +}); \ No newline at end of file diff --git a/html/webpage/assets/js/messagebank.js b/html/webpage/assets/js/messagebank.js new file mode 100644 index 0000000..17f48cb --- /dev/null +++ b/html/webpage/assets/js/messagebank.js @@ -0,0 +1,452 @@ +/** + * @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 + */ + +/** + * List of Messagebank data loaded from server + * @type {MessageBank[]} + */ +let messagebankdata = []; +/** + * Currently selected messagebank row in the table + * @type {JQuery|null} + */ +let selectedmessagerow = null; + +/** + * 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); +} + +/** +* 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); + }); +} + +$(document).ready(function () { + console.log("messagebank.js loaded"); + $('#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); + }); + }); +}); \ No newline at end of file diff --git a/html/webpage/assets/js/schedulebank.js b/html/webpage/assets/js/schedulebank.js new file mode 100644 index 0000000..419ffb6 --- /dev/null +++ b/html/webpage/assets/js/schedulebank.js @@ -0,0 +1,405 @@ +/** + * @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 + */ + +/** List of Schedulebank data loaded from server + * @type {ScheduleBank[]} + */ +let schedulebankdata = []; +/** + * Currently selected schedulebank row in the table + * @type {JQuery|null} + */ +let selectedschedulerow = null; + +/** + * 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); +} + +/** + * 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); + }); +} + +$(document).ready(function () { + console.log("schedulebank.js loaded successfully"); + $('#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); + }); + }); +}); \ No newline at end of file diff --git a/html/webpage/assets/js/script.js b/html/webpage/assets/js/script.js index 7d45816..8f99afa 100644 --- a/html/webpage/assets/js/script.js +++ b/html/webpage/assets/js/script.js @@ -1,124 +1,3 @@ -/** - * @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[]} @@ -141,195 +20,16 @@ let languages = []; */ 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=""){ +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} @@ -368,151 +68,45 @@ function fetchAPI(endpoint, method, headers = {}, body = null, cbOK, cbError) { 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/" + * Fetch asset file from /assets/img/* + * @param {String} url the filename to fetch, relative to /assets/img/ + * @param {Function} cbOK callback function on success, will receive the object URL + * @param {Function} cbError callback function on error, will receive the error object */ -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); - }); +function fetchImg(url, cbOK, cbError) { + url = "/assets/img/" + url; + fetch(url) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok ' + response.statusText); + } + return response.blob(); + }) + .then(blob => { + const url = URL.createObjectURL(blob); + cbOK(url); + }) + .catch(error => { + cbError(error); + }); } -/** -* 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 */ @@ -672,25 +266,59 @@ function DoImport(APIURL, cbOK, cbError) { fileInput.remove(); } +let $onlineindicator = null; +let $cpustatus = null; +let $ramstatus = null; +let $diskstatus = null; +let $networkstatus = null; +let $datetimetext = null; +let greencircle = null; +let redcircle = null; /** * App entry point */ $(document).ready(function () { document.title = "Automatic Announcement System" - $('#onlineindicator').attr('src', '/assets/img/red_circle.png'); + fetchImg('green_circle.png', (url) => { greencircle = url; }, (err) => { console.error("Error loading green_circle.png : ", err); }); + fetchImg('red_circle.png', (url) => { redcircle = url; }, (err) => { console.error("Error loading red_circle.png : ", err); }); + const wsURL = window.location.pathname + '/ws' + if (chrome && chrome.runtime && chrome.runtime.lastError) { + alert("Runtime error: " + chrome.runtime.lastError.message); + return; + } + $onlineindicator = $('#onlineindicator'); + $cpustatus = $('#cpustatus'); + $ramstatus = $('#ramstatus'); + $diskstatus = $('#diskstatus'); + $networkstatus = $('#networkstatus'); + $datetimetext = $('#datetimetext'); + + + // reset status indicators + function resetStatusIndicators() { + $onlineindicator.attr('src', redcircle); + $cpustatus.text("CPU : N/A"); + $ramstatus.text("RAM : N/A"); + $diskstatus.text("Disk : N/A"); + $networkstatus.text("Network : N/A"); + $datetimetext.text("Date/Time : N/A"); + } + + + resetStatusIndicators(); getVoiceTypes(); getCategories(); getLanguages(); getScheduledDays(); - reloadSoundbankFiles(); + - // Initialize WebSocket connection - ws = new WebSocket(window.location.pathname + '/ws'); + // Initialize WebSocket connection + ws = new WebSocket(wsURL); ws.onopen = () => { console.log('WebSocket connection established'); - $('#onlineindicator').attr('src', '/assets/img/green_circle.png'); + $onlineindicator.attr('src', greencircle); }; ws.onmessage = (event) => { let rep = JSON.parse(event.data); @@ -699,19 +327,19 @@ $(document).ready(function () { if (cmd && cmd.length > 0) { switch (cmd) { case "getCPUStatus": - $('#cpustatus').text("CPU Usage: " + data) + $cpustatus.text("CPU : " + data) break; case "getMemoryStatus": - $('#ramstatus').text("Memory Usage: " + data) + $ramstatus.text("RAM : " + data) break; case "getDiskStatus": - $('#diskstatus').text("Disk Usage: " + data) + $diskstatus.text("Disk : " + data) break; case "getNetworkStatus": - $('#networkstatus').text("Network Usage: " + data) + $networkstatus.text("Network : " + data) break; case "getSystemTime": - $('#datetimetext').text(data) + $datetimetext.text(data) break; } @@ -719,8 +347,10 @@ $(document).ready(function () { }; ws.onclose = () => { console.log('WebSocket connection closed'); - $('#onlineindicator').attr('src', '/assets/img/red_circle.png'); + resetStatusIndicators(); + }; + // ws.onerror = (error) => { // console.error('WebSocket error:', error); // }; @@ -751,159 +381,8 @@ $(document).ready(function () { $('#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); - }); - }); + // pindah soundbank.js + } else { console.error("Error loading soundbank content : ", xhr.status, xhr.statusText); } @@ -915,378 +394,8 @@ $(document).ready(function () { $('#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