From 708b625fbe0c8899572b8e34b4e9b76510bc80e9 Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Wed, 21 Feb 2024 01:17:15 +0100 Subject: [PATCH] modify itemlist interactivity --- src/app.py | 46 +++++++++++++++++++++- src/db.py | 53 +++++++++++++++++++++++++ web/app.html | 13 +++++-- web/app.js | 22 +---------- web/fetch.js | 37 ++++++++++++++++++ web/filters_checkboxes.js | 22 +++++++++++ web/list.js | 82 +++++++++++++++++++++++++++++++++++++++ web/popups.js | 76 ++++++++++++++++++++++++++++-------- web/style.css | 4 +- 9 files changed, 311 insertions(+), 44 deletions(-) create mode 100644 web/filters_checkboxes.js diff --git a/src/app.py b/src/app.py index 1af8e26..dd03d13 100644 --- a/src/app.py +++ b/src/app.py @@ -108,6 +108,44 @@ def deleteList(): except: return str('item not found in list'), 400 +@app.route('/app/change-list-name', methods=['POST']) +def changeListName(): + data = request.get_json() + name = data.get('name') + uuid = data.get('uuid') + try: + change_list_name(uuid, name) + return jsonify({'name changed': uuid}), 200 + except: + return str('error changing name'), 400 + +@app.route('/app/change-list-description', methods=['POST']) +def changeListDescription(): + data = request.get_json() + description = data.get('description') + uuid = data.get('uuid') + try: + change_list_description(uuid, description) + return jsonify({'name changed': uuid}), 200 + except: + return str('error changing description'), 400 + +@app.route('/app/change-list-content', methods=['POST']) +def changeListContent(): + data = request.get_json() + uuid = data.get('uuid') + new = data.get('content') + try: + remove_all_from_list(uuid) + if len(new)>0 : + for item in new.split('#'): + add_to_list(uuid, item) + return jsonify({'content changed': uuid}), 200 + except: + return str('error changing content'), 400 + + + @app.route('/app/datahistory',methods = ['GET']) def data_history_request(): if request.method == 'GET': @@ -142,11 +180,17 @@ def data_history_request_filtered(): lists_uuid = [] return jsonify(get_history_filtered(lists_uuid)) -@app.route('/app/datalist',methods = ['GET']) +@app.route('/app/datalist',methods = ['GET', 'POST']) def data_list_request(): if request.method == 'GET': print("fetching data item") return jsonify(get_lists()) + elif request.method == 'POST': + data = request.get_json() + list_uuid = data.get('uuid') + details = get_list(list_uuid) + return jsonify(details), 200 + if __name__ == '__main__': app.run(debug = True) diff --git a/src/db.py b/src/db.py index a126a5a..841de49 100644 --- a/src/db.py +++ b/src/db.py @@ -304,6 +304,19 @@ def remove_from_list(list_uuid, item_uuid): connection.commit() connection.close() +def remove_all_from_list(list_uuid): + '''remove every items from an itemlist''' + connection = connect_db() + cursor = connection.cursor() + cursor.execute(""" + DELETE + FROM listcontent + WHERE list_uuid = %s + """, (list_uuid,)) + cursor.close() + connection.commit() + connection.close() + def get_lists(): connection = connect_db() cursor = connection.cursor() @@ -316,6 +329,46 @@ def get_lists(): connection.close() return results +def get_list(list_uuid): + '''return details of a single list''' + connection = connect_db() + cursor = connection.cursor() + cursor.execute(""" + SELECT name, description + FROM itemlist + WHERE uuid = %s + """, (list_uuid,)) + results = cursor.fetchall() + cursor.close() + connection.close() + return results[0] + +def change_list_name(uuid, name): + '''modify itemlist name''' + connection = connect_db() + cursor = connection.cursor() + cursor.execute(""" + UPDATE itemlist + SET name = %s + WHERE uuid = %s + """, (name, uuid)) + cursor.close() + connection.commit() + connection.close() + +def change_list_description(uuid, description): + '''modify itemlist description''' + connection = connect_db() + cursor = connection.cursor() + cursor.execute(""" + UPDATE itemlist + SET description = %s + WHERE uuid = %s + """, (description, uuid)) + cursor.close() + connection.commit() + connection.close() + def export_csv(): '''join item and history data from database and export it in ./output.csv''' connection = connect_db() diff --git a/web/app.html b/web/app.html index 0906009..153ac81 100644 --- a/web/app.html +++ b/web/app.html @@ -60,7 +60,7 @@

Include items

