From 40b0a5f527fb9aa607595024661db210cd9fa17f Mon Sep 17 00:00:00 2001 From: Jadowyne Ulve Date: Tue, 12 Aug 2025 12:29:51 -0500 Subject: [PATCH] Implemented Planner System to its basic functions --- .../database_payloads.cpython-313.pyc | Bin 27853 -> 28783 bytes .../administration/sql/CREATE/food_info.sql | 1 + .../administration/sql/CREATE/item.sql | 7 +- .../administration/sql/CREATE/item_info.sql | 3 +- .../sql/CREATE/logistics_info.sql | 3 +- .../administration/sql/CREATE/plan_events.sql | 13 + .../administration/sql/CREATE/recipes.sql | 6 +- .../administration/templates/admin_index.html | 1 + application/database_payloads.py | 24 + application/items/templates/index.html | 1 + application/items/templates/item_new.html | 1 + application/items/templates/itemlink.html | 1 + application/items/templates/transaction.html | 1 + application/items/templates/transactions.html | 1 + application/meal_planner/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 172 bytes .../meal_planner_api.cpython-313.pyc | Bin 0 -> 7462 bytes .../meal_planner_database.cpython-313.pyc | Bin 0 -> 8869 bytes application/meal_planner/meal_planner_api.py | 112 ++++ .../meal_planner/meal_planner_database.py | 187 ++++++ .../meal_planner/meal_planner_processes.py | 0 .../meal_planner/sql/insertPlanEvent.sql | 4 + .../sql/selectPlanEventsByMonth.sql | 30 + .../meal_planner/static/css/planner.css | 138 ++++ .../static/js/mealPlannerHandler.js | 596 ++++++++++++++++++ .../meal_planner/templates/meal_planner.html | 315 +++++++++ application/poe/templates/receipts.html | 1 + application/poe/templates/scanner.html | 1 + application/receipts/templates/receipt.html | 1 + .../receipts/templates/receipts_index.html | 1 + .../recipes/templates/recipe_edit.html | 1 + .../recipes/templates/recipe_view.html | 1 + .../recipes/templates/recipes_index.html | 1 + application/shoppinglists/templates/edit.html | 1 + .../shoppinglists/templates/lists.html | 1 + application/shoppinglists/templates/view.html | 1 + logs/database.log | 93 ++- plu_items.csv | 319 ++++++++++ static/css/dark-mode.css | 127 +++- webserver.py | 2 + 40 files changed, 1988 insertions(+), 8 deletions(-) create mode 100644 application/administration/sql/CREATE/plan_events.sql create mode 100644 application/meal_planner/__init__.py create mode 100644 application/meal_planner/__pycache__/__init__.cpython-313.pyc create mode 100644 application/meal_planner/__pycache__/meal_planner_api.cpython-313.pyc create mode 100644 application/meal_planner/__pycache__/meal_planner_database.cpython-313.pyc create mode 100644 application/meal_planner/meal_planner_api.py create mode 100644 application/meal_planner/meal_planner_database.py create mode 100644 application/meal_planner/meal_planner_processes.py create mode 100644 application/meal_planner/sql/insertPlanEvent.sql create mode 100644 application/meal_planner/sql/selectPlanEventsByMonth.sql create mode 100644 application/meal_planner/static/css/planner.css create mode 100644 application/meal_planner/static/js/mealPlannerHandler.js create mode 100644 application/meal_planner/templates/meal_planner.html create mode 100644 plu_items.csv diff --git a/application/__pycache__/database_payloads.cpython-313.pyc b/application/__pycache__/database_payloads.cpython-313.pyc index 134fe43aeade247942effad063ee847f1e1c100f..0d264997383dd24c4aedf50a32ec744d4926e7c8 100644 GIT binary patch delta 854 zcmZ9IPfXKL9LL`q+gM8(9hR{){2iHtL{L;B$`X)2V*ImC>K_mY)~#VxHkQ|7qEBY~@WpJG2pw;ip^4>8kh)j-fbGFUd$9=<`7Wp}xusk@&o?E4BTy>a#{a$Q*u6 zd#5_-^catWbSok8GXXV_WZ4<7Tw!I zQrRzg`p3@)c+vpVyD{BL#prH0O24N#`Z3AVXS$A+?VtK5yfVr@=mrc+FPP*n#4p1( zt^$SuBY<(h7~l%v8sG-tIv@?W3CI8xzy#nHKn2_e+yUHWc;_Pn;3e)`L3=4V-h W-N^j1Ej@bc!!m7q+(LI28vg;hi{S$R delta 314 zcmaF=fbr~2M!wIyyj%=Gz+5yvL&a_)p9EvnM)hcBmUJdftIbQ8k0nmNkTIRnd~-(T zA;x-3Mh1pkteGXLxy42QfSQWTfC5FfAi^9(Sbzu{5Mc!(tU-i5h;RfE4j{rAL^y#6 zR}kR>BHTfQ8;I}#5uPBz3q*JWiIq%61|YT}Bg5oFnnIH=XtPc}kS)dNIQe$AGNbS0 z|Ji2TU2KczG$ r)vhQAs0N4_iZ^ban>&YTa(^D1xIH7=2L>SVnVEr!`y&&O1=b1x#Apps
    +
  • Planner
  • Recipes
  • Shopping Lists
  • Logistics
  • diff --git a/application/database_payloads.py b/application/database_payloads.py index 84cd7d8..bea0256 100644 --- a/application/database_payloads.py +++ b/application/database_payloads.py @@ -564,6 +564,30 @@ class BrandsPayload: self.name, ) + +@dataclass +class PlanEventPayload: + plan_uuid: str + event_shortname: str + event_description: str + event_date_start: datetime.datetime + event_date_end: datetime.datetime + created_by: int + recipe_uuid: str + event_type: str + + def payload(self): + return ( + self.plan_uuid, + self.event_shortname, + self.event_description, + self.event_date_start, + self.event_date_end, + self.created_by, + self.recipe_uuid, + self.event_type + ) + @dataclass class SiteManager: site_name: str diff --git a/application/items/templates/index.html b/application/items/templates/index.html index 1931405..b422f77 100644 --- a/application/items/templates/index.html +++ b/application/items/templates/index.html @@ -43,6 +43,7 @@ Apps
      +
    • Planner
    • Recipes
    • Shopping Lists
    • Logistics
    • diff --git a/application/items/templates/item_new.html b/application/items/templates/item_new.html index 0afdc15..500efcd 100644 --- a/application/items/templates/item_new.html +++ b/application/items/templates/item_new.html @@ -55,6 +55,7 @@ Apps
        +
      • Planner
      • Recipes
      • Shopping Lists
      • Logistics
      • diff --git a/application/items/templates/itemlink.html b/application/items/templates/itemlink.html index f8c5516..c37a42b 100644 --- a/application/items/templates/itemlink.html +++ b/application/items/templates/itemlink.html @@ -37,6 +37,7 @@ Apps
          +
        • Planner
        • Recipes
        • Shopping Lists
        • Logistics
        • diff --git a/application/items/templates/transaction.html b/application/items/templates/transaction.html index 2075cf1..c8ea573 100644 --- a/application/items/templates/transaction.html +++ b/application/items/templates/transaction.html @@ -41,6 +41,7 @@ Apps
            +
          • Planner
          • Recipes
          • Shopping Lists
          • Logistics
          • diff --git a/application/items/templates/transactions.html b/application/items/templates/transactions.html index 6bacda0..139839b 100644 --- a/application/items/templates/transactions.html +++ b/application/items/templates/transactions.html @@ -39,6 +39,7 @@ Apps
              +
            • Planner
            • Recipes
            • Shopping Lists
            • Logistics
            • diff --git a/application/meal_planner/__init__.py b/application/meal_planner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/application/meal_planner/__pycache__/__init__.cpython-313.pyc b/application/meal_planner/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36f3107849d32c6a96133cb4bb9774b5046c96ea GIT binary patch literal 172 zcmey&%ge<81l5W&GeGoX5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iit5r;LeoAUg zL1JD>QKfE4QDSm-Om1Rk9;!%UK|xMta$-qleqKy&YGO`&K~7>`UTRTHe0*kJW=VX! jUP0w84x8Nkl+v73yCPPg*&w@%L5z>gjEsy$%s>_Z=Y}mq literal 0 HcmV?d00001 diff --git a/application/meal_planner/__pycache__/meal_planner_api.cpython-313.pyc b/application/meal_planner/__pycache__/meal_planner_api.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0edf40ec9baed7e88e6f22f967051e4c6cf83e31 GIT binary patch literal 7462 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 v!Rm_nTfDk|z)-F quantity_on_hand) AS has_missing_ingredients + FROM cte_recipe_items + GROUP BY rp_id +) + +SELECT events.*, + COALESCE(row_to_json(recipes.*), '{}') as recipe, + COALESCE(recipe_missing_items.has_missing_ingredients, FALSE) AS has_missing_ingredients +FROM %%site_name%%_plan_events events +LEFT JOIN %%site_name%%_recipes recipes ON recipes.recipe_uuid = events.recipe_uuid +LEFT JOIN recipe_missing_items ON recipe_missing_items.rp_id = recipes.id +WHERE + event_date_end >= make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) + AND + event_date_start < (make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) + INTERVAL '1 month'); diff --git a/application/meal_planner/static/css/planner.css b/application/meal_planner/static/css/planner.css new file mode 100644 index 0000000..576ac2b --- /dev/null +++ b/application/meal_planner/static/css/planner.css @@ -0,0 +1,138 @@ +#calendar_container { + background-color: whitesmoke; + box-shadow: wheat; + border-radius: 10px; +} + +#calender_table { + table-layout: fixed; + width: 100%; +} + +#calender_table th { + width: 14.28%; + min-width: 100px; + max-width: 1fr; + height: 40px; + vertical-align: top; + box-sizing: border-box; + font-weight: bold; + background-color: whitesmoke; +} + +#calender_table td { + width: 14.28%; + min-width: 100px; + max-width: 1fr; + height: 120px; + vertical-align: top; + box-sizing: border-box; +} + +.calendar-cell-empty { + background-color: whitesmoke; + border: 1px solid rgba(155, 155, 155, 30%); +} + +.calendar-cell { + position: relative; + width: 150px; + height: 120px; + vertical-align: top; + padding: 5px; + margin: 5px; + background-color: white; + border: 1px solid rgba(155, 155, 155, 30%); +} +.calendar-cell:hover{ + background-color: whitesmoke; + border: 2px solid rgba(155, 155, 155, 30%); +} + + +.calendar-cell:hover, .calendar-cell-selected{ + background-color: whitesmoke; + border: 2px solid rgba(155, 155, 155, 30%); +} + +.date-box { + width: 25px; + height: 25px; + font-size: 18px; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + left: 5px; + top: 5px; + z-index: 2; +} + +.recipes-box { + position: absolute; + top: 5px; + left: 35px; + right: 5px; + bottom: 5px; + padding: 3px; + overflow: auto; + z-index: 1; +} + +.recipe-label.recipe-success { + background:rgb(158, 221, 145); + margin-bottom: 3px; + padding: 2px 5px; + border-radius: 3px; + font-size: 12px; + font-weight: bold; +} + +.recipe-label.recipe-error { + background-color: rgb(218, 143, 143); + margin-bottom: 3px; + padding: 2px 5px; + border-radius: 3px; + font-size: 12px; + font-weight: bold; +} + +.recipe-label.recipe-success:hover{ + background-color: rgb(178, 241, 165); + cursor: pointer; +} + +.recipe-label.recipe-success:hover, .recipe-label-selected { + background-color: rgb(178, 241, 165); +} + +.recipe-label.recipe-error:hover{ + background-color:rgb(238, 163, 163); + cursor: pointer; +} + +.recipe-label.recipe-error:hover, .recipe-label-selected { + background-color: rgb(238, 163, 163); +} + +.custom-label { + background:rgb(211, 211, 211); + margin-bottom: 3px; + padding: 2px 5px; + border-radius: 1px; + font-size: 12px; + font-weight: bold; +} + +.custom-label:hover{ + background-color: rgb(225, 255, 255); + cursor: pointer; +} + +.custom-label:hover, .custom-label-selected { + background-color: rgb(255, 255, 255); +} + +.my-list-item:hover { + background-color: whitesmoke; +} diff --git a/application/meal_planner/static/js/mealPlannerHandler.js b/application/meal_planner/static/js/mealPlannerHandler.js new file mode 100644 index 0000000..dccf52f --- /dev/null +++ b/application/meal_planner/static/js/mealPlannerHandler.js @@ -0,0 +1,596 @@ +var year = 2025 +var month = 8 +const monthNames = ["", "January", "February", "March", "April", "May", "June","July", "August", "September", "October", "November", "December"]; + +var eventsByDay = { + 3: ["Chicken Stir Fry", "Salad"], + 8: ["Spaghetti Bolognese"], + 12: ["Fish Tacos", "Rice", "Beans"], + 31: ['Brats'] +}; + +async function changeSite(site){ + console.log(site) + const response = await fetch(`/changeSite`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + site: site, + }), + }); + data = await response.json(); + transaction_status = "success" + if (data.error){ + transaction_status = "danger" + } + + UIkit.notification({ + message: data.message, + status: transaction_status, + pos: 'top-right', + timeout: 5000 + }); + location.reload(true) +} + +async function getEventsByMonth() { + const url = new URL('/planner/api/getEventsByMonth', window.location.origin); + url.searchParams.append('year', year); + url.searchParams.append('month', month); + const response = await fetch(url); + data = await response.json(); + return data.events; +} + +async function getEventByUUID(event_uuid) { + const url = new URL('/planner/api/getEventByUUID', window.location.origin); + url.searchParams.append('event_uuid', event_uuid); + const response = await fetch(url); + data = await response.json(); + return data.event; +} + +async function parseEvents(events) { + eventsByDay = {} + for (let i = 0; i < events.length; i++){ + console.log(`new event -- ${events[i].event_shortname}`) + let event_date_start = new Date(events[i].event_date_start) + let event_date_end = new Date(events[i].event_date_end) + + let this_month = month + let start_day = event_date_start.getUTCDate() + let start_month = event_date_start.getUTCMonth() + 1 + let end_day = event_date_end.getUTCDate() + let end_month = event_date_end.getUTCMonth() + 1 + + if(start_month !== this_month){ + start_day = 1 + } + + if(end_month !== this_month){ + end_day = new Date(year, month, 0).getUTCDate(); + + } + + for (let y = start_day; y <= end_day; y++){ + if (!eventsByDay[y]) { + eventsByDay[y] = []; + } + let dayarray = eventsByDay[y] + dayarray.push(events[i]) + eventsByDay[y] = dayarray + } + + } + console.log(eventsByDay) +} + +document.addEventListener('DOMContentLoaded', async function() { + let today = new Date(); + year = today.getFullYear(); + month = today.getMonth() + 1; + await setupCalendarAndEvents() +}) + +async function setupCalendarAndEvents(){ + console.log(year, month) + events = await getEventsByMonth() + await parseEvents(events) + + await createCalender() + document.getElementById('calender_table').addEventListener('contextmenu', function(e) { + e.preventDefault(); + let recipeLabel = e.target.closest('.recipe-label'); + let calendarCell = e.target.closest('.calendar-cell'); + let customLabel = e.target.closest('.custom-label'); + if (recipeLabel) { + recipeLabel.classList.add('recipe-label-selected') + let rect = recipeLabel.getBoundingClientRect(); + let scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; + let scrollTop = window.pageYOffset || document.documentElement.scrollTop; + let menuX = rect.left + scrollLeft; + let menuY = rect.bottom + scrollTop; + showContextMenuForEvent(recipeLabel, menuX, menuY); + } else if (customLabel) { + customLabel.classList.add('custom-label-selected') + let rect = customLabel.getBoundingClientRect(); + let scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; + let scrollTop = window.pageYOffset || document.documentElement.scrollTop; + let menuX = rect.left + scrollLeft; + let menuY = rect.bottom + scrollTop; + showContextMenuForEvent(customLabel, menuX, menuY); + } else if (calendarCell) { + calendarCell.classList.add('calendar-cell-selected') + let rect = calendarCell.getBoundingClientRect(); + let scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; + let scrollTop = window.pageYOffset || document.documentElement.scrollTop; + let menuX = rect.left + scrollLeft; + let menuY = rect.bottom + scrollTop; + showContextMenuForCell(calendarCell, menuX, menuY); + } else { + hideContextMenu(); + } + }); +} + +async function createCalender() { + let calender_container = document.getElementById('calendar_container') + calender_container.innerHTML = "" + + let firstDay = new Date(year, month - 1, 1); + let numDays = new Date(year, month, 0).getDate(); + let startDay = firstDay.getDay(); + + let calender_table = document.createElement('table') + calender_table.setAttribute('id', 'calender_table') + calender_table.setAttribute('class', 'uk-table uk-table-middle uk-table-large uk-table-responsive') + let table_headers = document.createElement('thead') + table_headers.innerHTML = `SundayMondayTuesdayWednesdayThursdayFridaySaturday` + + calender_table.append(table_headers) + let tableRow = document.createElement('tr') + + for (let i = 0; i < startDay; i++){ + let table_cell = document.createElement('td') + table_cell.setAttribute('class', 'uk-table-expand uk-visible@m calendar-cell-empty') + tableRow.append(table_cell) + } + console.log(eventsByDay) + for (let day = 1; day <= numDays; day++) { + let table_cell = document.createElement('td') + let eventsHTML = ""; + if (eventsByDay[day]) { + eventsByDay[day].forEach(event => { + if(event.event_type==="recipe" && event.has_missing_ingredients){ + eventsHTML += `
              ${event.event_shortname}
              `; + } else if (event.event_type==="recipe" && !event.has_missing_ingredients){ + eventsHTML += `
              ${event.event_shortname}
              `; + } else { + eventsHTML += `
              ${event.event_shortname}
              `; + } + + }); + } + + table_cell.innerHTML = `
              ${day}
              ${eventsHTML}
              `; + table_cell.classList.add("calendar-cell"); + table_cell.dataset.day = day; + + tableRow.append(table_cell) + if ((startDay + day) % 7 === 0 && day !== numDays){ + calender_table.append(tableRow) + tableRow = document.createElement('tr') + }; + } + + let lastDayOfWeek = (startDay + numDays - 1) % 7; + for (let i = lastDayOfWeek + 1; i <= 6; i++) { + let table_cell = document.createElement('td') + table_cell.setAttribute('class', 'uk-visible@m calendar-cell-empty') + tableRow.append(table_cell) + } + + calender_table.append(tableRow) + + let table_footer = document.createElement('tr') + table_footer.innerHTML = `` + + calender_table.append(table_footer) + calender_container.append(calender_table) + + document.getElementById("month-year-title").innerHTML = `${monthNames[month]} ${year}`; +} + +function showContextMenuForEvent(eventLabel, x, y) { + const menu = document.getElementById('calendarContextMenu'); + // Set only "Edit" and "Remove" (and optionally "Add Another") + menu.className = "uk-dropdown uk-open"; + menu.innerHTML = ` + + `; + menu.style.display = 'block'; + menu.style.left = x + 'px'; + menu.style.top = y + 'px'; +} + +function showContextMenuForCell(calendarCell, x, y) { + const menu = document.getElementById('calendarContextMenu'); + // Only "Add Event" + menu.className = "uk-dropdown uk-open"; + menu.innerHTML = ` + + `; + menu.style.display = 'block'; + menu.style.left = x + 'px'; + menu.style.top = y + 'px'; +} + +window.addEventListener('click', function() { + document.getElementById('calendarContextMenu').style.display = 'none'; + document.querySelectorAll('.calendar-cell-selected').forEach(el => el.classList.remove('calendar-cell-selected')); + document.querySelectorAll('.custom-label-selected').forEach(el => el.classList.remove('custom-label-selected')); + document.querySelectorAll('.recipe-label-selected').forEach(el => el.classList.remove('recipe-label-selected')); +}); + +async function addEvent(day) { + let menu = document.getElementById('calendarContextMenu'); + //let day = menu.getAttribute('data-day') + console.log(year, month, day) + let customDate = new Date(year, month-1, day); + document.getElementById('event_date_start').value = customDate.toISOString().split('T')[0]; + document.getElementById('event_date_end').value = customDate.toISOString().split('T')[0]; + UIkit.modal(document.getElementById('eventModal')).show(); +} + +async function editEvent(event_uuid) { + console.log(event_uuid) + let event = await getEventByUUID(event_uuid) + console.log(event) + + document.getElementById('event_uuid_edit').value = event_uuid + + let event_date_start = new Date(event.event_date_start) + let y = event_date_start.getFullYear(); + let m = event_date_start.getUTCMonth(); + let d = event_date_start.getUTCDate(); + event_date_start = new Date(y, m, d); + + let event_date_end = new Date(event.event_date_end) + let end_y = event_date_end.getFullYear(); + let end_m = event_date_end.getUTCMonth(); + let end_d = event_date_end.getUTCDate(); + event_date_end = new Date(end_y, end_m, end_d); + + document.getElementById('event_date_edit_start').value = event_date_start.toISOString().split('T')[0] + document.getElementById('event_date_edit_end').value = event_date_end.toISOString().split('T')[0] + document.getElementById('event_type_edit').value = event.event_type + document.getElementById('recipe_label_modal_edit').value = event.recipe_uuid + document.getElementById('event_description_edit').value = event.event_description + document.getElementById('event_name_edit').value = event.event_shortname + + if(event.event_type==="recipe"){ + document.getElementById('event_name_edit').classList.add('uk-disabled') + document.getElementById('event_name_edit').classList.add('uk-form-blank') + document.getElementById('recipe_label_edit_parent').hidden = false + } else { + document.getElementById('event_name_edit').classList.remove('uk-disabled') + document.getElementById('event_name_edit').classList.remove('uk-form-blank') + document.getElementById('recipe_label_edit_parent').hidden = true + } + + UIkit.modal(document.getElementById('eventEditModal')).show(); +} + +async function postNewEvent(){ + let event_shortname = document.getElementById('event_name').value + let event_description = document.getElementById('event_description').value + let event_date_start = document.getElementById('event_date_start').value + let event_date_end = document.getElementById('event_date_end').value + let event_type = document.getElementById('event_type').value + + let recipe_uuid = null + if (event_type === "recipe"){ + recipe_uuid = document.getElementById('selected-recipe').value + } + + const response = await fetch('/planner/api/addEvent', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + event_shortname: event_shortname, + event_description: event_description, + event_date_start: event_date_start, + event_date_end: event_date_end, + recipe_uuid: recipe_uuid, + event_type: event_type + }) + }); + + data = await response.json(); + response_status = 'primary' + if (!data.status === 201){ + response_status = 'danger' + } + + UIkit.notification({ + message: data.message, + status: response_status, + pos: 'top-right', + timeout: 5000 + }); + + await setupCalendarAndEvents() + UIkit.modal(document.getElementById('eventModal')).hide(); + +} + +async function postEditEvent(){ + let event_uuid = document.getElementById('event_uuid_edit').value + + let event_shortname = document.getElementById('event_name_edit').value + let event_description = document.getElementById('event_description_edit').value + let event_date_start = document.getElementById('event_date_edit_start').value + let event_date_end = document.getElementById('event_date_edit_end').value + let event_type = document.getElementById('event_type_edit').value + + const response = await fetch('/planner/api/saveEvent', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + event_uuid: event_uuid, + update: { + event_shortname: event_shortname, + event_description: event_description, + event_date_start: event_date_start, + event_date_end: event_date_end, + event_type: event_type + } + }) + }); + + data = await response.json(); + response_status = 'primary' + if (!data.status === 201){ + response_status = 'danger' + } + + UIkit.notification({ + message: data.message, + status: response_status, + pos: 'top-right', + timeout: 5000 + }); + + await setupCalendarAndEvents() + UIkit.modal(document.getElementById('eventEditModal')).hide(); + +} + +async function postRemoveEvent(event_uuid){ + + const response = await fetch('/planner/api/removeEvent', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + event_uuid: event_uuid + }) + }); + + data = await response.json(); + response_status = 'primary' + if (!data.status === 201){ + response_status = 'danger' + } + + UIkit.notification({ + message: data.message, + status: response_status, + pos: 'top-right', + timeout: 5000 + }); + + await setupCalendarAndEvents() +} + + +// main window functions +async function backOneMonth() { + if(month === 1){ + year = year - 1 + month = 12 + } else { + month = month - 1 + } + await setupCalendarAndEvents() +} + +async function forwardOneMonth() { + if(month === 12){ + year = year + 1 + month = 1 + } else { + month = month + 1 + } + await setupCalendarAndEvents() +} + + +// Main Modal Functions +var eventModal_type = "recipe" + +async function setEventTypeForm(){ + let event_type = document.getElementById('event_type').value + document.getElementById('event_name').value = "" + document.getElementById('event_description').value = "" + document.getElementById('selected-recipe').value = "" + if(event_type === "custom"){ + eventModal_type = "custom" + document.getElementById('recipe_button_modal').hidden = true + document.getElementById('recipe_label_modal').hidden = true + document.getElementById('event_name').classList.remove('uk-disabled') + + } else if (event_type === "recipe"){ + eventModal_type = "recipe" + document.getElementById('recipe_button_modal').hidden = false + document.getElementById('recipe_label_modal').hidden = false + document.getElementById('event_name').classList.add('uk-disabled') + } +} + +// Select Row Modal Handlers +var eventModal_page = 1 +var eventModal_end = 1 +var eventModal_search = "" +var eventModal_limit = 50 + + +async function selectRecipeEvent() { + document.getElementById('mainEventBody').hidden = true + document.getElementById('paginationModalBody').hidden = false + document.getElementById('eventsModalFooter').hidden = true + let recipes = await fetchRecipes() + await updateEventsPaginationElement() + await updateEventsTableWithRecipes(recipes) +} + +async function fetchRecipes() { + const url = new URL('/planner/api/getRecipes', window.location.origin); + url.searchParams.append('page', eventModal_page); + url.searchParams.append('limit', eventModal_limit); + url.searchParams.append('search_string', eventModal_search); + const response = await fetch(url); + data = await response.json(); + eventModal_end = data.end + return data.recipes; +} + + +async function updateEventsTableWithRecipes(recipes) { + let eventsTableBody = document.getElementById('eventsTableBody') + eventsTableBody.innerHTML = "" + + + for (let i = 0; i < recipes.length; i++){ + let tableRow = document.createElement('tr') + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${recipes[i].name}` + + + let opCell = document.createElement('td') + + let selectButton = document.createElement('button') + selectButton.setAttribute('class', 'uk-button uk-button-primary uk-button-small') + selectButton.innerHTML = "Select" + selectButton.onclick = async function() { + document.getElementById('selected-recipe').value = recipes[i].recipe_uuid + document.getElementById('event_name').value = recipes[i].name + document.getElementById('mainEventBody').hidden = false + document.getElementById('paginationModalBody').hidden = true + document.getElementById('eventsModalFooter').hidden = false + } + + opCell.append(selectButton) + + tableRow.append(nameCell, opCell) + eventsTableBody.append(tableRow) + } +} + +async function setEventModalPage(pageNumber){ + eventModal_page = pageNumber; + if (eventModal_type == "recipe"){ + let records = await fetchRecipes() + } + await updateItemsModalTable(records) + await updateItemsPaginationElement() +} + +async function updateEventsPaginationElement() { + let paginationElement = document.getElementById('eventPage'); + paginationElement.innerHTML = ""; + // previous + let previousElement = document.createElement('li') + if(eventModal_page<=1){ + previousElement.innerHTML = ``; + previousElement.classList.add('uk-disabled'); + }else { + previousElement.innerHTML = ``; + } + paginationElement.append(previousElement) + + //first + let firstElement = document.createElement('li') + if(eventModal_page<=1){ + firstElement.innerHTML = `1`; + firstElement.classList.add('uk-disabled'); + }else { + firstElement.innerHTML = `1`; + } + paginationElement.append(firstElement) + + // ... + if(eventModal_page-2>1){ + let firstDotElement = document.createElement('li') + firstDotElement.classList.add('uk-disabled') + firstDotElement.innerHTML = ``; + paginationElement.append(firstDotElement) + } + // last + if(eventModal_page-2>0){ + let lastElement = document.createElement('li') + lastElement.innerHTML = `${eventModal_page-1}` + paginationElement.append(lastElement) + } + // current + if(eventModal_page!=1 && eventModal_page != eventModal_end){ + let currentElement = document.createElement('li') + currentElement.innerHTML = `
            • ${eventModal_page}
            • ` + paginationElement.append(currentElement) + } + // next + if(eventModal_page+2${eventModal_page+1}` + paginationElement.append(nextElement) + } + // ... + if(eventModal_page+2<=eventModal_end){ + let secondDotElement = document.createElement('li') + secondDotElement.classList.add('uk-disabled') + secondDotElement.innerHTML = ``; + paginationElement.append(secondDotElement) + } + //end + let endElement = document.createElement('li') + if(eventModal_page>=eventModal_end){ + endElement.innerHTML = `${eventModal_end}`; + endElement.classList.add('uk-disabled'); + }else { + endElement.innerHTML = `${eventModal_end}`; + } + paginationElement.append(endElement) + //next button + let nextElement = document.createElement('li') + if(eventModal_page>=eventModal_end){ + nextElement.innerHTML = ``; + nextElement.classList.add('uk-disabled'); + }else { + nextElement.innerHTML = ``; + console.log(nextElement.innerHTML) + } + paginationElement.append(nextElement) +} \ No newline at end of file diff --git a/application/meal_planner/templates/meal_planner.html b/application/meal_planner/templates/meal_planner.html new file mode 100644 index 0000000..a63d943 --- /dev/null +++ b/application/meal_planner/templates/meal_planner.html @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + {% if session['user']['flags']['darkmode'] %} + + {% endif %} + + + + + {% if session['user']['flags']['darkmode'] %} + + {% else %} + + {% endif %} + +
              +
              +
              +
              + +
              +
              +

              +
              +
              + +
              +
              +
              +
              +
              +
              +
              + +
              + +
              +
              + +
              +

              Event Form

              +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              +
              + +
              +
              + +
              +

              Event Form

              +
              + +
              +
              +
              +

              Adding an event requires a type of event and the following information.

              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              + +
              +
              +
              + + +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              + +
              +
              + + {% assets "js_all" %} + + {% endassets %} + + \ No newline at end of file diff --git a/application/poe/templates/receipts.html b/application/poe/templates/receipts.html index 411777c..27307c6 100644 --- a/application/poe/templates/receipts.html +++ b/application/poe/templates/receipts.html @@ -57,6 +57,7 @@ Apps
                +
              • Planner
              • Recipes
              • Shopping Lists
              • Logistics
              • diff --git a/application/poe/templates/scanner.html b/application/poe/templates/scanner.html index 89234fc..d40e67e 100644 --- a/application/poe/templates/scanner.html +++ b/application/poe/templates/scanner.html @@ -40,6 +40,7 @@ Apps
                  +
                • Planner
                • Recipes
                • Shopping Lists
                • Logistics
                • diff --git a/application/receipts/templates/receipt.html b/application/receipts/templates/receipt.html index 9ab588c..2607854 100644 --- a/application/receipts/templates/receipt.html +++ b/application/receipts/templates/receipt.html @@ -40,6 +40,7 @@ Apps
                    +
                  • Planner
                  • Recipes
                  • Shopping Lists
                  • Logistics
                  • diff --git a/application/receipts/templates/receipts_index.html b/application/receipts/templates/receipts_index.html index 635fd74..6f4cc69 100644 --- a/application/receipts/templates/receipts_index.html +++ b/application/receipts/templates/receipts_index.html @@ -38,6 +38,7 @@ Apps
                      +
                    • Planner
                    • Recipes
                    • Shopping Lists
                    • Logistics
                    • diff --git a/application/recipes/templates/recipe_edit.html b/application/recipes/templates/recipe_edit.html index 27d19a7..ae277d2 100644 --- a/application/recipes/templates/recipe_edit.html +++ b/application/recipes/templates/recipe_edit.html @@ -38,6 +38,7 @@ Apps
                        +
                      • Planner
                      • Recipes
                      • Shopping Lists
                      • Logistics
                      • diff --git a/application/recipes/templates/recipe_view.html b/application/recipes/templates/recipe_view.html index 50048f4..30587aa 100644 --- a/application/recipes/templates/recipe_view.html +++ b/application/recipes/templates/recipe_view.html @@ -45,6 +45,7 @@ Apps
                          +
                        • Planner
                        • Recipes
                        • Shopping Lists
                        • Logistics
                        • diff --git a/application/recipes/templates/recipes_index.html b/application/recipes/templates/recipes_index.html index 841695c..fa86500 100644 --- a/application/recipes/templates/recipes_index.html +++ b/application/recipes/templates/recipes_index.html @@ -38,6 +38,7 @@ Apps
                            +
                          • Planner
                          • Recipes
                          • Shopping Lists
                          • Logistics
                          • diff --git a/application/shoppinglists/templates/edit.html b/application/shoppinglists/templates/edit.html index ce0cede..06ae517 100644 --- a/application/shoppinglists/templates/edit.html +++ b/application/shoppinglists/templates/edit.html @@ -39,6 +39,7 @@ Apps
                              +
                            • Planner
                            • Recipes
                            • Shopping Lists
                            • Logistics
                            • diff --git a/application/shoppinglists/templates/lists.html b/application/shoppinglists/templates/lists.html index 0f66219..bfa2b0f 100644 --- a/application/shoppinglists/templates/lists.html +++ b/application/shoppinglists/templates/lists.html @@ -39,6 +39,7 @@ Apps
                                +
                              • Planner
                              • Recipes
                              • Shopping Lists
                              • Logistics
                              • diff --git a/application/shoppinglists/templates/view.html b/application/shoppinglists/templates/view.html index b610437..00422ea 100644 --- a/application/shoppinglists/templates/view.html +++ b/application/shoppinglists/templates/view.html @@ -39,6 +39,7 @@ Apps
                                  +
                                • Planner
                                • Recipes
                                • Shopping Lists
                                • Logistics
                                • diff --git a/logs/database.log b/logs/database.log index 5f2d138..456f65e 100644 --- a/logs/database.log +++ b/logs/database.log @@ -145,4 +145,95 @@ sql='SELECT conversions.conv_factor FROM test_conversions conversions WHERE part_id = %s, uom_id = %s;') 2025-08-10 10:18:03.658146 --- ERROR --- DatabaseError(message='column "part_id" does not existLINE 1: ...nv_factor FROM test_conversions conversions WHERE part_id = ... ^', payload=(2016, 6), - sql='SELECT conversions.conv_factor FROM test_conversions conversions WHERE part_id = %s AND uom_id = %s;') \ No newline at end of file + sql='SELECT conversions.conv_factor FROM test_conversions conversions WHERE part_id = %s AND uom_id = %s;') +2025-08-10 11:11:24.348070 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "test_logistics_info_barcode_key"DETAIL: Key (barcode)=() already exists.', + payload=('', 1, 1, 1, 1), + sql='INSERT INTO test_logistics_info(barcode, primary_location, primary_zone, auto_issue_location, auto_issue_zone) VALUES (%s, %s, %s, %s, %s) RETURNING *;') +2025-08-10 19:30:16.195296 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "test_logistics_info_barcode_key"DETAIL: Key (barcode)=(%PLU0%) already exists.', + payload=('%PLU0%', 1, 1, 1, 1), + sql='INSERT INTO test_logistics_info(barcode, primary_location, primary_zone, auto_issue_location, auto_issue_zone) VALUES (%s, %s, %s, %s, %s) RETURNING *;') +2025-08-10 19:30:27.706841 --- ERROR --- DatabaseError(message='syntax error at or near "gen_random_uuid"LINE 3: event_uuid UUID gen_random_uuid(), ^', + payload=CREATE TABLE IF NOT EXISTS test_plan_events( + id SERIAL PRIMARY KEY, + event_uuid UUID gen_random_uuid(), + plan_uuid UUID, + event_shortname VARCHAR(32) NOT NULL, + event_description TEXT, + event_date TIMESTAMP NOT NULL, + created_by INTEGER NOT NULL, + UNIQUE(event_uuid) +) +, + sql='plan_events') +2025-08-11 17:29:32.899829 --- ERROR --- DatabaseError(message='invalid input syntax for type uuid: ""LINE 3: VALUES ('', 'Mongolian Beef Noodles', 'test', '2025-08-04T00... ^', + payload=('', 'Mongolian Beef Noodles', 'test', datetime.datetime(2025, 8, 4, 0, 0), 1, '1cc556f6-48b6-459f-8a3e-072c6c69ce3a', 'recipe'), + sql='INSERT INTO test_plan_events(plan_uuid, event_shortname, event_description, event_date, created_by, recipe_uuid, event_type) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING *;') +2025-08-11 18:10:54.890311 --- ERROR --- DatabaseError(message='invalid input syntax for type uuid: ""LINE 3: ... WORK YAY!', '2025-08-07T00:00:00'::timestamp, 1, '', 'custo... ^', + payload=(None, 'No Work', 'WE DONT HAVE WORK YAY!', datetime.datetime(2025, 8, 7, 0, 0), 1, '', 'custom'), + sql='INSERT INTO test_plan_events(plan_uuid, event_shortname, event_description, event_date, created_by, recipe_uuid, event_type) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING *;') +2025-08-11 18:47:53.328502 --- ERROR --- DatabaseError(message='relation "main_plan_events" does not existLINE 1: SELECT * FROM main_plan_events ^', + payload=('2025', '8'), + sql='SELECT * FROM main_plan_eventsWHERE EXTRACT(YEAR FROM event_date) = %s AND EXTRACT(MONTH FROM event_date) = %s;') +2025-08-11 18:47:56.885307 --- ERROR --- DatabaseError(message='relation "main_plan_events" does not existLINE 1: SELECT * FROM main_plan_events ^', + payload=('2025', '8'), + sql='SELECT * FROM main_plan_eventsWHERE EXTRACT(YEAR FROM event_date) = %s AND EXTRACT(MONTH FROM event_date) = %s;') +2025-08-11 18:48:24.644410 --- ERROR --- DatabaseError(message='relation "main_plan_events" does not existLINE 1: SELECT * FROM main_plan_events ^', + payload=('2025', '8'), + sql='SELECT * FROM main_plan_eventsWHERE EXTRACT(YEAR FROM event_date) = %s AND EXTRACT(MONTH FROM event_date) = %s;') +2025-08-11 18:52:46.905608 --- ERROR --- DatabaseError(message='relation "main_plan_events" does not existLINE 1: SELECT * FROM main_plan_events ^', + payload=('2025', '8'), + sql='SELECT * FROM main_plan_eventsWHERE EXTRACT(YEAR FROM event_date) = %s AND EXTRACT(MONTH FROM event_date) = %s;') +2025-08-11 18:53:54.266726 --- ERROR --- DatabaseError(message='relation "main_plan_events" does not existLINE 1: SELECT * FROM main_plan_events ^', + payload=('2025', '8'), + sql='SELECT * FROM main_plan_eventsWHERE EXTRACT(YEAR FROM event_date) = %s AND EXTRACT(MONTH FROM event_date) = %s;') +2025-08-12 07:40:16.852940 --- ERROR --- DatabaseError(message='relation "main_plan_events" does not existLINE 1: SELECT * FROM main_plan_events ^', + payload=('2025', '8'), + sql='SELECT * FROM main_plan_eventsWHERE EXTRACT(YEAR FROM event_date) = %s AND EXTRACT(MONTH FROM event_date) = %s;') +2025-08-12 08:43:31.049364 --- ERROR --- DatabaseError(message='invalid input syntax for type uuid: ""LINE 1: ...test 2', event_date = '2025-09-05', recipe_uuid = '', event_... ^', + payload={'uuid': 'c703059d-d03d-4046-866a-64ebfad7f12c', 'update': {'event_shortname': 'Spicy Bacon Chilli', 'event_description': 'test 2', 'event_date': '2025-09-05', 'recipe_uuid': '', 'event_type': 'recipe'}}, + sql='UPDATE test_plan_events SET event_shortname = %s, event_description = %s, event_date = %s, recipe_uuid = %s, event_type = %s WHERE event_uuid=%s RETURNING *;') +2025-08-12 09:02:56.840495 --- ERROR --- DatabaseError(message='column "event_date" does not existLINE 2: WHERE EXTRACT(YEAR FROM event_date) = '2025' ^HINT: Perhaps you meant to reference the column "test_plan_events.event_type".', + payload=('2025', '8'), + sql='SELECT * FROM test_plan_eventsWHERE EXTRACT(YEAR FROM event_date) = %s AND EXTRACT(MONTH FROM event_date) = %s;') +2025-08-12 09:44:45.289711 --- ERROR --- DatabaseError(message='relation "main_plan_events" does not existLINE 1: SELECT * FROM main_plan_events ^', + payload=('2025', '8'), + sql='SELECT * FROM main_plan_eventsWHERE EXTRACT(YEAR FROM event_date_start) = %s AND EXTRACT(MONTH FROM event_date_start) = %s;') +2025-08-12 09:44:49.727766 --- ERROR --- DatabaseError(message='relation "main_plan_events" does not existLINE 1: SELECT * FROM main_plan_events ^', + payload=('2025', '8'), + sql='SELECT * FROM main_plan_eventsWHERE EXTRACT(YEAR FROM event_date_start) = %s AND EXTRACT(MONTH FROM event_date_start) = %s;') +2025-08-12 09:58:22.528782 --- ERROR --- DatabaseError(message='argument formats can't be mixed', + payload=('2025', '8'), + sql='/*SELECT * FROM test_plan_eventsWHERE EXTRACT(YEAR FROM event_date_start) = %s AND EXTRACT(MONTH FROM event_date_start) = %s; */SELECT *FROM test_plan_eventsWHERE event_date_start <= (MAKE_DATE(%(year)s, %(month)s, 1) + INTERVAL '1 month' - INTERVAL '1 day')::date AND event_date_end >= MAKE_DATE(%(year)s, %(month)s, 1)') +2025-08-12 10:02:49.084186 --- ERROR --- DatabaseError(message='tuple index out of range', + payload=('2025', '8'), + sql='/*SELECT * FROM test_plan_eventsWHERE EXTRACT(YEAR FROM event_date_start) = %s AND EXTRACT(MONTH FROM event_date_start) = %s; */SELECT *FROM test_plan_eventsWHERE event_date_start <= %s AND event_date_end >= %s') +2025-08-12 10:03:17.111210 --- ERROR --- DatabaseError(message='invalid input syntax for type timestamp: "2025"LINE 5: WHERE event_date_start <= '2025' ^', + payload=('2025', '8'), + sql='SELECT *FROM test_plan_eventsWHERE event_date_start <= %s AND event_date_end >= %s') +2025-08-12 10:04:55.071530 --- ERROR --- DatabaseError(message='operator does not exist: numeric = dateLINE 2: WHERE EXTRACT(YEAR FROM event_date_start) = '2025-08-01'::da... ^HINT: No operator matches the given name and argument types. You might need to add explicit type casts.', + payload=('2025', '8'), + sql='SELECT * FROM test_plan_eventsWHERE EXTRACT(YEAR FROM event_date_start) = %s AND EXTRACT(MONTH FROM event_date_start) = %s;') +2025-08-12 10:09:58.906650 --- ERROR --- DatabaseError(message='missing FROM-clause entry for table "arguments"LINE 4: WHERE EXTRACT(YEAR FROM event_date_start) = arguments.year ^', + payload=('2025', '8'), + sql='WITH arguments AS (SELECT %s AS year, %s AS month)SELECT * FROM test_plan_eventsWHERE EXTRACT(YEAR FROM event_date_start) = arguments.year AND EXTRACT(MONTH FROM event_date_start) = arguments.month;') +2025-08-12 10:10:33.179512 --- ERROR --- DatabaseError(message='operator does not exist: numeric = textLINE 4: WHERE EXTRACT(YEAR FROM event_date_start) = (SELECT year FRO... ^HINT: No operator matches the given name and argument types. You might need to add explicit type casts.', + payload=('2025', '8'), + sql='WITH arguments AS (SELECT %s AS year, %s AS month)SELECT * FROM test_plan_eventsWHERE EXTRACT(YEAR FROM event_date_start) = (SELECT year FROM arguments) AND EXTRACT(MONTH FROM event_date_start) = (SELECT month FROM arguments);') +2025-08-12 10:11:48.528557 --- ERROR --- DatabaseError(message='tuple index out of range', + payload=('2025', '8'), + sql='WITH arguments AS (SELECT %s AS year, %s AS month)WITH arguments AS ( SELECT %s AS year, %s AS month)SELECT *FROM test_plan_eventsWHERE event_date_end >= make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) AND event_date_start < (make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) + INTERVAL '1 month');') +2025-08-12 10:12:03.257652 --- ERROR --- DatabaseError(message='function make_date(text, text, integer) does not existLINE 7: event_date_end >= make_date((SELECT year FROM arguments), ... ^HINT: No function matches the given name and argument types. You might need to add explicit type casts.', + payload=('2025', '8'), + sql='WITH arguments AS ( SELECT %s AS year, %s AS month)SELECT *FROM test_plan_eventsWHERE event_date_end >= make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) AND event_date_start < (make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) + INTERVAL '1 month');') +2025-08-12 11:05:42.431102 --- ERROR --- DatabaseError(message='missing FROM-clause entry for table "main_brands"LINE 5: COALESCE(row_to_json(main_brands.*), "{}") as recipe ^', + payload=(2025, 8), + sql='WITH arguments AS ( SELECT %s AS year, %s AS month)SELECT events.*, COALESCE(row_to_json(main_brands.*), "{}") as recipeFROM main_plan_events eventsLEFT JOIN main_recipes recipes ON recipes.recipe_uuid = events.recipe_uuidWHERE event_date_end >= make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) AND event_date_start < (make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) + INTERVAL '1 month');') +2025-08-12 11:05:56.643860 --- ERROR --- DatabaseError(message='column "{}" does not existLINE 5: COALESCE(row_to_json(recipes.*), "{}") as recipe ^', + payload=(2025, 8), + sql='WITH arguments AS ( SELECT %s AS year, %s AS month)SELECT events.*, COALESCE(row_to_json(recipes.*), "{}") as recipeFROM main_plan_events eventsLEFT JOIN main_recipes recipes ON recipes.recipe_uuid = events.recipe_uuidWHERE event_date_end >= make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) AND event_date_start < (make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) + INTERVAL '1 month');') +2025-08-12 11:09:37.978621 --- ERROR --- DatabaseError(message='column g.rp_id does not existLINE 17: ..._to_json(g)), '{}') FROM cte_recipe_items g WHERE g.rp_id = ... ^', + payload=(2025, 8), + sql='WITH arguments AS ( SELECT %s AS year, %s AS month),sum_cte AS ( SELECT mi.item_uuid, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM main_item_locations mil JOIN main_items mi ON mil.part_id = mi.id GROUP BY mi.id ),cte_recipe_items AS ( SELECT rp_item.qty, COALESCE(sum_cte.total_sum, 0) as quantity_on_hand FROM main_recipe_items rp_item LEFT JOIN sum_cte ON sum_cte.item_uuid = rp_item.item_uuid )SELECT events.*, COALESCE(row_to_json(recipes.*), '{}') as recipe, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM cte_recipe_items g WHERE g.rp_id = recipes.id) AS rp_itemsFROM main_plan_events eventsLEFT JOIN main_recipes recipes ON recipes.recipe_uuid = events.recipe_uuidWHERE event_date_end >= make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) AND event_date_start < (make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) + INTERVAL '1 month');') +2025-08-12 11:13:30.870202 --- ERROR --- DatabaseError(message='missing FROM-clause entry for table "recipe_missing_items"LINE 25: COALESCE(recipe_missing_items.has_missing_ingredients,... ^', + payload=(2025, 8), + sql='WITH arguments AS ( SELECT %s AS year, %s AS month),sum_cte AS ( SELECT mi.item_uuid, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM main_item_locations mil JOIN main_items mi ON mil.part_id = mi.id GROUP BY mi.id ),cte_recipe_items AS ( SELECT rp_item.rp_id, rp_item.qty, COALESCE(sum_cte.total_sum, 0) as quantity_on_hand FROM main_recipe_items rp_item LEFT JOIN sum_cte ON sum_cte.item_uuid = rp_item.item_uuid ), recipe_missing_items AS ( SELECT rp_id, bool_or(qty > quantity_on_hand) AS has_missing_ingredients FROM cte_recipe_items GROUP BY rp_id)SELECT events.*, COALESCE(row_to_json(recipes.*), '{}') as recipe, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM cte_recipe_items g WHERE g.rp_id = recipes.id) AS rp_items, COALESCE(recipe_missing_items.has_missing_ingredients, FALSE) AS has_missing_ingredientsFROM main_plan_events eventsLEFT JOIN main_recipes recipes ON recipes.recipe_uuid = events.recipe_uuidWHERE event_date_end >= make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) AND event_date_start < (make_date((SELECT year FROM arguments), (SELECT month FROM arguments), 1) + INTERVAL '1 month');') \ No newline at end of file diff --git a/plu_items.csv b/plu_items.csv new file mode 100644 index 0000000..cdf31dc --- /dev/null +++ b/plu_items.csv @@ -0,0 +1,319 @@ +item_name,description +Bananas,A bunch of ripe yellow bananas +Bananas Organic,Organic ripe yellow bananas +Apples Red Delicious,Classic sweet red apples +Apples Granny Smith,Tart green apples for baking and eating +Apples Fuji,Extra sweet crisp apples +Apples Gala,Medium small mildly sweet apple +Apples Honeycrisp,Juicy crisp and sweet apples +Apples Golden Delicious,Yellow-skinned sweet apples +Apples Pink Lady,Blush pink sweet-tart apple +Oranges Navel,Easy-to-peel seedless orange +Oranges Valencia,Juicy great for orange juice +Oranges Cara Cara,Pink-fleshed navel orange +Oranges Blood,Deep red-fleshed orange +Tangerines,Small easy-to-peel orange citrus +Clementines,Seedless mandarin oranges +Mandarins,Sweet citrus similar to clementines +Grapefruit Pink,Pink fleshed juicy grapefruit +Grapefruit White,Traditional tart grapefruit +Pomelo,Large fragrant ancestor of the grapefruit +Lemons,Traditional bright yellow lemons +Limes,Standard tart green limes +Key Limes,Small round extra tangy limes +Kumquats,Tiny citrus eaten whole rind and all +Pineapple,Juicy tropical fruit with spiky skin +Mango Tommy Atkins,Firm mild mango +Mango Ataulfo,Small yellow creamy mango +Mango Kent, Juicy green-red mango +Papaya,Honey-sweet tropical fruit with seeds +Dragonfruit,Exotic cactus fruit white or red flesh +Passionfruit,Round aromatic fruit with edible seeds +Guava,Sweet fragrant tropical fruit +Starfruit,Yellow crisp star-shaped when sliced +Avocados Hass,Classic creamy pebbly-skinned avocado +Avocados Fuerte,Smooth-skinned green avocado +Peaches,Juicy fuzzy-skinned summer fruit +Nectarines,Peach-like fruit with smooth skin +Plums,Juicy tart stone fruit +Pluots,Plum-apricot hybrid +Apricots,Small orange stone fruit +Cherries Sweet,Bing and similar red sweet cherry +Cherries Tart,Montmorency and pie cherries +Grapes Red Seedless,Classic sweet eating grape +Grapes Green Seedless,Larger green sweet grape +Grapes Black Seedless,Dark colored sweet grape +Raisins Dried Grapes,Classic dried sweet grapes +Strawberries,Red fragrant berries +Blueberries,Small round blue berries +Raspberries,Delicate tart red berries +Blackberries,Darker juicy tart berries +Cranberries,Small tart red berry +Gooseberries,Tart small usually green berries +Currants,Red black or white tart berries +Melon Watermelon,Large red pink melon with seeds +Melon Watermelon Seedless,Red sweet almost no seeds +Melon Cantaloupe,Orange-fleshed webbed melon +Melon Honeydew,Green-fleshed smooth-skinned melon +Melon Galia,Fragrant light green-flesh melon +Melon Canary,Bright yellow sweet melon +Melon Crenshaw,Sweet juicy hybrid melon +Pomegranate,Ruby red seeds inside a hard fruit +Figs Brown Turkey,Soft brownish edible fig +Figs Black Mission,Intensely sweet black fig +Dates Medjool,Large soft sweet dried date +Dates Deglet Noor,Smaller sweet dried date +Kiwi,Zesty green-flesh fuzzy brown +Kiwi Gold,Smooth-skinned yellow-flesh kiwi +Persimmons Fuyu,Orange eaten firm like apple +Persimmons Hachiya,Longer extremely astringent if unripe +Lychee,Small white fragrant fruit red skin +Rambutan,Hairy red Asian lychee relative +Longan,Sweet mild translucent Asian fruit +Jackfruit,Very large tropical fruit yellow flesh +Sapote Black,Chocolate pudding fruit +Soursop,Green spiky soft white tropical fruit +Tamarind,Brown pod tangy sweet-sour pulp +Breadfruit,Starchy Polynesian staple fruit +Squash Zucchini,Standard green summer squash +Squash Yellow,Yellow-skinned variety of squash +Squash Acorn,Small ribbed sweet-fleshed winter squash +Squash Butternut,Tan bell-shaped orange-fleshed winter squash +Squash Spaghetti,Pale yellow stringy flesh +Squash Delicata,Oblong sweet edible-skin +Pumpkin, Orange winter squash often large +Sweet Corn,Golden corn on the cob +Snap Peas,Edible sweet crunchy pods +Snow Peas,Flat tender often used in stir fries +Green Beans,String beans long and crispy +Fava Beans,Large flat green beans +Lima Beans,Flattened creamy bean +Peas English/fresh,Green round peas in a pod +Okra,Fuzzy green pods used in gumbo +Eggplant Globe,Large dark purple eggplant +Eggplant Italian,Small oval eggplant +Eggplant Japanese,Slender tender purple eggplant +Bell Pepper Red, Sweet juicy pepper +Bell Pepper Green, Classic crisp pepper +Bell Pepper Yellow,Sweet mild yellow pepper +Bell Pepper Orange,Sweet mild orange +Bell Pepper Purple,Rarer mild purple variety +Hot Pepper Jalapeño,Classic green spicy pepper +Hot Pepper Serrano,Thinner spicier pepper +Hot Pepper Habanero,Very hot orange pepper +Hot Pepper Anaheim,Mild light green pepper +Hot Pepper Poblano,Dark green mild pepper +Hot Pepper Thai Chili,Small very hot red/green +Hot Pepper Fresno,Red mild-medium looks like jalapeño +Hot Pepper Scotch Bonnet,Similar heat to habanero +Tomatoes Beefsteak,Jumbo sized great for slicing +Tomatoes Roma,Plum-shaped meaty for sauces +Tomatoes Cherry,Small round sweet tomatoes +Tomatoes Grape,Oblong bite-sized tomatoes +Tomatoes Heirloom,Old multi-colored flavorful +Tomatillos,Green paper-husked tart +Potatoes Russet,Large brown-skinned baking potato +Potatoes Yukon Gold,Yellow-fleshed buttery potato +Potatoes Red,Waxier bright red skin +Potatoes Fingerling,Mini elongated gourmet potato +Potatoes Purple,Blue-purple skinned and fleshed +Sweet Potatoes Orange,Classic moist sweet potato +Sweet Potatoes White,Palest orange or yellow +Yams,Often confused with sweet potatoes +Onion Yellow,Classic brown skin all-purpose +Onion White,Sharply flavored white onion +Onion Red,Strong purple skin often raw +Onion Vidalia,Sweet tender yellow onion +Onion Shallot,Fancy small mild flavor +Green Onions,Scallions mild green-topped +Leeks,Large tender white and green stalks +Chives,Thin mild green herb +Garlic,Pungent flavored bulbs +Garlic Elephant,Giant garlic with mild flavor +Radish,Crunchy red and white root +Daikon,Large white Asian radish +Beets Red,Classic earthy root sweet when cooked +Beets Golden,Milder yellow-skinned beet +Turnip,Purple-topped white root +Rutabaga,Large sweet yellow root +Parsnip,White sweet carrot-like root +Carrots Orange,Classic orange root +Carrots Rainbow,Bunches of various colored carrots +Celery,Stalks with crisp texture +Celery Root,Celeriac knobby root with celery flavor +Bok Choy,Asian green with white stalks +Napa Cabbage,Soft-leaved Chinese cabbage +Cabbage Green,Tight-headed green cabbage +Cabbage Red,Red/purple fine-leaved cabbage +Brussels Sprouts,Little round green buds +Kale Curly,Frilly green leaves popular in salads +Kale Lacinato,Firm blue-green dinosaur kale +Mustard Greens,Spicy frilly salad green +Collard Greens,Large dark green leathery leaves +Spinach,Small mild tender leaves +Swiss Chard,Colorful leafy vegetable +Arugula,Peppery leafy fun salad green +Lettuce Iceberg,Classic crisp pale green lettuce +Lettuce Romaine,Upright green perfect for Caesar +Lettuce Green Leaf,Soft fresh green leaves +Lettuce Red Leaf,Tender burgundy-green leaves +Escarole,Bitter broad more robust salad green +Endive,Curly bitter light green +Radicchio,Small burgundy bitter lettuce-like +Basil,Fragrant green Italian herb +Cilantro (Coriander),Fresh bright citrusy herb +Dill,Feathery aromatic often with pickles +Flat-leaf Parsley,Bright clean flavored herb +Curly Parsley,Classic garnish fluffy +Oregano,Earthy bold Mediterranean seasoning +Rosemary,Piney robust herb +Sage,Gray-green leaves strong flavor +Mint,Cool bright refreshing herb +Thyme,Tiny-leaved savory fragrant herb +Sorrel,Lemony spinach-like herb green +Tarragon,Anise-flavored cooking herb +Watercress,Peppery green leafy herb +Mushrooms White Button,Classic round mushrooms +Mushrooms Cremini,Deeper brown firmer +Mushrooms Portobello,Large brown mature mushrooms +Mushrooms Shiitake,Wide-capped Japanese mushroom +Mushrooms Oyster,Soft-fleshed cluster mushrooms +Mushrooms Enoki,Tiny white-capped +Seaweed,Nori sheets or kelp +Sprouts Alfalfa,Tender crunchy tiny sprout greens +Sprouts Bean,White crisp bean sprouts +Sprouts Broccoli,Nutritious peppery sprout +Artichokes,Tender green thistly Mediterranean buds +Asparagus,Green or white tender spears +Fennel,White bulb with fronds licorice flavor +Jicama,Large round sweet crunchy root +Kohlrabi,Baseball-shaped mild crunchy veggie +Okra,Gumbo vegetable with fuzzy pods +Lotus Root,Edible crisp root with holes +Turmeric root,Small orange potent-anti-inflammatory +Ginger root,Spicy knobby aromatic root +Horseradish root,Strong white spicy root +Sunchokes,Crunchy nutty Jerusalem artichoke +Burdock root,Long brown earthy-Japanese vegetable +Edamame,Immature soybeans in the pod +Plantains,Starchy cooking banana +Yuca (Cassava),Tropical brown starchy tuber +Taro root,Purple-tinged starchy root +Beef Ribeye Steak, Well-marbled boneless steak from rib section +Beef Rib Steak,Bone-in steak from rib section +Beef Tenderloin/Filet Mignon,Lean tender boneless steak from loin +Beef Sirloin Steak,Flavorful steak from rear back portion +Beef Top Sirloin,Lean sirloin cut for grilling or roasting +Beef Flank Steak,Long flat cut good for fajitas or stir fry +Beef Skirt Steak,Long thin cut best for grilling and marinating +Beef Strip Steak (NY Strip),Boneless steak from short loin well-marbled +Beef T-Bone Steak,Iconic bone-in steak with strip and tenderloin +Beef Porterhouse Steak,Larger T-bone with bigger tenderloin section +Beef Round Steak,Lean boneless steak from back leg +Beef Chuck Roast,Well-marbled cut ideal for slow cooking +Beef Brisket,Flat fatty cut ideal for barbecue or braising +Beef Short Ribs,Rich bone-in pieces ideal for slow roasting or braising +Beef Cross Rib Roast,Flavorsome roast cut +Beef Stew Meat,Cubed beef for stews +Beef Ground Beef 80/20,Standard ground beef for burgers and tacos +Beef Ground Beef 90/10,Lean ground beef +Beef Shank,Meaty cross-cut beef leg slice for soups and osso buco +Beef Oxtail,Bony flavorful ox tail cuts for stews +Beef Tri-Tip,Triangular roast popular for grilling or roasting +Beef Rump Roast,Leaner roast from the round +Beef Eye of Round Roast,"Lean, dense, boneless roast from the round" +Beef Top Round Steak,Lean steak for marinating or broiling +Beef Back Ribs,Beef ribs from rib section +Pork Loin Roast,Bone-in or boneless loin roast +Pork Loin Chops,Lean chops from the loin +Pork Rib Chops,Rib bone-in chops from the loin +Pork Center Cut Chops,Boneless or bone-in thick cut +Pork Tenderloin,Small tender boneless roast +Pork Shoulder Roast (Boston Butt),Marbled roast for pulled pork +Pork Picnic Roast,Oval-shaped cut from lower shoulder +Pork Spare Ribs,Large flat ribs from the belly side +Pork Baby Back Ribs,Short curved ribs from the back +Pork Country Style Ribs,Meaty ribs with more meat than bone +Pork St. Louis Ribs,Trimmed spare ribs +Pork Belly,Uncured slab used for bacon +Pork Ham,Fully cooked or fresh leg portion +Pork Fresh Ham,Uncured leg portion +Pork Smoked Ham,Smoked and cured pork leg +Pork Ground Pork,Ground meat from pork +Pork Sausage,Ground pork seasoned and formed into links or patties +Pork Hocks (Ham Hocks),Meaty lower leg cut used for soups +Pork Spareribs,Tender ribs from undersurface of ribs +Pork Shoulder Steak,Flavorful marbled steak for grilling +Pork Salt Pork,Heavily salted fatty pork cut +Pork Fatback,Layer of pork fat from the back +Lamb Loin Chop,Small bone-in tender chop +Lamb Rib Chop,Rib-section bone-in cut +Lamb Leg Roast,Bone-in or boneless roast from leg +Lamb Shoulder Chop,Well-flavored less tender cut +Lamb Rack,Full rib section for roasting and chops +Lamb Shank,Meaty lower leg cut excellent for braising +Lamb Ground Lamb,Ground lamb for burgers or kebabs +Lamb Stew Meat,Cubed lamb for stews +Lamb Sirloin Chop,Boneless chop from leg/top sirloin +Lamb Neck Slices,Collagen-rich cut for stews and braises +Lamb Breast,Flatter flavorful section of rib and breast +Chicken Whole Chicken,Raw whole ready-to-cook chicken +Chicken Breast Boneless Skinless,Most popular white meat cut +Chicken Breast Bone-In,Moist white meat with rib bone +Chicken Tenders,Thin strips from under breast +Chicken Thighs Boneless Skinless,Juicy flavorful dark meat +Chicken Thighs Bone-In Juicy,thigh portion of chicken leg +Chicken Drumsticks,Lower leg portion great for frying and baking +Chicken Wings,Popular for appetizers and barbecues +Chicken Legs,Whole leg (drumstick and thigh attached) +Chicken Split Breast,Bone-in white meat cut in half +Chicken Back,Used for stock and soup +Chicken Liver,Edible poultry organ for pâté or sautéing +Chicken Gizzards,Muscular stomach popular fried or sautéed +Chicken Hearts,Small organ popular grilled or stewed +Turkey Whole Turkey,Whole ready-to-cook turkey +Turkey Breast,Large boneless or bone-in white meat cut +Turkey Thighs,Juicy dark meat portion +Turkey Drumsticks,Large flavorful portion for roasting +Turkey Wings,Meaty for roasting or stock +Turkey Ground,Ground white and dark turkey meat +Duck Whole Duck,Whole ready-to-cook duck +Duck Breast,Breast portion with rich flavor +Duck Legs,Leg quarter with dark rich flavor +Duck Wings,For roasting or confit +Duck Confit,Legs cooked slowly in duck fat +Venison Steaks,Boneless wild game steak +Venison Roast,Large cut for roasting +Venison Ground,Ground wild game meat +Venison Sausage,Wild game sausage links +Bison Steak,Lean boneless steak +Bison Ground,Ground lean bison +Rabbit Whole Rabbit,Ready-to-cook whole rabbit +Rabbit Legs,Dark meat for braising or stewing +Pheasant Whole Pheasant,Game bird for roasting +Quail Whole Quail,Small game bird for roasting/braising +Goose Whole Goose,Rich dark-meat poultry for roasting +Salmon Fillet,Boned side of salmon skin on or off +Salmon Steak,Cut end-to-end with backbone +Trout Whole,Whole raw trout +Trout Fillets,Filleted sides of trout +Cod Fillet,Boneless flaky white fish fillet +Halibut Steak,Thick cross-section cut from halibut +Tilapia Fillet,Mild boneless white fish +Snapper Fillet,Boneless fillet from snapper +Catfish Fillet,Mild boneless fillet +Swordfish Steak,Thick firm boneless steak +Mahi Mahi Fillet,Lean flavorful white fish +Tuna Steak,Firm meaty typically grilled +Scallops,Meaty adductor muscle of shellfish +Shrimp Raw,Headless shell-on or peeled raw shrimp +Shrimp Cooked,Precooked shrimp tail-on or off +Crab Whole,Live or cooked whole crab +Crab Legs,Steamed or frozen crab legs +Lobster Whole,Live lobster +Lobster Tail,Meaty tail section of lobster +Oysters,Fresh or shucked +Clams,Fresh or shucked +Mussels,Whole fresh blue or green mussels +Octopus,Fresh or cleaned octopus +Squid,Fresh or cleaned squid (calamari) \ No newline at end of file diff --git a/static/css/dark-mode.css b/static/css/dark-mode.css index 380a7a8..4ba64e5 100644 --- a/static/css/dark-mode.css +++ b/static/css/dark-mode.css @@ -2,10 +2,11 @@ --background: #121212; --background-text: #ffffff; - --surface: #121212; + --surface: #181818; --surface-text: #ffffff; --surface-two: #252525; + --surface-three: #383838; --error: #CF6679; --error-text: #000000; @@ -251,4 +252,128 @@ select, option { .uk-button.uk-button-danger { background-color: var(--error); color: var(--error-text); +} + + + +/* Darkmode settings for planner plage */ + +#calendar_container { + background-color: var(--surface); + box-shadow: var(--elevation-high); + border-radius: var(--radius); +} + +#calender_table { + background-color: var(--surface); +} + +#calender_table th{ + background-color: var(--surface); + font-weight: bold; +} + +.calendar-cell-empty { + background-color: var(--surface); + border: 1px solid var(--surface-three); +} + +.calendar-cell { + background-color: var(--surface-two); + border: 1px solid var(--surface-three); + position: relative; + width: 150px; + height: 120px; + vertical-align: top; + padding: 5px; + margin: 5px; +} + +.calendar-cell:hover{ + background-color: var(--surface-two); + border: 2px solid var(--primary-color); +} + + +.calendar-cell:hover, .calendar-cell-selected{ + background-color: var(--surface-two); + border: 2px solid var(--primary-color); +} + +.date-box { + width: 25px; + height: 25px; + font-size: 18px; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + left: 5px; + top: 5px; + z-index: 2; +} + +.recipes-box { + position: absolute; + top: 5px; + left: 35px; + right: 5px; + bottom: 5px; + padding: 3px; + overflow: auto; + z-index: 1; +} + +.recipe-label.recipe-success { + background:rgba(158, 221, 145, 0.479); + margin-bottom: 3px; + padding: 2px 5px; + border-radius: 3px; + font-size: 12px; + font-weight: bold; +} + +.recipe-label.recipe-error { + background-color: rgba(218, 143, 143, 0.425); + margin-bottom: 3px; + padding: 2px 5px; + border-radius: 3px; + font-size: 12px; + font-weight: bold; +} + +.recipe-label.recipe-success:hover{ + background-color: rgba(158, 221, 145, 0.185); + cursor: pointer; +} + +.recipe-label.recipe-success:hover, .recipe-label-selected { + background-color: rgba(158, 221, 145, 0.185); +} + +.recipe-label.recipe-error:hover{ + background-color:rgba(218, 143, 143, 0.185); + cursor: pointer; +} + +.recipe-label.recipe-error:hover, .recipe-label-selected { + background-color: rgba(218, 143, 143, 0.185); +} + +.custom-label { + background:rgba(211, 211, 211, 0.459); + margin-bottom: 3px; + padding: 2px 5px; + border-radius: 1px; + font-size: 12px; + font-weight: bold; +} + +.custom-label:hover{ + background-color: rgba(211, 211, 211, 0.185); + cursor: pointer; +} + +.custom-label:hover, .custom-label-selected { + background-color: rgba(211, 211, 211, 0.185); } \ No newline at end of file diff --git a/webserver.py b/webserver.py index 4663bbc..dd7d6b1 100644 --- a/webserver.py +++ b/webserver.py @@ -12,6 +12,7 @@ from application.items import items_API from application.poe import poe_api from application.shoppinglists import shoplist_api from application.receipts import receipts_api +from application.meal_planner import meal_planner_api from flasgger import Swagger from outh import oauth @@ -43,6 +44,7 @@ app.register_blueprint(site_management_api.site_management_api, url_prefix="/sit app.register_blueprint(receipts_api.receipt_api, url_prefix='/receipts') app.register_blueprint(shoplist_api.shopping_list_api, url_prefix="/shopping-lists") app.register_blueprint(recipes_api.recipes_api, url_prefix='/recipes') +app.register_blueprint(meal_planner_api.meal_planner_api, url_prefix='/planner') js = Bundle('js/uikit.min.js', 'js/uikit-icons.min.js', output='gen/main.js') assets.register('js_all', js)