diff --git a/caimira/apps/calculator/static/js/co2_form.js b/caimira/apps/calculator/static/js/co2_form.js index 59189d37..bf9751bd 100644 --- a/caimira/apps/calculator/static/js/co2_form.js +++ b/caimira/apps/calculator/static/js/co2_form.js @@ -1,7 +1,5 @@ -// JS file to handle manipulation on CO2 Fitting Algorithm Dialog. const CO2_data_form = [ 'CO2_data', - 'specific_breaks', 'exposed_coffee_break_option', 'exposed_coffee_duration', 'exposed_finish', @@ -9,6 +7,8 @@ const CO2_data_form = [ 'exposed_lunch_option', 'exposed_lunch_start', 'exposed_start', + 'fitting_ventilation_states', + 'fitting_ventilation_type', 'infected_coffee_break_option', 'infected_coffee_duration', 'infected_dont_have_breaks_with_exposed', @@ -21,174 +21,239 @@ const CO2_data_form = [ 'room_volume', 'total_people', 'ventilation_type', - 'windows_duration', - 'windows_frequency', - 'window_opening_regime', -] - -// Method to upload a valid excel file -function upload_file() { - clear_fitting_result_component(); - $("#generate_fitting_data").show(); - $("#save_and_dismiss_dialog").hide(); - var files = document.getElementById("file_upload").files; - if (files.length == 0) { - alert("Please choose any file..."); - return; + ]; + + // Method to upload a valid excel file + function uploadFile(endpoint) { + clearFittingResultComponent(); + const files = document.getElementById("file_upload").files; + if (files.length === 0) { + alert("Please choose any file..."); + return; } - const filename = files[0].name; - const extension = filename.substring(filename.lastIndexOf(".")).toUpperCase(); - if (extension == ".XLS" || extension == ".XLSX") { - //Here calling another method to read excel file into json - excelFileToJSON(files[0]); - } else { - alert("Please select a valid excel file."); - } -} - -//Method to read excel file and convert it into JSON -function excelFileToJSON(file) { + const file = files[0]; + const extension = file.name.substring(file.name.lastIndexOf(".")).toUpperCase(); + extension === ".XLS" || extension === ".XLSX" + ? excelFileToJSON(endpoint, file) + : alert("Please select a valid excel file."); + } + + // Method to read excel file and convert it into JSON + function excelFileToJSON(endpoint, file) { try { - var reader = new FileReader(); - reader.readAsBinaryString(file); - reader.onload = function (e) { - var data = e.target.result; - var workbook = XLSX.read(data, { type: "binary" }); - var firstSheetName = workbook.SheetNames[0]; - //reading only first sheet data - var jsonData = XLSX.utils.sheet_to_json(workbook.Sheets[firstSheetName]); - //displaying the json result into HTML table - displayJsonToHtmlTable(jsonData); - }; + const reader = new FileReader(); + reader.readAsBinaryString(file); + reader.onload = function (e) { + const data = e.target.result; + const workbook = XLSX.read(data, { type: "binary" }); + const firstSheetName = workbook.SheetNames[0]; + const jsonData = XLSX.utils.sheet_to_json(workbook.Sheets[firstSheetName]); + displayJsonToHtmlTable(endpoint, jsonData); + }; } catch (e) { - console.error(e); + console.error(e); } -} - -//Method to display the data in HTML Table -function displayJsonToHtmlTable(jsonData) { - var table = document.getElementById("display_excel_data"); - var format = document.getElementById("formatted_data"); - let structure = { times: [], CO2: [] }; + } + + // Method to display the data in HTML Table + function displayJsonToHtmlTable(endpoint, jsonData) { + const table = document.getElementById("display_excel_data"); + const format = document.getElementById("CO2_data"); + const structure = { times: [], CO2: [] }; if (jsonData.length > 0) { - var htmlData = "TimeCO2 Value"; - let jsonLength = jsonData.length; - for (var i = 0; i < jsonLength; i++) { - var row = jsonData[i]; - if (i < 5) { - htmlData += - ` - ${row["Times"].toFixed(2)} - - ${row["CO2"].toFixed(2)} - `; - } - structure["times"].push(row["Times"]); - structure["CO2"].push(row["CO2"]); + let htmlData = "TimeCO2 Value"; + const jsonLength = jsonData.length; + for (let i = 0; i < jsonLength; i++) { + const row = jsonData[i]; + if (i < 5) { + htmlData += ` + + ${row["Times"].toFixed(2)} + ${row["CO2"].toFixed(2)} + `; } - - if (jsonLength >= 5) htmlData += " ... ... "; - table.innerHTML = htmlData; - format.value = JSON.stringify(structure); - $('#generate_fitting_data').prop("disabled", false); + structure.times.push(row["Times"]); + structure.CO2.push(row["CO2"]); + } + + if (jsonLength >= 5) { + htmlData += " ... ... "; + } + format.value = JSON.stringify(structure); + $('#generate_fitting_data').prop("disabled", false); + $('#fitting_ventilation_states').prop('disabled', false); + $('[name=fitting_ventilation_type]').prop('disabled', false); + plotCO2Data(endpoint); } else { - table.innerHTML = "There is no data in the spreadsheet file"; + table.innerHTML = "There is no data in the spreadsheet file"; } -} - -// Method to download Excel template available on CERNBox -function downloadTemplate(uri = 'https://caimira-resources.web.cern.ch/CO2_template.xlsx', filename = 'CO2_template.xlsx') { - var link = document.createElement("a"); + } + + // Method to download Excel template available on CERNBox + function downloadTemplate(uri = 'https://caimira-resources.web.cern.ch/CO2_template.xlsx', filename = 'CO2_template.xlsx') { + const link = document.createElement("a"); link.download = filename; link.href = uri; document.body.appendChild(link); link.click(); document.body.removeChild(link); delete link; -} - -function insertErrorFor(referenceNode, text) { - var element = document.createElement("span"); + } + + function insertErrorFor(referenceNode, text) { + const element = document.createElement("span"); element.setAttribute("class", "error_text"); element.classList.add("red_text"); element.innerHTML = "  " + text; $(referenceNode).before(element); -} - -function validate() { + } + + function validateFormInputs(obj) { $('span.' + "error_text").remove(); let submit = true; - for (var i = 0; i < CO2_data_form.length; i++) { - let element = $(`[name=${CO2_data_form[i]}]`)[0]; - if (element.value === '') { - insertErrorFor($('#CO2_input_data_div'), `'${element.name}' must be defined.`); // raise error for total number and room volume. + for (let i = 0; i < CO2_data_form.length; i++) { + const element = $(`[name=${CO2_data_form[i]}]`)[0]; + if (element.name !== 'fitting_ventilation_states' && element.value === '') { + insertErrorFor($('#DIVCO2_data_dialog'), `'${element.name}' must be defined.
`); + submit = false; + } + } + if (submit) { + $($(obj).data('target')).modal('show'); + } + return submit; + } + +function validateCO2Form() { + let submit = true; + if (validateFormInputs($('#button_fit_data'))) submit = true; + + // Check if natural ventilation is selected + if ($('input[name="fitting_ventilation_type"]:checked')[0].value == 'fitting_natural_ventilation') { + // Validate ventilation scheme + const element = $('[name=fitting_ventilation_states')[0] + if (element.value !== '') { + // validate input format + try { + const parsedValue = JSON.parse(element.value); + if (!Array.isArray(parsedValue)) { + insertErrorFor($('#DIVCO2_fitting_result'), `'${element.name}' must be a list.
`); + submit = false; + }; + } catch { + insertErrorFor($('#DIVCO2_fitting_result'), `'${element.name}' must be a list of numbers.
`); + submit = false; + }; + } else { + insertErrorFor($('#DIVCO2_fitting_result'), `'${element.name}' must be defined.
`); submit = false; }; - } + }; + return submit; } -function display_transition_times_hour_format(start, stop) { - var minutes_start = start % 1 * 60; - var minutes_stop = stop % 1 * 60; - return Math.floor(start) + ':' + minutes_start.toPrecision(2) + ' - ' + Math.floor(stop) + ':' + minutes_stop.toPrecision(2); +function displayTransitionTimesHourFormat(start, stop) { + var minutes_start = (start % 1 * 60).toPrecision(2); + var minutes_stop = (stop % 1 * 60).toPrecision(2); + return Math.floor(start) + ':' + ((minutes_start != '0.0') ? minutes_start : '00') + ' - ' + Math.floor(stop) + ':' + ((minutes_stop != '0.0') ? minutes_stop : '00'); } -function display_fitting_data(json_response) { - $("#DIV_CO2_fitting_result").show(); +function displayFittingData(json_response) { + $("#DIVCO2_fitting_result").show(); $("#CO2_data_plot").attr("src", json_response['CO2_plot']); // Not needed for the form submit delete json_response['CO2_plot']; $("#CO2_fitting_result").val(JSON.stringify(json_response)); $("#exhalation_rate_fit").html('Exhalation rate: ' + String(json_response['exhalation_rate'].toFixed(2)) + ' m³/h'); let ventilation_table = "Time (HH:MM)ACH value (h⁻¹)"; - json_response['ventilation_values'].map((val, index) => { - let transition_times = display_transition_times_hour_format(json_response['transition_times'][index], json_response['transition_times'][index + 1]); + json_response['ventilation_values'].forEach((val, index) => { + let transition_times = displayTransitionTimesHourFormat(json_response['transition_times'][index], json_response['transition_times'][index + 1]); ventilation_table += `${transition_times}${val.toPrecision(2)}`; }); + $('#disable_fitting_algorithm').prop('disabled', false); $("#ventilation_rate_fit").html(ventilation_table); $("#generate_fitting_data").html('Fit data'); $("#generate_fitting_data").hide(); $("#save_and_dismiss_dialog").show(); } -function submit_fitting_algorithm(url) { - if (validate()) { - let CO2_mapping = {}; - CO2_data_form.map(el => { - let element = $(`[name=${el}]`); - // Validate radio buttons - if (element.length != 1) CO2_mapping[element[0].name] = $(`[name=${element[0].name}]:checked`)[0].value - else CO2_mapping[element[0].name] = element[0].value; - }) - $('#CO2_input_data_div').show(); - $("#generate_fitting_data").html( - `Loading...` - ); - $('#CO2_input_data').html(JSON.stringify(CO2_mapping, null, "\t")) +function formatCO2DataForm(CO2_data_form) { + let CO2_mapping = {}; + CO2_data_form.map(el => { + let element = $(`[name=${el}]`); + // Validate checkboxes + if (element[0].type == 'checkbox') { + CO2_mapping[element[0].name] = String(+element[0].checked); + } + // Validate radio buttons + else if (element[0].type == 'radio') CO2_mapping[element[0].name] = $(`[name=${element[0].name}]:checked`)[0].value; + else CO2_mapping[element[0].name] = element[0].value; + }); + return CO2_mapping; +} + +function plotCO2Data(url) { + if (validateFormInputs()) { + let CO2_mapping = formatCO2DataForm(CO2_data_form); fetch(url, { method: "POST", body: JSON.stringify(CO2_mapping), }) - .then((response) => response.json()) - .then((json_response) => { - display_fitting_data(json_response); - }); + .then((response) => + response.json() + .then(json_response => $("#CO2_data_plot").attr("src", json_response['CO2_plot'])) + .then($('#DIVCO2_fitting_to_submit').show()) + .catch(error => console.log(error)) + ); } } -function clear_fitting_result_component() { - $('#generate_fitting_data').prop("disabled", true); - $("#display_excel_data tbody").remove(); +function submitFittingAlgorithm(url) { + if (validateCO2Form()) { + // Disable all the ventilation inputs + $('#fitting_ventilation_states, [name=fitting_ventilation_type]').prop('disabled', true); + + // Prepare data for submission + const CO2_mapping = formatCO2DataForm(CO2_data_form); + $('#CO2_input_data_div').show(); + $('#disable_fitting_algorithm').prop('disabled', true); + $('#generate_fitting_data') + .html('Loading...') + .prop('disabled', true); + $('#CO2_input_data').html(JSON.stringify(CO2_mapping, null, '\t')); + + fetch(url, { + method: 'POST', + body: JSON.stringify(CO2_mapping), + }) + .then((response) => response.json()) + .then((json_response) => { + displayFittingData(json_response); + }); + } + } + + function clearFittingResultComponent() { + // Remove all the previously generated fitting elements + $('#generate_fitting_data').prop('disabled', true); $('#CO2_fitting_result').val(''); - $('#formatted_data').val(''); - $('span.' + "error_text").remove(); - $('#DIV_CO2_fitting_result').hide(); - $('#CO2_input_data_div').hide(); -} - -function clear_fitting_algorithm() { - clear_fitting_result_component(); - $('#CO2_data_no').click(); - ventilation_from_fitting(false); -} + $('#CO2_data').val('{}'); + $('#fitting_ventilation_states').val(''); + $('span.error_text').remove(); + $('#DIVCO2_fitting_result, #CO2_input_data_div').hide(); + $('#CO2_data_plot').attr('src', ''); + + // Update the ventilation scheme components + $('#fitting_ventilation_states, [name=fitting_ventilation_type]').prop('disabled', false); + + // Update the bottom right buttons + $('#generate_fitting_data').show(); + $('#save_and_dismiss_dialog').hide(); + } + + function disableFittingAlgorithm() { + clearFittingResultComponent(); + $('#CO2_data_no').click(); + } + diff --git a/caimira/apps/calculator/static/js/form.js b/caimira/apps/calculator/static/js/form.js index 72eff671..f497a3c4 100644 --- a/caimira/apps/calculator/static/js/form.js +++ b/caimira/apps/calculator/static/js/form.js @@ -247,8 +247,6 @@ function on_ventilation_type_change() { ventilation_types = $('input[type=radio][name=ventilation_type]'); ventilation_types.each(function (index) { if (this.checked) { - - if ($(this).val() != 'from_fitting') $('#button_fit_data').attr('data-previous-vent', $(this).val()) getChildElement($(this)).show(); require_fields(this); } else { @@ -495,32 +493,13 @@ function on_coffee_break_option_change() { } } -function ventilation_from_fitting(condition_from_fitting) { - $('input[type=radio][id=no_ventilation]').prop("disabled", condition_from_fitting); - $('input[type=radio][id=mechanical_ventilation]').prop("disabled", condition_from_fitting); - $('input[type=radio][id=natural_ventilation]').prop("disabled", condition_from_fitting); - $('input[type=radio][id=from_fitting]').prop("disabled", !condition_from_fitting); - if (condition_from_fitting) { - $('input[type=radio][id=from_fitting]').prop('checked',true); - $('#DIVfrom_fitting').after($('#window_opening_regime')); - } - else { - let selected_ventilation = $("#button_fit_data").attr('data-previous-vent'); - $(`input[type=radio][id=${selected_ventilation}]`).prop('checked',true); - $('#DIVopening_distance').after($('#window_opening_regime')); - } - on_ventilation_type_change(); -} - -function on_CO2_data_option_change() { - CO2_data_options = $('input[type=radio][name=CO2_data_option]'); - CO2_data_options.each(function (index){ +function on_CO2_fitting_ventilation_change() { + ventilation_options = $('input[type=radio][name=fitting_ventilation_type]'); + ventilation_options.each(function (index) { if (this.checked) { - if (this.id == 'CO2_data_yes') ventilation_from_fitting(true); - else if (this.id == 'CO2_data_no') ventilation_from_fitting(false); getChildElement($(this)).show(); require_fields(this); - } + } else { getChildElement($(this)).hide(); require_fields(this); @@ -700,6 +679,13 @@ function validate_form(form) { on_short_range_option_change(); } + // Check if fitting is selected + if ($('input[type=radio][id=from_fitting]').prop('checked') ) { + if ($('#CO2_fitting_result').val() == '') + $("input[type=radio][id=no_ventilation]").prop("checked", true); + on_ventilation_type_change(); + } + if (submit) { $("#generate_report").prop("disabled", true); //Add spinner to button @@ -969,7 +955,7 @@ $(document).ready(function () { // Populate CO2 Fitting Algorithm Dialog let CO2_data = url.searchParams.has('CO2_fitting_result') ? url.searchParams.get('CO2_fitting_result') : null; - if (CO2_data) display_fitting_data(JSON.parse(CO2_data)); + if (CO2_data) displayFittingData(JSON.parse(CO2_data)); // Populate primary vaccine dropdown $("#vaccine_type option").remove(); @@ -1086,11 +1072,11 @@ $(document).ready(function () { // Call the function now to handle forward/back button presses in the browser. on_coffee_break_option_change(); - // When the CO2_data_option changes we want to make its respective + // When the ventilation on the fitting changes we want to make its respective // children show/hide. - $("input[type=radio][name=CO2_data_option]").change(on_CO2_data_option_change); + $("input[type=radio][name=fitting_ventilation_type]").change(on_CO2_fitting_ventilation_change); // Call the function now to handle forward/back button presses in the browser. - on_CO2_data_option_change(); + on_CO2_fitting_ventilation_change(); // Setup the maximum number of people at page load (to handle back/forward), // and update it when total people is changed.