-
+
@@ -69,14 +69,18 @@ -
+
+
+
+ + +
@@ -104,6 +108,7 @@ + diff --git a/web/app.js b/web/app.js index b3de43c..8245a7d 100644 --- a/web/app.js +++ b/web/app.js @@ -52,28 +52,8 @@ function refresh_graphs() { render_graphs_wrapper(); // filters checkboxes -function filters_checkboxes() { - fetch_list().then(function(data){ - { - const node = document.getElementById("filters_checkboxes"); - while (node.firstChild) { - node.removeChild(node.lastChild); - } - } - const node = d3.select("#filters_checkboxes"); - for (const itemlist of data) { - var label = node.append("label"); - var input = label.append("input") - .attr("type", "checkbox") - .attr("class", "checkbox-filters") - .attr("id", `checkbox-${itemlist.uuid}`); - label.append("span") - .text(`${itemlist.name}`); - node.append("br"); - } - }) -} filters_checkboxes() + // listen to filters changes var checkedBefore = getCheckedCheckboxIds('.checkbox-filters'); $(document).ready(function(){ diff --git a/web/fetch.js b/web/fetch.js index b62a549..dedbc6d 100644 --- a/web/fetch.js +++ b/web/fetch.js @@ -181,3 +181,40 @@ async function get_data_filtered(id_array) { history_data = await fetch_history_filtered(id_array); return Array(items_data, history_data); } + +async function get_list_details(list_uuid) { + try { + // SELECT name, description + const apiUrl = `${currentUrl}app/datalist`; + const postData = { + uuid: list_uuid + }; + + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(postData) + }; + + const response = await fetch(apiUrl, requestOptions) + .catch(error => { + console.error('Error during POST request:', error); + }); + + if (response.ok) { + const rawData = await response.json(); + const listData = { + name: rawData[0], + description: rawData[1], + }; + return listData; + } else { + throw new Error('Error in server response'); + } + } catch (error) { + console.error('Error fetching data item: ', error); + throw error; + } +} diff --git a/web/filters_checkboxes.js b/web/filters_checkboxes.js new file mode 100644 index 0000000..a3a47eb --- /dev/null +++ b/web/filters_checkboxes.js @@ -0,0 +1,22 @@ +function filters_checkboxes() { + console.log("refresh filters"); + fetch_list().then(function(data){ + { + const node = document.getElementById("filters_checkboxes"); + while (node.firstChild) { + node.removeChild(node.lastChild); + } + } + const node = d3.select("#filters_checkboxes"); + for (const itemlist of data) { + var label = node.append("label"); + var input = label.append("input") + .attr("type", "checkbox") + .attr("class", "checkbox-filters") + .attr("id", `checkbox-${itemlist.uuid}`); + label.append("span") + .text(`${itemlist.name}`); + node.append("br"); + } + }) +} diff --git a/web/list.js b/web/list.js index ffebeb0..07125e5 100644 --- a/web/list.js +++ b/web/list.js @@ -23,3 +23,85 @@ async function delItemlist(uuid) { throw new Error('Error in server response'); } } + +async function changeListName(uuid, new_name) { + const apiUrl = `${currentUrl}app/change-list-name`; + const postData = { + uuid: uuid, + name: new_name, + }; + + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(postData) + }; + + const response = await fetch(apiUrl, requestOptions) + .catch(error => { + console.error('Error during POST request:', error); + }); + + if (response.ok) { + console.log(response); + } else { + throw new Error('Error in server response'); + } +} + +async function changeListDescription(uuid, new_description) { + const apiUrl = `${currentUrl}app/change-list-description`; + const postData = { + uuid: uuid, + description: new_description, + }; + + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(postData) + }; + + const response = await fetch(apiUrl, requestOptions) + .catch(error => { + console.error('Error during POST request:', error); + }); + + if (response.ok) { + console.log(response); + } else { + throw new Error('Error in server response'); + } +} + +async function changeListContent(uuid, new_content) { + const apiUrl = `${currentUrl}app/change-list-content`; + const content = new_content.join('#'); + const postData = { + uuid: uuid, + content: content, + }; + + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(postData) + }; + + const response = await fetch(apiUrl, requestOptions) + .catch(error => { + console.error('Error during POST request:', error); + }); + + if (response.ok) { + console.log(response); + } else { + throw new Error('Error in server response'); + } +} diff --git a/web/popups.js b/web/popups.js index e510a44..77c3cb6 100644 --- a/web/popups.js +++ b/web/popups.js @@ -50,8 +50,6 @@ document.getElementById("create_list_cancel").addEventListener("click", function createListPopup.style.display = 'none'; }); -// modify list - // delete list document.getElementById("delete_list").addEventListener("click", function () { // remove div content @@ -79,11 +77,11 @@ document.getElementById("delete_list").addEventListener("click", function () { deleteListPopup.style.display = 'flex'; }); // confirm delete list -document.getElementById("delete_list_ok").addEventListener("click", function () { +document.getElementById("delete_list_ok").addEventListener("click", async function () { const checked_delete = getCheckedCheckboxIds('.checkbox-delete-list'); const id_array = checked_delete.map(str => str.substring(9)); for (const uuid of id_array) { - delItemlist(uuid); + await delItemlist(uuid); } // refresh filters checkboxes filters_checkboxes(); @@ -95,11 +93,16 @@ document.getElementById("delete_list_cancel").addEventListener("click", function }); // modify list popup +var previous_modify = Object(); document.getElementById("modify_list").addEventListener("click", function () { generate_modifylist_buttons(); // modifyListPopup.style.display = 'flex'; }); +// cancel list modify +document.getElementById("modifylist_cancel").addEventListener("click", function () { + modifyListPopup.style.display = 'none'; + }); function generate_modifylist_buttons() { // remove div content const node = document.getElementById("modify_itemlist_choice"); @@ -125,24 +128,36 @@ function generate_modifylist_buttons() { }); } async function toggle_modify(itemlist_uuid) { - //
- // - // - // - // - //

Include items

- //
// remove div content const node = document.getElementById("modifylist_content"); while (node.firstChild) { node.removeChild(node.lastChild); } - // input name - // input description - // content + // new content + previous_modify.uuid = itemlist_uuid; const existing_content = Object.keys(await fetch_item_filtered([itemlist_uuid])); + previous_modify.content = existing_content; + const list_data = await get_list_details(itemlist_uuid); const modify_content = d3.select("#modifylist_content"); - const checkboxContainer = modify_content.append("div"); + // dropdown menu text + document.getElementById("modify_itemlist_dropdown").innerHTML = `${list_data.name}`; + // input name + previous_modify.name = list_data.name; + const input_name = modify_content.append("input") + .attr("type", "text") + .attr("placeholder", "name") + .attr("value", `${list_data.name}`) + .attr("id", `input_name-${itemlist_uuid}`); + // input description + const input_description = modify_content.append("input") + .attr("type", "text") + .attr("placeholder", "description") + .attr("value", `${list_data.description}`) + .attr("id", `input_description-${itemlist_uuid}`); + previous_modify.description = list_data.description; + // content + const checkboxContainer = modify_content.append("div") + .attr("class", "item_checkboxes"); fetch_item().then(function(data){ Object.keys(data).forEach(uuid => { const item = data[uuid]; @@ -150,7 +165,7 @@ async function toggle_modify(itemlist_uuid) { const checkbox = div.append("input") .attr("type", "checkbox") - .attr("class", "checkbox-itemlist") + .attr("class", "checkbox-modify-itemlist") .attr("id", `checkbox-${uuid}`); if (existing_content.includes(`${uuid}`)) { checkbox.property("checked", true); @@ -167,6 +182,35 @@ async function toggle_modify(itemlist_uuid) { }); }); } +// confirm list modify +document.getElementById("modifylist_ok").addEventListener("click", async function () { + const uuid = previous_modify.uuid; + const new_name = document.getElementById(`input_name-${uuid}`).value; + const new_description = document.getElementById(`input_description-${uuid}`).value; + const checked = getCheckedCheckboxIds('.checkbox-modify-itemlist'); + const new_content = checked.map(str => str.substring(9)); + var diff = false; + if (new_name != previous_modify.name) { + // name different + diff = true; + await changeListName(uuid, new_name); + } + if (new_description != previous_modify.description) { + // description different + diff = true; + await changeListDescription(uuid, new_description); + } + if (!(new_content.every(item => previous_modify.content.includes(item)) && previous_modify.content.every(item => new_content.includes(item)))) { + // content different + diff = true; + await changeListContent(uuid, new_content); + } + if (diff) { + // refresh filters checkboxes + filters_checkboxes(); + } + modifyListPopup.style.display = 'none'; + }); // delete item, pop-up confirm document.addEventListener('DOMContentLoaded', function () { diff --git a/web/style.css b/web/style.css index 32b3c85..917c4a4 100644 --- a/web/style.css +++ b/web/style.css @@ -42,14 +42,14 @@ body { padding: 0px; } -#checkbox-container { +.item_checkboxes { max-height: 50%; overflow-y: auto; display: flex; flex-wrap: wrap; } -#checkbox-container div { +.item_checkboxes div { margin-bottom: 10px; width: calc(50% - 10px); display: flex;