Implemented Planner System to its basic functions
This commit is contained in:
parent
94f7bad2e6
commit
40b0a5f527
Binary file not shown.
@ -1,5 +1,6 @@
|
|||||||
CREATE TABLE IF NOT EXISTS %%site_name%%_food_info (
|
CREATE TABLE IF NOT EXISTS %%site_name%%_food_info (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
|
food_info_uuid UUID gen_random_uuid(),
|
||||||
food_groups TEXT [],
|
food_groups TEXT [],
|
||||||
ingrediants TEXT [],
|
ingrediants TEXT [],
|
||||||
nutrients JSONB,
|
nutrients JSONB,
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
CREATE TABLE IF NOT EXISTS %%site_name%%_items(
|
CREATE TABLE IF NOT EXISTS %%site_name%%_items(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
item_uuid UUID DEFAULT gen_random_uuid(),
|
item_uuid UUID DEFAULT gen_random_uuid(),
|
||||||
barcode VARCHAR(255) NOT NULL,
|
barcode VARCHAR(255),
|
||||||
item_name VARCHAR(255) NOT NULL,
|
item_name VARCHAR(255) NOT NULL,
|
||||||
brand INTEGER,
|
brand INTEGER,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
tags TEXT [],
|
tags TEXT [],
|
||||||
links JSONB,
|
links JSONB,
|
||||||
item_info_id INTEGER NOT NULL,
|
item_info_id INTEGER NOT NULL,
|
||||||
|
item_info_uuid UUID NOT NULL,
|
||||||
logistics_info_id INTEGER NOT NULL,
|
logistics_info_id INTEGER NOT NULL,
|
||||||
|
logistics_info_uuid UUID NOT NULL,
|
||||||
food_info_id INTEGER,
|
food_info_id INTEGER,
|
||||||
|
food_info_uuid UUID NOT NULL,
|
||||||
row_type VARCHAR(255) NOT NULL,
|
row_type VARCHAR(255) NOT NULL,
|
||||||
item_type VARCHAR(255) NOT NULL,
|
item_type VARCHAR(255) NOT NULL,
|
||||||
search_string TEXT NOT NULL,
|
search_string TEXT NOT NULL,
|
||||||
UNIQUE(item_uuid, barcode, item_info_id),
|
UNIQUE(item_uuid),
|
||||||
CONSTRAINT fk_item_info
|
CONSTRAINT fk_item_info
|
||||||
FOREIGN KEY(item_info_id)
|
FOREIGN KEY(item_info_id)
|
||||||
REFERENCES %%site_name%%_item_info(id)
|
REFERENCES %%site_name%%_item_info(id)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
CREATE TABLE IF NOt EXISTS %%site_name%%_item_info (
|
CREATE TABLE IF NOt EXISTS %%site_name%%_item_info (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
barcode VARCHAR(255) NOT NULL,
|
item_info_uuid UUID gen_random_uuid(),
|
||||||
|
barcode VARCHAR(255),
|
||||||
packaging VARCHAR(255),
|
packaging VARCHAR(255),
|
||||||
uom_quantity FLOAT8,
|
uom_quantity FLOAT8,
|
||||||
uom INTEGER,
|
uom INTEGER,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
CREATE TABLE IF NOT EXISTS %%site_name%%_logistics_info(
|
CREATE TABLE IF NOT EXISTS %%site_name%%_logistics_info(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
barcode VARCHAR(255) NOT NULL,
|
logistics_info_uuid UUID gen_random_uuid(),
|
||||||
|
barcode VARCHAR(255),
|
||||||
primary_location INTEGER NOT NULL,
|
primary_location INTEGER NOT NULL,
|
||||||
primary_zone INTEGER NOT NULL,
|
primary_zone INTEGER NOT NULL,
|
||||||
auto_issue_location INTEGER NOT NULL,
|
auto_issue_location INTEGER NOT NULL,
|
||||||
|
|||||||
13
application/administration/sql/CREATE/plan_events.sql
Normal file
13
application/administration/sql/CREATE/plan_events.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS %%site_name%%_plan_events(
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
event_uuid UUID DEFAULT gen_random_uuid(),
|
||||||
|
plan_uuid UUID,
|
||||||
|
recipe_uuid UUID,
|
||||||
|
event_shortname VARCHAR(32) NOT NULL,
|
||||||
|
event_description TEXT,
|
||||||
|
event_date_start TIMESTAMP NOT NULL,
|
||||||
|
event_date_end TIMESTAMP NOT NULL,
|
||||||
|
created_by INTEGER NOT NULL,
|
||||||
|
event_type VARCHAR(32) NOT NULL,
|
||||||
|
UNIQUE(event_uuid)
|
||||||
|
)
|
||||||
@ -1,9 +1,11 @@
|
|||||||
CREATE TABLE IF NOT EXISTS %%site_name%%_recipes (
|
CREATE TABLE IF NOT EXISTS %%site_name%%_recipes (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
|
recipe_uuid UUID DEFAULT gen_random_uuid() NOT NULL,
|
||||||
name VARCHAR,
|
name VARCHAR,
|
||||||
author INTEGER,
|
author INTEGER,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
creation_date TIMESTAMP,
|
creation_date TIMESTAMP,
|
||||||
instructions TEXT [],
|
instructions TEXT [],
|
||||||
picture_path TEXT
|
picture_path TEXT,
|
||||||
|
UNIQUE(recipe_uuid)
|
||||||
);
|
);
|
||||||
@ -32,6 +32,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -564,6 +564,30 @@ class BrandsPayload:
|
|||||||
self.name,
|
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
|
@dataclass
|
||||||
class SiteManager:
|
class SiteManager:
|
||||||
site_name: str
|
site_name: str
|
||||||
|
|||||||
@ -43,6 +43,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -55,6 +55,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -37,6 +37,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -41,6 +41,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
0
application/meal_planner/__init__.py
Normal file
0
application/meal_planner/__init__.py
Normal file
BIN
application/meal_planner/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
application/meal_planner/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
112
application/meal_planner/meal_planner_api.py
Normal file
112
application/meal_planner/meal_planner_api.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# 3RD PARTY IMPORTS
|
||||||
|
from flask import (
|
||||||
|
Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response
|
||||||
|
)
|
||||||
|
import psycopg2
|
||||||
|
import math
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
# APPLICATION IMPORTS
|
||||||
|
from config import config
|
||||||
|
from application.access_module import access_api
|
||||||
|
from application import postsqldb, database_payloads
|
||||||
|
from application.meal_planner import meal_planner_database
|
||||||
|
|
||||||
|
meal_planner_api = Blueprint('meal_planner_api', __name__, template_folder="templates", static_folder="static")
|
||||||
|
|
||||||
|
|
||||||
|
@meal_planner_api.route('/', methods=["GET"])
|
||||||
|
@access_api.login_required
|
||||||
|
def plannerIndex():
|
||||||
|
sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])]
|
||||||
|
return render_template('meal_planner.html', current_site=session['selected_site'], sites=sites)
|
||||||
|
|
||||||
|
@meal_planner_api.route('/api/getEventsByMonth', methods=["GET"])
|
||||||
|
@access_api.login_required
|
||||||
|
def getEventsByMonth():
|
||||||
|
if request.method == "GET":
|
||||||
|
site_name = session['selected_site']
|
||||||
|
year = int(request.args.get('year', 2025))
|
||||||
|
month = int(request.args.get('month', 1))
|
||||||
|
events = ()
|
||||||
|
events = meal_planner_database.selectPlanEventsByMonth(site_name, (year, month))
|
||||||
|
return jsonify(status=201, message="Events fetched Successfully!", events=events)
|
||||||
|
return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!", events=events)
|
||||||
|
|
||||||
|
@meal_planner_api.route('/api/getEventByUUID', methods=["GET"])
|
||||||
|
@access_api.login_required
|
||||||
|
def getEventByUUID():
|
||||||
|
if request.method == "GET":
|
||||||
|
site_name = session['selected_site']
|
||||||
|
event_uuid = request.args.get('event_uuid', "")
|
||||||
|
event = ()
|
||||||
|
event = meal_planner_database.selectPlanEventByUUID(site_name, (event_uuid,))
|
||||||
|
|
||||||
|
return jsonify(status=201, message="Event fetched Successfully!", event=event)
|
||||||
|
return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!", event=event)
|
||||||
|
|
||||||
|
|
||||||
|
@meal_planner_api.route('/api/getRecipes', methods=["GET"])
|
||||||
|
@access_api.login_required
|
||||||
|
def getRecipes():
|
||||||
|
if request.method == "GET":
|
||||||
|
site_name = session['selected_site']
|
||||||
|
page = int(request.args.get('page', 1))
|
||||||
|
limit = int(request.args.get('limit', 50))
|
||||||
|
search_string = request.args.get('search_string', "")
|
||||||
|
|
||||||
|
offset = (page - 1) * limit
|
||||||
|
recipes, count = [], 0
|
||||||
|
recipes, count = meal_planner_database.paginateRecipesTuples(site_name, (limit, offset))
|
||||||
|
|
||||||
|
return jsonify(status=201, message="Recipes fetched Successfully!", recipes=recipes, end=math.ceil(count/limit))
|
||||||
|
return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!", recipes=recipes, end=math.ceil(count/limit))
|
||||||
|
|
||||||
|
@meal_planner_api.route('/api/addEvent', methods=["POST"])
|
||||||
|
@access_api.login_required
|
||||||
|
def addEvent():
|
||||||
|
if request.method == "POST":
|
||||||
|
site_name = session['selected_site']
|
||||||
|
event_date_start = datetime.datetime.strptime(request.get_json()['event_date_start'], "%Y-%m-%d")
|
||||||
|
event_date_end = datetime.datetime.strptime(request.get_json()['event_date_end'], "%Y-%m-%d")
|
||||||
|
|
||||||
|
event_payload = database_payloads.PlanEventPayload(
|
||||||
|
plan_uuid=None,
|
||||||
|
event_shortname=request.get_json()['event_shortname'],
|
||||||
|
event_description=request.get_json()['event_description'],
|
||||||
|
event_date_start=event_date_start,
|
||||||
|
event_date_end=event_date_end,
|
||||||
|
created_by=session['user_id'],
|
||||||
|
recipe_uuid=request.get_json()['recipe_uuid'],
|
||||||
|
event_type=request.get_json()['event_type']
|
||||||
|
)
|
||||||
|
|
||||||
|
meal_planner_database.insertPlanEventTuple(site_name, event_payload.payload())
|
||||||
|
|
||||||
|
return jsonify(status=201, message="Event added Successfully!")
|
||||||
|
return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!")
|
||||||
|
|
||||||
|
@meal_planner_api.route('/api/saveEvent', methods=["POST"])
|
||||||
|
@access_api.login_required
|
||||||
|
def saveEvent():
|
||||||
|
if request.method == "POST":
|
||||||
|
site_name = session['selected_site']
|
||||||
|
event_uuid = request.get_json()['event_uuid']
|
||||||
|
update = request.get_json()['update']
|
||||||
|
|
||||||
|
meal_planner_database.updatePlanEventTuple(site_name, {'uuid': event_uuid, "update": update})
|
||||||
|
|
||||||
|
return jsonify(status=201, message="Event Saved Successfully!")
|
||||||
|
return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!")
|
||||||
|
|
||||||
|
@meal_planner_api.route('/api/removeEvent', methods=["POST"])
|
||||||
|
@access_api.login_required
|
||||||
|
def removeEvent():
|
||||||
|
if request.method == "POST":
|
||||||
|
site_name = session['selected_site']
|
||||||
|
event_uuid = request.get_json()['event_uuid']
|
||||||
|
|
||||||
|
meal_planner_database.deletePlanEventTuple(site_name, (event_uuid, ))
|
||||||
|
|
||||||
|
return jsonify(status=201, message="Event removed Successfully!")
|
||||||
|
return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!")
|
||||||
187
application/meal_planner/meal_planner_database.py
Normal file
187
application/meal_planner/meal_planner_database.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import psycopg2
|
||||||
|
|
||||||
|
from application import postsqldb
|
||||||
|
import config
|
||||||
|
|
||||||
|
def paginateRecipesTuples(site: str, payload: tuple, convert=True, conn=None):
|
||||||
|
self_conn = False
|
||||||
|
recipes = ()
|
||||||
|
count = 0
|
||||||
|
sql = f"SELECT * FROM {site}_recipes ORDER BY name ASC LIMIT %s OFFSET %s;"
|
||||||
|
sql_count = f"SELECT COUNT(*) FROM {site}_recipes;"
|
||||||
|
try:
|
||||||
|
if not conn:
|
||||||
|
database_config = config.config()
|
||||||
|
conn = psycopg2.connect(**database_config)
|
||||||
|
conn.autocommit = True
|
||||||
|
self_conn = True
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(sql, payload)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
if rows and convert:
|
||||||
|
recipes = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows]
|
||||||
|
if rows and not convert:
|
||||||
|
recipes = rows
|
||||||
|
|
||||||
|
cur.execute(sql_count)
|
||||||
|
count = cur.fetchone()[0]
|
||||||
|
|
||||||
|
if self_conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return recipes, count
|
||||||
|
except Exception as error:
|
||||||
|
raise postsqldb.DatabaseError(error, payload, sql)
|
||||||
|
|
||||||
|
def selectPlanEventsByMonth(site: str, payload: tuple, convert=True, conn=None):
|
||||||
|
"""payload=(year, month)"""
|
||||||
|
self_conn = False
|
||||||
|
event_tuples = ()
|
||||||
|
|
||||||
|
|
||||||
|
with open('application/meal_planner/sql/selectPlanEventsByMonth.sql', 'r+') as file:
|
||||||
|
sql = file.read().replace("%%site_name%%", site)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not conn:
|
||||||
|
database_config = config.config()
|
||||||
|
conn = psycopg2.connect(**database_config)
|
||||||
|
conn.autocommit = True
|
||||||
|
self_conn = True
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(sql, payload)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
if rows and convert:
|
||||||
|
event_tuples = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows]
|
||||||
|
if rows and not convert:
|
||||||
|
event_tuples = rows
|
||||||
|
|
||||||
|
|
||||||
|
if self_conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return event_tuples
|
||||||
|
except Exception as error:
|
||||||
|
raise postsqldb.DatabaseError(error, payload, sql)
|
||||||
|
|
||||||
|
def selectPlanEventByUUID(site: str, payload: tuple, convert=True, conn=None):
|
||||||
|
"""payload=(event_uuid,)"""
|
||||||
|
self_conn = False
|
||||||
|
event_tuple = ()
|
||||||
|
|
||||||
|
sql = f"SELECT * FROM {site}_plan_events WHERE event_uuid = %s;"
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not conn:
|
||||||
|
database_config = config.config()
|
||||||
|
conn = psycopg2.connect(**database_config)
|
||||||
|
conn.autocommit = True
|
||||||
|
self_conn = True
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(sql, payload)
|
||||||
|
rows = cur.fetchone()
|
||||||
|
if rows and convert:
|
||||||
|
event_tuple = postsqldb.tupleDictionaryFactory(cur.description, rows)
|
||||||
|
if rows and not convert:
|
||||||
|
event_tuple = rows
|
||||||
|
|
||||||
|
|
||||||
|
if self_conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return event_tuple
|
||||||
|
except Exception as error:
|
||||||
|
raise postsqldb.DatabaseError(error, payload, sql)
|
||||||
|
|
||||||
|
def insertPlanEventTuple(site: str, payload: tuple, convert=True, conn=None):
|
||||||
|
self_conn = False
|
||||||
|
event_tuple = ()
|
||||||
|
|
||||||
|
with open('application/meal_planner/sql/insertPlanEvent.sql', 'r+') as file:
|
||||||
|
sql = file.read().replace("%%site_name%%", site)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not conn:
|
||||||
|
database_config = config.config()
|
||||||
|
conn = psycopg2.connect(**database_config)
|
||||||
|
conn.autocommit = True
|
||||||
|
self_conn = True
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(sql, payload)
|
||||||
|
rows = cur.fetchone()
|
||||||
|
if rows and convert:
|
||||||
|
event_tuple = postsqldb.tupleDictionaryFactory(cur.description, rows)
|
||||||
|
if rows and not convert:
|
||||||
|
event_tuple = rows
|
||||||
|
|
||||||
|
|
||||||
|
if self_conn:
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return event_tuple
|
||||||
|
except Exception as error:
|
||||||
|
raise postsqldb.DatabaseError(error, payload, sql)
|
||||||
|
|
||||||
|
def updatePlanEventTuple(site:str, payload: dict, convert=True, conn=None):
|
||||||
|
""" payload (dict): {'barcode': row_id, 'update': {... column_to_update: value_to_update_to...}} """
|
||||||
|
updated = ()
|
||||||
|
self_conn = False
|
||||||
|
set_clause, values = postsqldb.updateStringFactory(payload['update'])
|
||||||
|
values.append(payload['uuid'])
|
||||||
|
sql = f"UPDATE {site}_plan_events SET {set_clause} WHERE event_uuid=%s RETURNING *;"
|
||||||
|
try:
|
||||||
|
if not conn:
|
||||||
|
database_config = config.config()
|
||||||
|
conn = psycopg2.connect(**database_config)
|
||||||
|
conn.autocommit = False
|
||||||
|
self_conn = True
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(sql, values)
|
||||||
|
rows = cur.fetchone()
|
||||||
|
if rows and convert:
|
||||||
|
updated = postsqldb.tupleDictionaryFactory(cur.description, rows)
|
||||||
|
elif rows and not convert:
|
||||||
|
updated = rows
|
||||||
|
|
||||||
|
if self_conn:
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return updated
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
raise postsqldb.DatabaseError(error, payload, sql)
|
||||||
|
|
||||||
|
def deletePlanEventTuple(site, payload, convert=True, conn=None):
|
||||||
|
""" payload = (ids...)"""
|
||||||
|
deleted = ()
|
||||||
|
self_conn = False
|
||||||
|
sql = f"WITH deleted_rows AS (DELETE FROM {site}_plan_events WHERE event_uuid IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;"
|
||||||
|
try:
|
||||||
|
if not conn:
|
||||||
|
database_config = config.config()
|
||||||
|
conn = psycopg2.connect(**database_config)
|
||||||
|
conn.autocommit = True
|
||||||
|
self_conn = True
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(sql, payload)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
if rows and convert:
|
||||||
|
deleted = [postsqldb.tupleDictionaryFactory(cur.description, r) for r in rows]
|
||||||
|
elif rows and not convert:
|
||||||
|
deleted = rows
|
||||||
|
|
||||||
|
if self_conn:
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return deleted
|
||||||
|
except Exception as error:
|
||||||
|
raise postsqldb.DatabaseError(error, payload, sql)
|
||||||
0
application/meal_planner/meal_planner_processes.py
Normal file
0
application/meal_planner/meal_planner_processes.py
Normal file
4
application/meal_planner/sql/insertPlanEvent.sql
Normal file
4
application/meal_planner/sql/insertPlanEvent.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO %%site_name%%_plan_events
|
||||||
|
(plan_uuid, event_shortname, event_description, event_date_start, event_date_end, created_by, recipe_uuid, event_type)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
RETURNING *;
|
||||||
30
application/meal_planner/sql/selectPlanEventsByMonth.sql
Normal file
30
application/meal_planner/sql/selectPlanEventsByMonth.sql
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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 %%site_name%%_item_locations mil
|
||||||
|
JOIN %%site_name%%_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 %%site_name%%_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,
|
||||||
|
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');
|
||||||
138
application/meal_planner/static/css/planner.css
Normal file
138
application/meal_planner/static/css/planner.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
596
application/meal_planner/static/js/mealPlannerHandler.js
Normal file
596
application/meal_planner/static/js/mealPlannerHandler.js
Normal file
@ -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 = `<tr><th class="uk-text-center">Sunday</th><th class="uk-text-center">Monday</th><th class="uk-text-center">Tuesday</th><th class="uk-text-center">Wednesday</th><th class="uk-text-center">Thursday</th><th class="uk-text-center">Friday</th><th class="uk-text-center">Saturday</th></tr>`
|
||||||
|
|
||||||
|
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 += `<div class="recipe-label recipe-error" data-event_uuid="${event.event_uuid}" data-day="${day}">${event.event_shortname}</div>`;
|
||||||
|
} else if (event.event_type==="recipe" && !event.has_missing_ingredients){
|
||||||
|
eventsHTML += `<div class="recipe-label recipe-success" data-event_uuid="${event.event_uuid}" data-day="${day}">${event.event_shortname}</div>`;
|
||||||
|
} else {
|
||||||
|
eventsHTML += `<div class="custom-label" data-event_uuid="${event.event_uuid}" data-day="${day}">${event.event_shortname}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
table_cell.innerHTML = `<div class="date-box" data-day="${day}">${day}</div><div class="recipes-box">${eventsHTML}</div>`;
|
||||||
|
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 = `<th></th><th></th><th></th><th></th><th></th><th></th><th></th>`
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<ul class="uk-nav uk-dropdown-nav">
|
||||||
|
<li><a href="#" onclick="editEvent('${eventLabel.dataset.event_uuid}')">Edit Event</a></li>
|
||||||
|
<li><a href="#" onclick="postRemoveEvent('${eventLabel.dataset.event_uuid}')">Remove Event</a></li>
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
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 = `
|
||||||
|
<ul class="uk-nav uk-dropdown-nav">
|
||||||
|
<li><a href="#" onclick="addEvent('${calendarCell.dataset.day}')">Add Event</a></li>
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
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 = `<a><span uk-pagination-previous></span></a>`;
|
||||||
|
previousElement.classList.add('uk-disabled');
|
||||||
|
}else {
|
||||||
|
previousElement.innerHTML = `<a onclick="setEventModalPage(${eventModal_page-1})"><span uk-pagination-previous></span></a>`;
|
||||||
|
}
|
||||||
|
paginationElement.append(previousElement)
|
||||||
|
|
||||||
|
//first
|
||||||
|
let firstElement = document.createElement('li')
|
||||||
|
if(eventModal_page<=1){
|
||||||
|
firstElement.innerHTML = `<a><strong>1</strong></a>`;
|
||||||
|
firstElement.classList.add('uk-disabled');
|
||||||
|
}else {
|
||||||
|
firstElement.innerHTML = `<a onclick="setEventModalPage(1)">1</a>`;
|
||||||
|
}
|
||||||
|
paginationElement.append(firstElement)
|
||||||
|
|
||||||
|
// ...
|
||||||
|
if(eventModal_page-2>1){
|
||||||
|
let firstDotElement = document.createElement('li')
|
||||||
|
firstDotElement.classList.add('uk-disabled')
|
||||||
|
firstDotElement.innerHTML = `<span>…</span>`;
|
||||||
|
paginationElement.append(firstDotElement)
|
||||||
|
}
|
||||||
|
// last
|
||||||
|
if(eventModal_page-2>0){
|
||||||
|
let lastElement = document.createElement('li')
|
||||||
|
lastElement.innerHTML = `<a onclick="setEventModalPage(${eventModal_page-1})">${eventModal_page-1}</a>`
|
||||||
|
paginationElement.append(lastElement)
|
||||||
|
}
|
||||||
|
// current
|
||||||
|
if(eventModal_page!=1 && eventModal_page != eventModal_end){
|
||||||
|
let currentElement = document.createElement('li')
|
||||||
|
currentElement.innerHTML = `<li class="uk-active"><span aria-current="page"><strong>${eventModal_page}</strong></span></li>`
|
||||||
|
paginationElement.append(currentElement)
|
||||||
|
}
|
||||||
|
// next
|
||||||
|
if(eventModal_page+2<eventModal_end+1){
|
||||||
|
let nextElement = document.createElement('li')
|
||||||
|
nextElement.innerHTML = `<a onclick="setEventModalPage(${eventModal_page+1})">${eventModal_page+1}</a>`
|
||||||
|
paginationElement.append(nextElement)
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
if(eventModal_page+2<=eventModal_end){
|
||||||
|
let secondDotElement = document.createElement('li')
|
||||||
|
secondDotElement.classList.add('uk-disabled')
|
||||||
|
secondDotElement.innerHTML = `<span>…</span>`;
|
||||||
|
paginationElement.append(secondDotElement)
|
||||||
|
}
|
||||||
|
//end
|
||||||
|
let endElement = document.createElement('li')
|
||||||
|
if(eventModal_page>=eventModal_end){
|
||||||
|
endElement.innerHTML = `<a><strong>${eventModal_end}</strong></a>`;
|
||||||
|
endElement.classList.add('uk-disabled');
|
||||||
|
}else {
|
||||||
|
endElement.innerHTML = `<a onclick="setEventModalPage(${eventModal_end})">${eventModal_end}</a>`;
|
||||||
|
}
|
||||||
|
paginationElement.append(endElement)
|
||||||
|
//next button
|
||||||
|
let nextElement = document.createElement('li')
|
||||||
|
if(eventModal_page>=eventModal_end){
|
||||||
|
nextElement.innerHTML = `<a><span uk-pagination-next></span></a>`;
|
||||||
|
nextElement.classList.add('uk-disabled');
|
||||||
|
}else {
|
||||||
|
nextElement.innerHTML = `<a onclick="setEventModalPage(${eventModal_page+1})"><span uk-pagination-next></span></a>`;
|
||||||
|
console.log(nextElement.innerHTML)
|
||||||
|
}
|
||||||
|
paginationElement.append(nextElement)
|
||||||
|
}
|
||||||
315
application/meal_planner/templates/meal_planner.html
Normal file
315
application/meal_planner/templates/meal_planner.html
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr" id="main_html">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
|
||||||
|
<title id="title"></title>
|
||||||
|
<!-- Material Icons -->
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
|
<!-- Material Symbols - Outlined Set -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
|
||||||
|
<!-- Material Symbols - Rounded Set -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded" rel="stylesheet" />
|
||||||
|
<!-- Material Symbols - Sharp Set -->
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/pantry.css') }}"/>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('meal_planner_api.static', filename='css/planner.css') }}"/>
|
||||||
|
|
||||||
|
{% if session['user']['flags']['darkmode'] %}
|
||||||
|
<link id="dark-mode" rel="stylesheet" href="{{ url_for('static', filename='css/dark-mode.css') }}"/>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
{% if session['user']['flags']['darkmode'] %}
|
||||||
|
<body class="uk-light">
|
||||||
|
{% else %}
|
||||||
|
<body>
|
||||||
|
{% endif %}
|
||||||
|
<nav class="uk-navbar-container">
|
||||||
|
<div class="uk-container uk-container-expand">
|
||||||
|
<div class="uk-navbar uk-navbar-primary">
|
||||||
|
<!-- Application Navigation-->
|
||||||
|
<div class="uk-navbar-left">
|
||||||
|
<ul class="uk-navbar-nav">
|
||||||
|
<li>
|
||||||
|
<a href>Apps</a>
|
||||||
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li class="uk-active"><a href="/planner">Planner</a></li>
|
||||||
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
<li><a href="/items">Items</a></li>
|
||||||
|
<li><a href="/items/transaction">Transaction</a></li>
|
||||||
|
<li><a href="/receipts">Receipts</a></li>
|
||||||
|
<li class="uk-nav-header">Points of Ease</li>
|
||||||
|
<li><a href="/poe/scanner">Transaction Scanner</a></li>
|
||||||
|
<li class="uk-active"><a href="/poe/receipts">Receipts Scanner</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- Breadcrumbs Navigation -->
|
||||||
|
<div class="uk-navbar-center uk-visible@m">
|
||||||
|
<ul class="uk-breadcrumb uk-margin-remove">
|
||||||
|
<li style="cursor: pointer;"><span><strong>{{current_site}}</strong></span>
|
||||||
|
<div uk-dropdown="mode: hover">
|
||||||
|
<ul class="uk-nav uk-dropdown-nav">
|
||||||
|
<li class="uk-nav-header">Select Site</li>
|
||||||
|
<li class="uk-nav-divider"></li>
|
||||||
|
{% for site in sites %}
|
||||||
|
{% if site == current_site %}
|
||||||
|
<li><a class="uk-disabled" href="#">{{site}}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a onclick="changeSite('{{site}}')">{{site}}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li style="cursor: default; user-select: none;" class="uk-disabled"><span>Meal Planner</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- Profile/Management Navigation-->
|
||||||
|
<div class="uk-navbar-right">
|
||||||
|
<ul class="uk-navbar-nav">
|
||||||
|
<li>
|
||||||
|
<a href="#">
|
||||||
|
<img src="{{session['user']['profile_pic_url']}}" alt="Profile Picture" class="profile-pic uk-visible@m" style="width: 40px; height: 40px; border-radius: 50%; margin-right: 5px;">
|
||||||
|
{{username}}
|
||||||
|
</a>
|
||||||
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/profile">Profile</a></li>
|
||||||
|
<li><a onclick="toggleDarkMode()">Dark Mode</a></li>
|
||||||
|
<li><a href="/site-management">Site Management</a></li>
|
||||||
|
<li><a href="/administration">System Management</a></li>
|
||||||
|
<li><a href="/access/logout">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="uk-section">
|
||||||
|
<div class="uk-container uk-text-center">
|
||||||
|
<div uk-grid>
|
||||||
|
<div class="uk-width-1-5">
|
||||||
|
<a onclick="backOneMonth()" uk-icon="icon: chevron-left"></a>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-3-5">
|
||||||
|
<h3 id="month-year-title" class=""></h3>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-5">
|
||||||
|
<a onclick="forwardOneMonth()" uk-icon="icon: chevron-right"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-container-xlarge uk-align-center">
|
||||||
|
<div id='calendar_container' class="uk-align-center uk-overflow-auto">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="calendarContextMenu"
|
||||||
|
class="uk-dropdown uk-open"
|
||||||
|
style="display:none; position:absolute; z-index:9999;">
|
||||||
|
<ul class="uk-nav uk-dropdown-nav">
|
||||||
|
<li onclick="addEventContextAction()"><a href="#">Add Event</a></li>
|
||||||
|
<li onclick=""><a href="#">Edit</a></li>
|
||||||
|
<li onclick=""><a href="#">Remove</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Event Edit Modal-->
|
||||||
|
<div id="eventEditModal" class="uk-flex-top" uk-modal>
|
||||||
|
<div class="uk-modal-dialog">
|
||||||
|
<button class="uk-modal-close-default" type="button" uk-close></button>
|
||||||
|
<div class="uk-modal-header">
|
||||||
|
<h2 class="uk-modal-title">Event Form</h2>
|
||||||
|
</div>
|
||||||
|
<div class="uk-modal-body">
|
||||||
|
<div class="uk-grid-small" uk-grid>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_uuid_edit">Event UUID</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input id="event_uuid_edit" class="uk-input uk-disabled uk-form-blank" type="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_date_edit_start">Event Date</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input id="event_date_edit_start" class="uk-input" type="date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_date_edit_end">Event Date</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input id="event_date_edit_end" class="uk-input" type="date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_type_edit">Event Type</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input id="event_type_edit" class="uk-input uk-disabled uk-form-blank" type="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1" id="recipe_label_edit_parent">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="recipe_label_modal_edit">Event Recipe UUID</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input id="recipe_label_modal_edit" class="uk-input uk-disabled uk-form-blank" type="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_name_edit">Event Name</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input class="uk-input" id="event_name_edit" type="text" placeholder="Enter Event Name...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_description_edit">Event Description</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<textarea id="event_description_edit" class="uk-textarea" rows="5" placeholder="Enter Event Description..." aria-label="Textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-modal-footer uk-text-right" id="eventsModalFooter">
|
||||||
|
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
|
||||||
|
<button onclick="postEditEvent()" class="uk-button uk-button-primary" type="button">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Event Modal-->
|
||||||
|
<div id="eventModal" class="uk-flex-top" uk-modal>
|
||||||
|
<div class="uk-modal-dialog">
|
||||||
|
<button class="uk-modal-close-default" type="button" uk-close></button>
|
||||||
|
<div class="uk-modal-header">
|
||||||
|
<h2 class="uk-modal-title">Event Form</h2>
|
||||||
|
</div>
|
||||||
|
<div id="paginationModalBody" class="uk-modal-body" hidden>
|
||||||
|
<div class="uk-grid-small" uk-grid>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div id="searchItemsForm" onkeydown="" class="uk-search uk-search-default uk-align-center">
|
||||||
|
<input id="searchItemsInput" class="uk-border-pill uk-search-input" type="search" placeholder="" aria-label="">
|
||||||
|
<span class="uk-search-icon-flip" uk-search-icon></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<nav aria-label="Pagination">
|
||||||
|
<ul id="eventPage" class="uk-pagination uk-flex-center" uk-margin>
|
||||||
|
<li><a href="#"><span uk-pagination-previous></span></a></li>
|
||||||
|
<li><a href="#">1</a></li>
|
||||||
|
<li class="uk-disabled"><span>…</span></li>
|
||||||
|
<li><a href="#">5</a></li>
|
||||||
|
<li><a href="#">6</a></li>
|
||||||
|
<li class="uk-active"><span aria-current="page">7</span></li>
|
||||||
|
<li><a href="#">8</a></li>
|
||||||
|
<li><a href="#"><span uk-pagination-next></span></a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<caption>Select a Recipe from the system...</caption>
|
||||||
|
<table class="uk-table uk-table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Operations</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="eventsTableBody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="mainEventBody" class="uk-modal-body">
|
||||||
|
<div class="uk-grid-small" uk-grid>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<p>Adding an event requires a type of event and the following information.</p>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_date_start">Event Date Start</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input id="event_date_start" class="uk-input uk-disabled" type="date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_date_end">Event Date End</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input id="event_date_end" class="uk-input uk-disabled" type="date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_type">Select Event Type</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<select onchange="setEventTypeForm()" class="uk-select" id="event_type">
|
||||||
|
<option value="custom" selected>Custom</option>
|
||||||
|
<option value="recipe">Recipe</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1" id="recipe_button_modal" hidden>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<button class="uk-button uk-button-default" type="button" onclick="selectRecipeEvent()">Choose Recipe...</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1" id="recipe_label_modal" hidden>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<input id="selected-recipe" class="uk-input uk-disabled" type="text" placeholder="No recipe selected">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_name">Event Name</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input class="uk-input" id="event_name" type="text" placeholder="Enter Event Name...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-1">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="event_description">Event Description</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<textarea id="event_description" class="uk-textarea" rows="5" placeholder="Enter Event Description..." aria-label="Textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-modal-footer uk-text-right" id="eventsModalFooter">
|
||||||
|
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
|
||||||
|
<button onclick="postNewEvent()" class="uk-button uk-button-primary" type="button">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
{% assets "js_all" %}
|
||||||
|
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||||
|
{% endassets %}
|
||||||
|
<script src="{{ url_for('meal_planner_api.static', filename='js/mealPlannerHandler.js') }}"></script>
|
||||||
|
</html>
|
||||||
@ -57,6 +57,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -40,6 +40,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -40,6 +40,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -38,6 +38,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -38,6 +38,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -45,6 +45,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown uk-light" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown uk-light" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -38,6 +38,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li class="uk-active"><a href="/recipes">Recipes</a></li>
|
<li class="uk-active"><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li class="uk-active"><a href="/shopping-lists">Shopping Lists</a></li>
|
<li class="uk-active"><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
<a href>Apps</a>
|
<a href>Apps</a>
|
||||||
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
|
||||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
<li><a href="/planner">Planner</a></li>
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
<li><a href="/shopping-lists">Shopping Lists</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
|
|||||||
@ -145,4 +145,95 @@
|
|||||||
sql='SELECT conversions.conv_factor FROM test_conversions conversions WHERE part_id = %s, uom_id = %s;')
|
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 = ... ^',
|
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),
|
payload=(2016, 6),
|
||||||
sql='SELECT conversions.conv_factor FROM test_conversions conversions WHERE part_id = %s AND uom_id = %s;')
|
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');')
|
||||||
319
plu_items.csv
Normal file
319
plu_items.csv
Normal file
@ -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)
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
--background: #121212;
|
--background: #121212;
|
||||||
--background-text: #ffffff;
|
--background-text: #ffffff;
|
||||||
|
|
||||||
--surface: #121212;
|
--surface: #181818;
|
||||||
--surface-text: #ffffff;
|
--surface-text: #ffffff;
|
||||||
|
|
||||||
--surface-two: #252525;
|
--surface-two: #252525;
|
||||||
|
--surface-three: #383838;
|
||||||
|
|
||||||
--error: #CF6679;
|
--error: #CF6679;
|
||||||
--error-text: #000000;
|
--error-text: #000000;
|
||||||
@ -251,4 +252,128 @@ select, option {
|
|||||||
.uk-button.uk-button-danger {
|
.uk-button.uk-button-danger {
|
||||||
background-color: var(--error);
|
background-color: var(--error);
|
||||||
color: var(--error-text);
|
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);
|
||||||
}
|
}
|
||||||
@ -12,6 +12,7 @@ from application.items import items_API
|
|||||||
from application.poe import poe_api
|
from application.poe import poe_api
|
||||||
from application.shoppinglists import shoplist_api
|
from application.shoppinglists import shoplist_api
|
||||||
from application.receipts import receipts_api
|
from application.receipts import receipts_api
|
||||||
|
from application.meal_planner import meal_planner_api
|
||||||
from flasgger import Swagger
|
from flasgger import Swagger
|
||||||
from outh import oauth
|
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(receipts_api.receipt_api, url_prefix='/receipts')
|
||||||
app.register_blueprint(shoplist_api.shopping_list_api, url_prefix="/shopping-lists")
|
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(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')
|
js = Bundle('js/uikit.min.js', 'js/uikit-icons.min.js', output='gen/main.js')
|
||||||
assets.register('js_all', js)
|
assets.register('js_all', js)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user