Compare commits

..

2 Commits

Author SHA1 Message Date
Jadowyne Ulve
f1f656a066 next step 2024-10-22 19:07:05 -05:00
Jadowyne Ulve
e8697ffceb Lots of work 2024-10-20 10:03:14 -05:00
90 changed files with 3392 additions and 259 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

451
api.py
View File

@ -1,9 +1,14 @@
from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response
import psycopg2, math import psycopg2, math, json, datetime, main, copy
from config import config from config import config, sites_config
database_api= Blueprint('database_api', __name__) database_api= Blueprint('database_api', __name__)
@database_api.route("/changeSite")
def changeSite():
site = request.args.get('site', 'main')
session['selected_site'] = site
return jsonify({'status': 'SUCCESS'})
def paginate_with_params(cur, limit, offset, params): def paginate_with_params(cur, limit, offset, params):
sql = f"SELECT * FROM main_items LEFT JOIN main_logistics_info ON main_items.logistics_info_id = main_logistics_info.id" sql = f"SELECT * FROM main_items LEFT JOIN main_logistics_info ON main_items.logistics_info_id = main_logistics_info.id"
@ -48,15 +53,87 @@ def paginate_default(cur, limit, offset):
count = cur.fetchone()[0] count = cur.fetchone()[0]
return pantry_inventory, count return pantry_inventory, count
def paginate_with_params_groups(cur, limit, offset, params):
sql = f"SELECT * FROM main_groups"
count = f"SELECT COUNT(*) FROM main_groups"
# WHERE search_string LIKE '%{search_string}%'
strings = []
count_strings = []
if params['search_string'] != "":
s = params['search_string']
strings.append(f" search_string LIKE '%{s}%'")
count_strings.append(f" search_string LIKE '%{s}%'")
# LIMIT {limit} OFFSET {offset};"
if len(strings) > 0:
sql = f"{sql} WHERE{" AND".join(strings)}"
if len(count_strings) > 0:
count = f"{count} WHERE{" AND".join(count_strings)}"
sql = f"{sql} ORDER BY main_logistics_info.quantity_on_hand LIMIT {limit} OFFSET {offset};"
count = f"{count};"
print(count)
print(sql)
cur.execute(sql)
pantry_inventory = cur.fetchall()
cur.execute(count)
count = cur.fetchone()[0]
return pantry_inventory, count
@database_api.route("/getGroups")
def paginate_groups():
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
site_name = session['selected_site']
offset = (page - 1) * limit
groups = []
count = 0
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"SELECT * FROM {site_name}_groups LIMIT %s OFFSET %s;"
count = f"SELECT COUNT(*) FROM {site_name}_groups"
cur.execute(sql, (limit, offset))
groups = cur.fetchall()
cur.execute(count)
count = cur.fetchone()[0]
sql_item = f"SELECT {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_logistics_info.quantity_on_hand FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id WHERE {site_name}_items.id = %s; "
new_groups = []
for group in groups:
qty = 0
group = list(group)
items = []
print(group[3])
for item_id in group[3]:
cur.execute(sql_item, (item_id,))
item_row = cur.fetchone()
qty += float(item_row[2])
items.append(item_row)
group[3] = items
group.append(qty)
new_groups.append(group)
except (Exception, psycopg2.DatabaseError) as error:
print(error)
return jsonify({'groups': new_groups, "end": math.ceil(count/limit)})
@database_api.route("/getItems") @database_api.route("/getItems")
def pagninate_items(): def pagninate_items():
print("hello")
page = int(request.args.get('page', 1)) page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10)) limit = int(request.args.get('limit', 10))
search_string = str(request.args.get('search_text', "")) search_string = str(request.args.get('search_text', ""))
sort_order = int(request.args.get('sort_order', 1)) sort_order = int(request.args.get('sort_order', 1))
view = int(request.args.get('view', 0)) view = int(request.args.get('view', 0))
site_name = session['selected_site']
offset = (page - 1) * limit offset = (page - 1) * limit
@ -67,30 +144,25 @@ def pagninate_items():
with psycopg2.connect(**database_config) as conn: with psycopg2.connect(**database_config) as conn:
try: try:
with conn.cursor() as cur: with conn.cursor() as cur:
pantry_inventory, count = paginate_with_params( sql = f"SELECT * FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id LIMIT {limit} OFFSET {offset};"
cur, limit, offset, count = f"SELECT COUNT(*) FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id;"
{'search_string': search_string, cur.execute(sql)
'view': view} pantry_inventory = cur.fetchall()
) cur.execute(count)
count = cur.fetchone()[0]
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
if sort_order == 0:
pantry_inventory = sorted(pantry_inventory, key=lambda x: x[1])
if sort_order == 1:
pantry_inventory = sorted(pantry_inventory, key=lambda x: x[2])
if sort_order == 2:
pantry_inventory = sorted(pantry_inventory, key=lambda x: x[18])
return jsonify({'items': pantry_inventory, "end": math.ceil(count/limit)}) return jsonify({'items': pantry_inventory, "end": math.ceil(count/limit)})
@database_api.route("/getItem") @database_api.route("/getItem")
def get_item(): def get_item():
id = int(request.args.get('id', 1)) id = int(request.args.get('id', 1))
database_config = config() database_config = config()
site_name = "main" site_name = session['selected_site']
sites = sites_config()
item = [] item = []
with psycopg2.connect(**database_config) as conn: with psycopg2.connect(**database_config) as conn:
try: try:
@ -99,24 +171,351 @@ def get_item():
sql = file.read() sql = file.read()
cur.execute(sql, (id, )) cur.execute(sql, (id, ))
item = list(cur.fetchone()) item = list(cur.fetchone())
item[5] = {'walmart': 'https://www.walmart.com/ip/Ptasie-Mleczko-Chocolate-Covered-Vanilla-Marshmallow-birds-milk-chocolate-13-4-Oz-Includes-Our-Exclusive-HolanDeli-Chocolate-Mints/965551629?classType=REGULAR&from=/search', 'target': 'https://www.target.com/p/hershey-39-s-cookies-39-n-39-cr-232-me-fangs-halloween-candy-snack-size-9-45oz/-/A-79687769#lnk=sametab'} SQL_groups = f"SELECT * FROM {site_name}_groups WHERE included_items @> ARRAY[%s];"
item[22] = ['test_list', 'main_list'] cur.execute(SQL_groups, (item[0], ))
item[23] = ['test_recipe',] item[25] = list(cur.fetchall())
item[24] = ['test_group', 'main_group', 'test2_group'] SQL_shopping_lists = f"SELECT * FROM {site_name}_shopping_lists WHERE pantry_items @> ARRAY[%s];"
cur.execute(SQL_shopping_lists, (item[0], ))
item[23] = list(cur.fetchall())
print(item)
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
return render_template(f"item_page/index.html", item=item) return render_template(f"items/item.html", item=item, current_site=site_name, sites=sites['sites'])
@database_api.route("/addItem")
def addItem():
barcode = str(request.args.get('barcode', ""))
name = str(request.args.get('item_name', ""))
description = str(request.args.get('item_description', ""))
item_type = str(request.args.get('item_type', ""))
subtype = str(request.args.get('sub_type', ""))
site_name = session['selected_site']
state = "FAILED"
payload = copy.deepcopy(main.payload_food_item)
defaults = config(filename=f"sites/{site_name}/site.ini", section="defaults")
uuid = f"{defaults["default_zone"]}@{defaults["default_primary_location"]}"
name = name.replace("'", "@&apostraphe&")
payload["logistics_info"]["primary_location"] = uuid
payload["logistics_info"]["auto_issue_location"] = uuid
database_config = config()
with psycopg2.connect(**database_config) as conn:
logistics_info_id = main.create_logistics_info(conn, site_name, barcode, payload["logistics_info"])
if not logistics_info_id:
return jsonify({'state': str(logistics_info_id)})
item_info_id = main.create_item_info(conn, site_name, barcode, payload["item_info"])
if not item_info_id:
return jsonify({'state': str(item_info_id)})
food_info_id = main.create_food_info(conn, site_name, payload["food_info"])
if not food_info_id:
return jsonify({'state': str(food_info_id)})
sqltwo = f"INSERT INTO {site_name}_items(barcode, item_name, description, item_info_id, logistics_info_id, food_info_id, row_type, item_type, search_string) VALUES('{barcode}', '{name}', '{description}', {item_info_id}, {logistics_info_id}, {food_info_id}, '{item_type}', '{subtype}', '{barcode}%{name}') RETURNING *;"
row = None
try:
with conn.cursor() as cur:
cur.execute(sqltwo)
rows = cur.fetchone()
if rows:
row = rows[:]
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
return jsonify({'state': str(error)})
conn.commit()
main.add_transaction(site_name, barcode, qty=0, user_id=1, description="Added Item to System!")
return jsonify({'state': "SUCCESS"})
@database_api.route("/addGroup") @database_api.route("/addGroup")
def addGroup(): def addGroup():
name = str(request.args.get('name', "")) name = str(request.args.get('name', ""))
description = str(request.args.get('description', "")) description = str(request.args.get('description', ""))
group_type = str(request.args.get('type', "")) group_type = str(request.args.get('type', ""))
site_name = session['selected_site']
state = "FAILED" state = "FAILED"
if name or description or group_type == "": database_config = config()
print("this is empty") with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"INSERT INTO {site_name}_groups (name, description, included_items, group_type) VALUES (%s, %s, %s, %s);"
cur.execute(sql, (name, description, json.dumps({}), group_type))
state = "SUCCESS"
conn.commit()
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
return jsonify({'state': state}) return jsonify({'state': state})
@database_api.route("/getGroup")
def get_group():
id = int(request.args.get('id', 1))
database_config = config()
site_name = session['selected_site']
group = []
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"SELECT * FROM {site_name}_groups WHERE id=%s;"
cur.execute(sql, (id, ))
group = list(cur.fetchone())
sql_item = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_logistics_info.quantity_on_hand FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id WHERE {site_name}_items.id = %s;"
qty = 0
group = list(group)
items = []
print(group[3])
for item_id in group[3]:
cur.execute(sql_item, (item_id,))
item_row = cur.fetchone()
qty += float(item_row[3])
items.append(item_row)
group[3] = items
group.append(qty)
except (Exception, psycopg2.DatabaseError) as error:
print(error)
return jsonify(group=group)
@database_api.route("/updateGroup", methods=["POST"])
def update_group():
if request.method == "POST":
site_name = session['selected_site']
group_id = request.get_json()['id']
items = request.get_json()['items']
name = request.get_json()['name']
description = request.get_json()['description']
group_type = request.get_json()['group_type']
data = (name, description, items, group_type, group_id)
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
# Start by updating the group -> included items with the up to date list
sql = f"UPDATE {site_name}_groups SET name = %s, description = %s, included_items = %s, group_type = %s WHERE id=%s;"
cur.execute(sql, data)
update_item_sql = f"UPDATE {site_name}_item_info SET groups = %s WHERE id = %s;"
select_item_sql = f"SELECT {site_name}_item_info.id, {site_name}_item_info.groups FROM {site_name}_items LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE {site_name}_items.id = %s;"
# Now we will fetch each item row one by one and check if the group id is already inside of its groups array
for item_id in items:
cur.execute(select_item_sql, (item_id, ))
item = cur.fetchone()
print(item)
item_groups: set = set(item[1])
# Condition check, adds it if it doesnt exist.
if group_id not in item_groups:
item_groups.add(group_id)
cur.execute(update_item_sql, (list(item_groups), item[0]))
# Now we fetch all items that have the group id in its groups array
fetch_items_with_group = f"SELECT {site_name}_items.id, groups, {site_name}_item_info.id FROM {site_name}_item_info LEFT JOIN {site_name}_items ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE groups @> ARRAY[%s];"
cur.execute(fetch_items_with_group, (group_id, ))
group_items = cur.fetchall()
print(items)
# We will then check each item id against the groups new included_items list to see if the item should be in there
for item_id, group, info_id in group_items:
# If it is not we remove the group form the items list and update the item
if item_id not in items:
groups: list = list(group)
groups.remove(group_id)
cur.execute(update_item_sql, (list(groups), info_id))
conn.commit()
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
return jsonify({"state": "SUCCESS"})
return jsonify({"state": "FAILED"})
@database_api.route("/addList")
def addList():
name = str(request.args.get('name', ""))
description = str(request.args.get('description', ""))
list_type = str(request.args.get('type', ""))
site_name = session['selected_site']
print(name, description, list_type)
state = "FAILED"
#if name or description or group_type == "":
# print("this is empty")
# return jsonify({'state': state})
timestamp = datetime.datetime.now()
data = (name, description, [], json.dumps({}), [], [], 0, timestamp, list_type)
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"INSERT INTO {site_name}_shopping_lists (name, description, pantry_items, custom_items, recipes, groups, author, creation_date, type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s);"
cur.execute(sql, data)
state = "SUCCESS"
conn.commit()
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
return jsonify({'state': state})
@database_api.route("/getLists")
def paginate_lists():
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
site_name = session['selected_site']
offset = (page - 1) * limit
lists = []
count = 0
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"SELECT * FROM {site_name}_shopping_lists LIMIT %s OFFSET %s;"
count = f"SELECT COUNT(*) FROM {site_name}_shopping_lists;"
cur.execute(sql, (limit, offset))
temp_lists = list(cur.fetchall())
cur.execute(count)
count = cur.fetchone()[0]
for shopping_list in temp_lists:
shopping_list: list = list(shopping_list)
pantry_items = shopping_list[3]
custom_items = shopping_list[4]
list_length = len(custom_items)
if shopping_list[10] == 'calculated':
item_sql = f"SELECT COUNT(*) FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id LEFT JOIN {site_name}n_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id LEFT JOIN {site_name}_food_info ON {site_name}_items.food_info_id = {site_name}_food_info.id WHERE {site_name}_logistics_info.quantity_on_hand < {site_name}_item_info.safety_stock AND shopping_lists @> ARRAY[%s];"
cur.execute(item_sql, (shopping_list[0], ))
list_length += cur.fetchone()[0]
else:
list_length += len(pantry_items)
shopping_list.append(list_length)
lists.append(shopping_list)
except (Exception, psycopg2.DatabaseError) as error:
print(error)
return jsonify({'lists': lists, 'end': math.ceil(count/limit)})
@database_api.route("/getListView")
def get_list_view():
id = int(request.args.get('id', 1))
site_name = session['selected_site']
shopping_list = []
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"SELECT * FROM {site_name}_shopping_lists WHERE id=%s;"
cur.execute(sql, (id, ))
shopping_list = list(cur.fetchone())
if shopping_list[10] == "calculated":
itemSQL = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_items.links, {site_name}_logistics_info.quantity_on_hand, {site_name}_item_info.safety_stock, {site_name}_item_info.uom FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id LEFT JOIN {site_name}_food_info ON {site_name}_items.food_info_id = {site_name}_food_info.id WHERE {site_name}_logistics_info.quantity_on_hand < {site_name}_item_info.safety_stock AND shopping_lists @> ARRAY[%s];"
else:
itemSQL = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_items.links, {site_name}_logistics_info.quantity_on_hand, {site_name}_item_info.safety_stock, {site_name}_item_info.uom FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id LEFT JOIN {site_name}_food_info ON {site_name}_items.food_info_id = {site_name}_food_info.id WHERE shopping_lists @> ARRAY[%s];"
cur.execute(itemSQL, (id, ))
shopping_list[3] = list(cur.fetchall())
print(shopping_list)
except (Exception, psycopg2.DatabaseError) as error:
print(error)
return jsonify(shopping_list=shopping_list)
@database_api.route("/getList")
def get_list():
id = int(request.args.get('id', 1))
database_config = config()
site_name = session['selected_site']
shopping_list = []
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
sql = f"SELECT * FROM {site_name}_shopping_lists WHERE id=%s;"
cur.execute(sql, (id, ))
shopping_list = list(cur.fetchone())
itemSQL = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_items.links, {site_name}_item_info.uom FROM {site_name}_items LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE {site_name}_item_info.shopping_lists @> ARRAY[%s];"
cur.execute(itemSQL, (id, ))
shopping_list[3] = list(cur.fetchall())
print(shopping_list)
except (Exception, psycopg2.DatabaseError) as error:
print(error)
return jsonify(shopping_list=shopping_list)
@database_api.route("/updateList", methods=["POST"])
def update_list():
if request.method == "POST":
site_name = session['selected_site']
list_id = request.get_json()['id']
items = request.get_json()['items']
print(items)
custom_items = request.get_json()['custom']
name = request.get_json()['name']
description = request.get_json()['description']
list_type = request.get_json()['list_type']
quantities = request.get_json()['quantities']
data = (name, description, items, json.dumps(custom_items), list_type, json.dumps(quantities), list_id)
database_config = config()
with psycopg2.connect(**database_config) as conn:
try:
with conn.cursor() as cur:
# Start by updating the group -> included items with the up to date list
sql = f"UPDATE {site_name}_shopping_lists SET name = %s, description = %s, pantry_items = %s, custom_items = %s, type = %s, quantities = %s WHERE id=%s;"
cur.execute(sql, data)
update_item_sql = f"UPDATE {site_name}_item_info SET shopping_lists = %s WHERE id = %s;"
select_item_sql = f"SELECT {site_name}_item_info.id, {site_name}_item_info.shopping_lists FROM {site_name}_items LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE {site_name}_items.id = %s;"
# Now we will fetch each item row one by one and check if the group id is already inside of its groups array
for item_id in items:
cur.execute(select_item_sql, (item_id, ))
item = cur.fetchone()
print(item)
shopping_lists: set = set(item[1])
# Condition check, adds it if it doesnt exist.
if list_id not in shopping_lists:
shopping_lists.add(list_id)
cur.execute(update_item_sql, (list(shopping_lists), item[0]))
# Now we fetch all items that have the group id in its groups array
fetch_items_with_list = f"SELECT {site_name}_items.id, {site_name}_item_info.shopping_lists, {site_name}_item_info.id FROM {site_name}_item_info LEFT JOIN {site_name}_items ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE {site_name}_item_info.shopping_lists @> ARRAY[%s];"
cur.execute(fetch_items_with_list, (list_id, ))
list_items = cur.fetchall()
print(items)
# We will then check each item id against the groups new included_items list to see if the item should be in there
for item_id, shopping_list, info_id in list_items:
# If it is not we remove the group form the items list and update the item
if item_id not in items:
shopping_lists: list = list(shopping_list)
shopping_lists.remove(list_id)
cur.execute(update_item_sql, (list(shopping_lists), info_id))
conn.commit()
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
return jsonify({"state": "SUCCESS"})
return jsonify({"state": "FAILED"})

View File

@ -1,5 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
from configparser import ConfigParser from configparser import ConfigParser
import json
def config(filename='database.ini', section='postgresql'): def config(filename='database.ini', section='postgresql'):
@ -19,3 +20,47 @@ def config(filename='database.ini', section='postgresql'):
return db return db
def sites_config(filename='database.ini', section='manage'):
# create a parser
parser = ConfigParser()
# read config file
parser.read(filename)
# get section, default to postgresql
sites = {}
if parser.has_section(section):
params = parser.items(section)
for param in params:
sites[param[0]] = param[1].split(',')
else:
raise Exception('Section {0} not found in the {1} file'.format(section, filename))
return sites
def write_new_site(site_name):
old_value = sites_config()['sites']
print(old_value)
old_value.append(site_name)
old_value = set(old_value)
config = ConfigParser()
config.read('database.ini')
config.set('manage', 'sites', ','.join(old_value))
with open('database.ini', 'w') as configFile:
config.write(configFile)
def delete_site(site_name):
old_value = sites_config()['sites']
old_value.remove(site_name)
config = ConfigParser()
config.read('database.ini')
config.set('manage', 'sites', ','.join(old_value))
with open('database.ini', 'w') as configFile:
config.write(configFile)

View File

@ -4,3 +4,7 @@ database=test
user = test user = test
password = test password = test
port = 5432 port = 5432
[manage]
sites = test,test2,main

21
main.py
View File

@ -35,7 +35,7 @@ def update_item_primary(site_name, barcode, new_primary: str):
return False return False
def insert_row(table_name, name): def insert_row(table_name, name):
sql = f"INSERT INTO {table_name}(id, name) VALUES(%s, %s) RETURNING id" sql = f"INSERT INTO {table_name}(id, name) VALUES(%s, %s) RETURNING id;"
id = None id = None
try: try:
database_config = config() database_config = config()
@ -85,7 +85,7 @@ def create_logistics_info(conn, site_name, barcode, payload):
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
conn.rollback() conn.rollback()
return False return error
return logistics_info_id return logistics_info_id
@ -103,7 +103,7 @@ def create_item_info(conn, site_name, barcode, payload):
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
conn.rollback() conn.rollback()
return False return error
return item_info_id return item_info_id
@ -136,7 +136,7 @@ def add_location(site_name, name, zone_id):
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
conn.rollback() conn.rollback()
return False return error
uuid = f"{zone_name}@{name}" uuid = f"{zone_name}@{name}"
try: try:
@ -145,7 +145,7 @@ def add_location(site_name, name, zone_id):
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
conn.rollback() conn.rollback()
return False return error
def add_zone(site_name, name): def add_zone(site_name, name):
database_config = config() database_config = config()
@ -157,7 +157,7 @@ def add_zone(site_name, name):
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
conn.rollback() conn.rollback()
return False return error
def add_transaction(site_name, barcode, qty, user_id, transaction_type = "info", description = "", data = {}, location=None): def add_transaction(site_name, barcode, qty, user_id, transaction_type = "info", description = "", data = {}, location=None):
database_config = config() database_config = config()
@ -192,7 +192,7 @@ def add_transaction(site_name, barcode, qty, user_id, transaction_type = "info",
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
conn.rollback() conn.rollback()
return False return error
if not location: if not location:
mover = logistics_info[2] mover = logistics_info[2]
@ -211,7 +211,7 @@ def add_transaction(site_name, barcode, qty, user_id, transaction_type = "info",
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
conn.rollback() conn.rollback()
return False return error
if logistics_info[3] in location_items.keys(): if logistics_info[3] in location_items.keys():
location_items[logistics_info[3]] = location_items[logistics_info[3]] + qty location_items[logistics_info[3]] = location_items[logistics_info[3]] + qty
@ -234,11 +234,11 @@ def add_transaction(site_name, barcode, qty, user_id, transaction_type = "info",
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
conn.rollback() conn.rollback()
return False return error
conn.commit() conn.commit()
def add_food_item(site_name: str, barcode: str, name: str, qty: float, payload: dict): def add_food_item(site_name: str, barcode: str, name: str, payload: dict):
# TODO: I need to validate the name so that it doesnt have characters against the SQL database schema such as ' # TODO: I need to validate the name so that it doesnt have characters against the SQL database schema such as '
@ -279,7 +279,6 @@ def add_food_item(site_name: str, barcode: str, name: str, qty: float, payload:
add_transaction(site_name, barcode, qty=0, user_id=1, description="Added Item to System!") add_transaction(site_name, barcode, qty=0, user_id=1, description="Added Item to System!")
add_transaction(site_name, barcode, qty=qty, user_id=1, description="scan in")
def drop_table(sql_file: str): def drop_table(sql_file: str):
database_config = config() database_config = config()

View File

@ -1,5 +1,6 @@
import sys, os, shutil import sys, os, shutil
import main import main
import config as cfg
""" """
Manage.py is where the databases and configuration is set up. Its a CLI for quick serving the databases necessary for Manage.py is where the databases and configuration is set up. Its a CLI for quick serving the databases necessary for
@ -90,6 +91,7 @@ def create():
config.write(f"default_primary_location={default_location_name}\n") config.write(f"default_primary_location={default_location_name}\n")
config.write(f"default_auto_issue_location={default_location_name}\n") config.write(f"default_auto_issue_location={default_location_name}\n")
cfg.write_new_site(site_name)
print(f"Site {site_name} config created!") print(f"Site {site_name} config created!")
print(f"Site {site_name} created!") print(f"Site {site_name} created!")
@ -108,6 +110,8 @@ if __name__ == "__main__":
if func_name == "delete" and argument == "site": if func_name == "delete" and argument == "site":
main.delete_site(sys.argv[3]) main.delete_site(sys.argv[3])
shutil.rmtree(f"sites/{sys.argv[3]}") shutil.rmtree(f"sites/{sys.argv[3]}")
cfg.delete_site(sys.argv[3])
if func_name == "item": if func_name == "item":
if argument == "add": if argument == "add":

View File

@ -3,7 +3,7 @@ CREATE TABLE IF NOT EXISTS %sitename%_items(
barcode VARCHAR(255) NOT NULL, barcode VARCHAR(255) NOT NULL,
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,

View File

@ -2,10 +2,11 @@ CREATE TABLE IF NOT EXISTS %sitename%_shopping_lists (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
description TEXT, description TEXT,
pantry_items JSONB, pantry_items INTEGER [],
custom_items JSONB, custom_items JSONB,
recipes JSONB, recipes INTEGER [],
groups JSONB, groups INTEGER [],
quantities JSONB,
author INTEGER, author INTEGER,
creation_date TIMESTAMP, creation_date TIMESTAMP,
type VARCHAR(64), type VARCHAR(64),

View File

@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS main_items(
barcode VARCHAR(255) NOT NULL, barcode VARCHAR(255) NOT NULL,
item_name VARCHAR(255) NOT NULL, item_name VARCHAR(255) NOT NULL,
brand INTEGER, brand INTEGER,
description TEXT,
tags TEXT [], tags TEXT [],
links JSONB, links JSONB,
item_info_id INTEGER NOT NULL, item_info_id INTEGER NOT NULL,

View File

@ -2,10 +2,11 @@ CREATE TABLE IF NOT EXISTS main_shopping_lists (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
description TEXT, description TEXT,
pantry_items JSONB, pantry_items INTEGER [],
custom_items JSONB, custom_items JSONB,
recipes JSONB, recipes INTEGER [],
groups JSONB, groups INTEGER [],
quantities JSONB,
author INTEGER, author INTEGER,
creation_date TIMESTAMP, creation_date TIMESTAMP,
type VARCHAR(64), type VARCHAR(64),

View File

@ -9,36 +9,37 @@ WHERE main_items.id=%s;
01 - barcode 01 - barcode
02 - item_name 02 - item_name
03 - brand (id) 03 - brand (id)
04 - tags 04 - description
05 - links 05 - tags
06 - item_info_id 06 - links
07 - logistics_info_id 07 - item_info_id
08 - food_info_id 08 - logistics_info_id
09 - row_type 09 - food_info_id
10 - item_type 10 - row_type
11 - search_string 11 - item_type
12 - logistics_info_id 12 - search_string
13 - barcode 13 - logistics_info_id
14 - primary_location 14 - barcode
15 - auto_issue_location 15 - primary_location
16 - dynamic_locations 16 - auto_issue_location
17 - location_data 17 - dynamic_locations
18 - quantity_on_hand 18 - location_data
19 - item_info_id 19 - quantity_on_hand
20 - barcode 20 - item_info_id
21 - linked_items 21 - barcode
22 - shopping_lists 22 - linked_items
23 - recipes 23 - shopping_lists
24 - groups 24 - recipes
25 - packaging 25 - groups
26 - uom 26 - packaging
27 - cost 27 - uom
28 - safety_stock 28 - cost
29 - lead_time_days 29 - safety_stock
30 - ai_pick 30 - lead_time_days
31 - food_info_id 31 - ai_pick
32 - food_groups 32 - food_info_id
33 - ingrediants 33 - food_groups
34 - nutrients 34 - ingrediants
35 - expires 35 - nutrients
36 - expires
*/ */

9
sites/test/site.ini Normal file
View File

@ -0,0 +1,9 @@
[site]
site_name=test
site_owner=
email=
[defaults]
default_zone=default
default_primary_location=all
default_auto_issue_location=all

View File

@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS test_brands (
id SERIAL PRIMARY KEY,
name VARCHAR(255)
);

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS test_food_info (
id SERIAL PRIMARY KEY,
food_groups TEXT [],
ingrediants TEXT [],
nutrients JSONB,
expires BOOLEAN
);

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS test_groups(
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
included_items INTEGER [],
group_type VARCHAR(255),
UNIQUE (name)
);

View File

@ -0,0 +1,28 @@
CREATE TABLE IF NOT EXISTS test_items(
id SERIAL PRIMARY KEY,
barcode VARCHAR(255) NOT NULL,
item_name VARCHAR(255) NOT NULL,
brand INTEGER,
description TEXT,
tags TEXT [],
links JSONB,
item_info_id INTEGER NOT NULL,
logistics_info_id INTEGER NOT NULL,
food_info_id INTEGER,
row_type VARCHAR(255) NOT NULL,
item_type VARCHAR(255) NOT NULL,
search_string TEXT NOT NULL,
UNIQUE(barcode, item_info_id),
CONSTRAINT fk_item_info
FOREIGN KEY(item_info_id)
REFERENCES test_item_info(id),
CONSTRAINT fk_food_info
FOREIGN KEY(food_info_id)
REFERENCES test_food_info(id),
CONSTRAINT fk_brand
FOREIGN KEY(brand)
REFERENCES test_brands(id),
CONSTRAINT fk_logistics_info
FOREIGN KEY(logistics_info_id)
REFERENCES test_logistics_info(id)
);

View File

@ -0,0 +1,15 @@
CREATE TABLE IF NOt EXISTS test_item_info (
id SERIAL PRIMARY KEY,
barcode VARCHAR(255) NOT NULL,
linked_items INTEGER [],
shopping_lists INTEGER [],
recipes INTEGER [],
groups INTEGER [],
packaging VARCHAR(255),
uom VARCHAR(255),
cost FLOAT8,
safety_stock FLOAT8,
lead_time_days FLOAT8,
ai_pick BOOLEAN,
UNIQUE(barcode)
);

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS test_itemlinks (
id SERIAL PRIMARY KEY,
barcode VARCHAR(255) NOt NULL,
link INTEGER NOT NULL,
data JSONB NOT NULL,
conv_factor FLOAT8 NOt NULL,
UNIQUE(barcode)
);

View File

@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS test_locations(
id SERIAL PRIMARY KEY,
uuid VARCHAR(255) NOT NULL,
name VARCHAR(32) NOT NULL,
zone_id INTEGER NOT NULL,
items JSONB,
UNIQUE(uuid),
CONSTRAINT fk_zone
FOREIGN KEY(zone_id)
REFERENCES test_zones(id)
);

View File

@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS logins(
id SERIAL PRIMARY KEY,
username VARCHAR(255),
password VARCHAR(255),
favorites JSONB,
unseen_pantry_items INTEGER [],
unseen_groups INTEGER [],
unseen_shopping_lists INTEGER [],
unseen_recipes INTEGER [],
seen_pantry_items INTEGER [],
seen_groups INTEGER[],
seen_shopping_lists INTEGER [],
seen_recipes INTEGER [],
flags JSONB
);

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS test_logistics_info(
id SERIAL PRIMARY KEY,
barcode VARCHAR(255) NOT NULL,
primary_location VARCHAR(64),
auto_issue_location VARCHAR(64),
dynamic_locations JSONB,
location_data JSONB,
quantity_on_hand FLOAT8 NOT NULL,
UNIQUE(barcode)
);

View File

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS test_receipt_items (
id SERIAL PRIMARY KEY,
type VARCHAR(255) NOT NULL,
barcode VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
qty FLOAT8 NOT NULL,
data JSONB,
status VARCHAR (64)
);

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS test_receipts (
id SERIAL PRIMARY KEY,
receipt_id INTEGER NOT NULL,
receipt_status VARCHAR (64) NOT NULL,
date_submitted TIMESTAMP NOT NULL,
submitted_by INTEGER NOT NULL,
vendor_id INTEGER,
files JSONB,
UNIQUE(receipt_id)
);

View File

@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS test_recipes (
id SERIAL PRIMARY KEY,
name VARCHAR,
author INTEGER,
description TEXT,
creation_date TIMESTAMP,
custom_items JSONB,
pantry_items JSONB,
group_items JSONB,
instructions TEXT [],
picture_path TEXT
);

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS test_shopping_lists (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
pantry_items INTEGER [],
custom_items JSONB,
recipes INTEGER [],
groups INTEGER [],
quantities JSONB,
author INTEGER,
creation_date TIMESTAMP,
type VARCHAR(64),
UNIQUE(name)
);

View File

@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS test_Transactions (
id SERIAL PRIMARY KEY,
timestamp TIMESTAMP,
logistics_info_id INTEGER NOT NULL,
barcode VARCHAR(255) NOT NULL,
name VARCHAR(255),
transaction_type VARCHAR(255) NOT NULL,
quantity FLOAT8 NOT NULL,
description TEXT,
user_id INTEGER NOT NULL,
data JSONB,
CONSTRAINT fk_logistics_info
FOREIGN KEY(logistics_info_id)
REFERENCES test_logistics_info(id)
);

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS test_vendors (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
address VARCHAR(255),
creation_date TIMESTAMP NOT NULL,
created_by TIMESTAMP NOT NULL,
phone_number VARCHAR(32)
);

View File

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS test_zones(
id SERIAL PRIMARY KEY,
name VARCHAR(32) NOT NULL,
UNIQUE(name)
);

View File

@ -0,0 +1 @@
DROP TABLE test_brands CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_food_info CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_groups CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_item_info CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_items CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_itemlinks CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_locations CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_logistics_info CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_receipt_items CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_receipts CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_recipes CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_shopping_lists CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_transactions CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_vendors CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test_zones CASCADE;

View File

@ -0,0 +1,45 @@
SELECT * FROM test_items
LEFT JOIN test_logistics_info ON test_items.logistics_info_id = test_logistics_info.id
LEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.id
LEFT JOIN test_food_info ON test_items.food_info_id = test_food_info.id
WHERE test_items.id=%s;
/*
00 - item_id
01 - barcode
02 - item_name
03 - brand (id)
04 - description
05 - tags
06 - links
07 - item_info_id
08 - logistics_info_id
09 - food_info_id
10 - row_type
11 - item_type
12 - search_string
13 - logistics_info_id
14 - barcode
15 - primary_location
16 - auto_issue_location
17 - dynamic_locations
18 - location_data
19 - quantity_on_hand
20 - item_info_id
21 - barcode
22 - linked_items
23 - shopping_lists
24 - recipes
25 - groups <--
26 - packaging
27 - uom
28 - cost
29 - safety_stock
30 - lead_time_days
31 - ai_pick
32 - food_info_id
33 - food_groups
34 - ingrediants
35 - nutrients
36 - expires
*/

9
sites/test2/site.ini Normal file
View File

@ -0,0 +1,9 @@
[site]
site_name=test2
site_owner=joe
email=jdoe@gmail.com
[defaults]
default_zone=default
default_primary_location=all
default_auto_issue_location=all

View File

@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS test2_brands (
id SERIAL PRIMARY KEY,
name VARCHAR(255)
);

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS test2_food_info (
id SERIAL PRIMARY KEY,
food_groups TEXT [],
ingrediants TEXT [],
nutrients JSONB,
expires BOOLEAN
);

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS test2_groups(
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
included_items INTEGER [],
group_type VARCHAR(255),
UNIQUE (name)
);

View File

@ -0,0 +1,28 @@
CREATE TABLE IF NOT EXISTS test2_items(
id SERIAL PRIMARY KEY,
barcode VARCHAR(255) NOT NULL,
item_name VARCHAR(255) NOT NULL,
brand INTEGER,
description TEXT,
tags TEXT [],
links JSONB,
item_info_id INTEGER NOT NULL,
logistics_info_id INTEGER NOT NULL,
food_info_id INTEGER,
row_type VARCHAR(255) NOT NULL,
item_type VARCHAR(255) NOT NULL,
search_string TEXT NOT NULL,
UNIQUE(barcode, item_info_id),
CONSTRAINT fk_item_info
FOREIGN KEY(item_info_id)
REFERENCES test2_item_info(id),
CONSTRAINT fk_food_info
FOREIGN KEY(food_info_id)
REFERENCES test2_food_info(id),
CONSTRAINT fk_brand
FOREIGN KEY(brand)
REFERENCES test2_brands(id),
CONSTRAINT fk_logistics_info
FOREIGN KEY(logistics_info_id)
REFERENCES test2_logistics_info(id)
);

View File

@ -0,0 +1,15 @@
CREATE TABLE IF NOt EXISTS test2_item_info (
id SERIAL PRIMARY KEY,
barcode VARCHAR(255) NOT NULL,
linked_items INTEGER [],
shopping_lists INTEGER [],
recipes INTEGER [],
groups INTEGER [],
packaging VARCHAR(255),
uom VARCHAR(255),
cost FLOAT8,
safety_stock FLOAT8,
lead_time_days FLOAT8,
ai_pick BOOLEAN,
UNIQUE(barcode)
);

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS test2_itemlinks (
id SERIAL PRIMARY KEY,
barcode VARCHAR(255) NOt NULL,
link INTEGER NOT NULL,
data JSONB NOT NULL,
conv_factor FLOAT8 NOt NULL,
UNIQUE(barcode)
);

View File

@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS test2_locations(
id SERIAL PRIMARY KEY,
uuid VARCHAR(255) NOT NULL,
name VARCHAR(32) NOT NULL,
zone_id INTEGER NOT NULL,
items JSONB,
UNIQUE(uuid),
CONSTRAINT fk_zone
FOREIGN KEY(zone_id)
REFERENCES test2_zones(id)
);

View File

@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS logins(
id SERIAL PRIMARY KEY,
username VARCHAR(255),
password VARCHAR(255),
favorites JSONB,
unseen_pantry_items INTEGER [],
unseen_groups INTEGER [],
unseen_shopping_lists INTEGER [],
unseen_recipes INTEGER [],
seen_pantry_items INTEGER [],
seen_groups INTEGER[],
seen_shopping_lists INTEGER [],
seen_recipes INTEGER [],
flags JSONB
);

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS test2_logistics_info(
id SERIAL PRIMARY KEY,
barcode VARCHAR(255) NOT NULL,
primary_location VARCHAR(64),
auto_issue_location VARCHAR(64),
dynamic_locations JSONB,
location_data JSONB,
quantity_on_hand FLOAT8 NOT NULL,
UNIQUE(barcode)
);

View File

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS test2_receipt_items (
id SERIAL PRIMARY KEY,
type VARCHAR(255) NOT NULL,
barcode VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
qty FLOAT8 NOT NULL,
data JSONB,
status VARCHAR (64)
);

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS test2_receipts (
id SERIAL PRIMARY KEY,
receipt_id INTEGER NOT NULL,
receipt_status VARCHAR (64) NOT NULL,
date_submitted TIMESTAMP NOT NULL,
submitted_by INTEGER NOT NULL,
vendor_id INTEGER,
files JSONB,
UNIQUE(receipt_id)
);

View File

@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS test2_recipes (
id SERIAL PRIMARY KEY,
name VARCHAR,
author INTEGER,
description TEXT,
creation_date TIMESTAMP,
custom_items JSONB,
pantry_items JSONB,
group_items JSONB,
instructions TEXT [],
picture_path TEXT
);

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS test2_shopping_lists (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
pantry_items INTEGER [],
custom_items JSONB,
recipes INTEGER [],
groups INTEGER [],
quantities JSONB,
author INTEGER,
creation_date TIMESTAMP,
type VARCHAR(64),
UNIQUE(name)
);

View File

@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS test2_Transactions (
id SERIAL PRIMARY KEY,
timestamp TIMESTAMP,
logistics_info_id INTEGER NOT NULL,
barcode VARCHAR(255) NOT NULL,
name VARCHAR(255),
transaction_type VARCHAR(255) NOT NULL,
quantity FLOAT8 NOT NULL,
description TEXT,
user_id INTEGER NOT NULL,
data JSONB,
CONSTRAINT fk_logistics_info
FOREIGN KEY(logistics_info_id)
REFERENCES test2_logistics_info(id)
);

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS test2_vendors (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
address VARCHAR(255),
creation_date TIMESTAMP NOT NULL,
created_by TIMESTAMP NOT NULL,
phone_number VARCHAR(32)
);

View File

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS test2_zones(
id SERIAL PRIMARY KEY,
name VARCHAR(32) NOT NULL,
UNIQUE(name)
);

View File

@ -0,0 +1 @@
DROP TABLE test2_brands CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_food_info CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_groups CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_item_info CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_items CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_itemlinks CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_locations CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_logistics_info CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_receipt_items CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_receipts CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_recipes CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_shopping_lists CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_transactions CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_vendors CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE test2_zones CASCADE;

View File

@ -0,0 +1,45 @@
SELECT * FROM test2_items
LEFT JOIN test2_logistics_info ON test2_items.logistics_info_id = test2_logistics_info.id
LEFT JOIN test2_item_info ON test2_items.item_info_id = test2_item_info.id
LEFT JOIN test2_food_info ON test2_items.food_info_id = test2_food_info.id
WHERE test2_items.id=%s;
/*
00 - item_id
01 - barcode
02 - item_name
03 - brand (id)
04 - description
05 - tags
06 - links
07 - item_info_id
08 - logistics_info_id
09 - food_info_id
10 - row_type
11 - item_type
12 - search_string
13 - logistics_info_id
14 - barcode
15 - primary_location
16 - auto_issue_location
17 - dynamic_locations
18 - location_data
19 - quantity_on_hand
20 - item_info_id
21 - barcode
22 - linked_items
23 - shopping_lists
24 - recipes
25 - groups
26 - packaging
27 - uom
28 - cost
29 - safety_stock
30 - lead_time_days
31 - ai_pick
32 - food_info_id
33 - food_groups
34 - ingrediants
35 - nutrients
36 - expires
*/

45
static/itemHandler.js Normal file
View File

@ -0,0 +1,45 @@
async function addLink(){
event.preventDefault()
let key = document.getElementById('link_name').value;
let link = document.getElementById('link').value;
links[key] = link;
console.log(links)
await propagateLinks()
};
function updatePackaging(){
let packaging = document.getElementById('packaging').value;
item_info['packaging'] = packaging;
console.log(item_info)
};
function updateUOM(){
let uom = document.getElementById('uom').value;
item_info['uom'] = uom;
console.log(item_info)
};
function updateCost(){
let cost = document.getElementById('cost').value;
item_info['cost'] = parseFloat(cost);
console.log(item_info)
};
function updateSafetyStock(){
let safety_stock = document.getElementById('safety_stock').value;
item_info['safety_stock'] = parseFloat(safety_stock);
console.log(item_info)
};
function updateLeadTimeDays(){
let lead_time_days = document.getElementById('lead_time_days').value;
item_info['lead_time_days'] = parseFloat(lead_time_days);
console.log(item_info)
};
function updateAiPickable(){
let ai_pick = document.getElementById('ai_pickable');
item_info['ai_pick'] = ai_pick.checked;
console.log(item_info)
};

352
templates/groups/group.html Normal file
View File

@ -0,0 +1,352 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry - Groups</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<!-- 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 href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<style>
header, main, footer, body {
padding-left: 300px;
}
@media only screen and (max-width : 992px) {
header, main, footer, body {
padding-left: 0;
}
}
.dropdown-disabled {
pointer-events: none;
opacity: 0.5; /* or your desired degree of transparency */
}
</style>
<ul id='dropdown1' class='dropdown-content'>
{% for site in sites %}
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
{% endfor %}
</ul>
<ul id="slide-out" class="sidenav sidenav-fixed purple lighten-4" style="width: 250px;">
<li>
<div class="user-view">
<!-- <div class="background">
<img src="images/office.jpg">
</div> -->
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
<a href="#name"><span class="black-text name">John Doe</span></a>
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
</div>
</li>
<li><a class="dropdown-trigger dropdown-disabled" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
<li><a href="/items">Site Items</a></li>
<li><a href="/groups">Site Groups</a></li>
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
</ul>
<body>
<div class="container">
<div class="section">
<div class="row">
<div class="col s12">
<div class="row">
<div class="col s2">
<h3><a href="#" data-target="slide-out" class="sidenav-trigger hide-on-large-only left"><i class="material-icons">menu</i></a></h3>
</div>
<div class="col s8 purple lighten-4" style="border-radius: 10px;">
<h3 id="group_name" class="center"></h3>
</div>
<div class="col s2">
</div>
</div>
</div>
<div class="col s12 p-2">
<h5 id="database_id"></h5>
</div>
<div class="col s12 m6 p-2">
<label for="group_type">Group Type</label>
<select id="group_type" class="browser-default">
<option value="plain">Plain</option>
<option value="calculated">Calculated</option>
</select>
</div>
<div class="input-field col s12 p-2">
<textarea id="description" class="materialize-textarea" placeholder=" "></textarea>
<label for="description">Group Description</label>
</div>
<div class="divider col s12"></div>
<div class="col s3 p-2">
<button class="btn filled icon-left modal-trigger purple lighten-4 black-text z-depth-0" data-target="item_modal">Add Item</button>
</div>
<div id="table_div" class="col s12 p-2">
</div>
</div>
</div>
</div>
<div class="fixed-action-btn">
<button class="btn-floating btn-large" onclick="saveGroup()">
<i class="large material-icons">save</i>
</button>
</div>
<div id="item_modal" class="modal">
<div class="modal-content">
<div class="row" style="gap: 5px;">
<div class="col s12">
<h4>Add Items...</h4>
</div>
<div class="col s12">
<div class="card-panel purple lighten-4 z-depth-0">
<span class="black-text"> Here is where you can search, add, and remove items from this group by checking or unchecking the items below. You can select
multiple at a time or simply one. Utilize the search bar to filter down quickly to items you need or simply scroll to your hearts content.
<br><br>
<b>WARNING:</b> clicking the checkbox will not save the changes right off the bat! You <b>MUST</b> click the Update Items button!
<br><br>
</span>
</div>
</div>
<div class="col s9 m6 offset-m3 input-field outlined align-center">
<i class="material-icons prefix">search</i>
<input style="border-radius: 20px; border: 1px solid #ccc;" id="search" name="search" type="search" placeholder="Search Items" value="">
</div>
<div class="col s12 center">
<a id="back" class="btn icon-left purple lighten-4 black-text z-depth-0"><i class="material-icons">chevron_left</i></a>
<a id="update_items" class="btn purple lighten-4 black-text z-depth-0">Update Items</a>
<a id="forward" class="btn icon-right purple lighten-4 black-text z-depth-0"><i class="material-icons">chevron_right</i></a>
</div>
<div class="divider col s12"></div>
<div id="checkbox-container" class="col s12">
</div>
</div>
</div>
</div>
</body>
<script>
const group_id = {{id|tojson}}
let site = {{ current_site|tojson }}
let group;
let current_page = 1
let limit = 25
let checked_items = new Array();
let search_text = "";
document.addEventListener("DOMContentLoaded", async function(){
await fetchGroup()
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {});
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, {});
var elems = document.querySelectorAll('.fixed-action-btn');
var instances = M.FloatingActionButton.init(elems, {});
var elems = document.querySelectorAll('.sidenav');
var instances = M.Sidenav.init(elems, {});
var elems = document.querySelectorAll('.dropdown-trigger');
var instances = M.Dropdown.init(elems, {});
var elems = document.querySelectorAll('.collapsible');
var instances = M.Collapsible.init(elems, {});
M.AutoInit();
await populateInfo()
await populateReferences(group[3])
update_list()
})
document.getElementById("search").addEventListener('keydown', function(event){
if(event.key === 'Enter'){
console.log(document.getElementById("search").value);
search_text = document.getElementById("search").value;
update_list()
}
})
async function fetchGroup(){
const url = new URL('/getGroup', window.location.origin);
url.searchParams.append('id', group_id);
const response = await fetch(url);
data = await response.json();
group = data.group;
}
async function populateInfo(){
var itemName = document.getElementById('group_name');
var databaseID = document.getElementById('database_id');
var selectElement = document.getElementById('group_type');
var description = document.getElementById('description');
itemName.innerHTML = group[1];
databaseID.innerHTML = `Database ID: ${group[0]}`;
selectElement.value = group[4];
selectElement.dispatchEvent(new Event('change'));
description.value = group[2];
M.Forms.textareaAutoResize(description);
checked_items = [];
for(let i=0; i < group[3].length; i++){
checked_items.push(group[3][i][0]);
}
}
async function populateReferences(references){
var table_div = document.getElementById('table_div')
table_div.innerHTML = ""
var references_table = document.createElement('table')
references_table.classList.add("striped")
var tbl_header = document.createElement('thead')
tbl_header.innerHTML = `<tr><th>Database ID</th><th>Barcode</th><th>Item Name</th><th>QOH</th></tr>`
var tbl_body = document.createElement('tbody')
for (let i=0; i < references.length; i++){
var row = document.createElement('tr')
row.innerHTML = `
<td>${references[i][0]}</td>
<td>${references[i][1]}</td>
<td>${references[i][2]}</td>
<td>${references[i][3]}</td>
`
tbl_body.appendChild(row)
};
references_table.appendChild(tbl_header)
references_table.appendChild(tbl_body)
table_div.appendChild(references_table)
}
function save_checked(){
let checkboxes = document.querySelectorAll('.checkbox-class');
checkboxes.forEach((checkbox) => {
if (checked_items.includes(Number(checkbox.id)) && !checkbox.checked){
checked_items.splice(checked_items.indexOf(Number(Number(checkbox.id))), 1);
}
if (!checked_items.includes(Number(checkbox.id)) && checkbox.checked){
checked_items.push(Number(checkbox.id))
}
});
}
function update_list(){
if (current_page == 1){
document.getElementById('back').classList.add("disabled")
}else{
document.getElementById('back').classList.remove("disabled")
};
const url = new URL('/getItems', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', limit);
url.searchParams.append('search_text', search_text);
let container = document.getElementById('checkbox-container')
fetch(url)
.then(response => response.json())
.then(data => {
if (data.items.length < limit){
document.getElementById('forward').classList.add("disabled")
} else {
document.getElementById('forward').classList.remove("disabled")
}
const divi = document.createElement('div')
data.items.forEach(item => {
const checkboxHTML = document.createElement('p')
if (checked_items.includes(item[0])){
checkboxHTML.innerHTML = `<label>
<input class="checkbox-class" id=${item[0]} name=${item[0]} checked="checked" type="checkbox" />
<span>${item[1]} - ${item[2]}</span>
</label>`;
} else {
checkboxHTML.innerHTML = `<div class="col s12"><label>
<input class="checkbox-class" id=${item[0]} name=${item[0]} type="checkbox" />
<span>${item[1]} - ${item[2]}</span>
</label></div>`;
}
divi.appendChild(checkboxHTML)
})
container.innerHTML = divi.innerHTML
})
}
async function saveGroup(){
save_checked()
var itemName = document.getElementById('group_name');
var selectElement = document.getElementById('group_type');
var description = document.getElementById('description');
await fetch(`/updateGroup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: group[0],
name: itemName.innerHTML,
description:description.value,
items: checked_items,
group_type:selectElement.value,
}),
});
M.toast({text: "Group Saved!"})
}
document.getElementById('forward').addEventListener('click', () =>{
current_page++
save_checked()
update_list()
})
document.getElementById('back').addEventListener('click', () =>{
current_page--
save_checked()
update_list()
})
document.getElementById('update_items').addEventListener('click', async function(){
save_checked()
var itemName = document.getElementById('group_name');
var selectElement = document.getElementById('group_type');
var description = document.getElementById('description');
await fetch(`/updateGroup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: group[0],
name: itemName.innerHTML,
description:description.value,
items: checked_items,
group_type:selectElement.value,
}),
})
var modalElem = document.querySelector('#item_modal');
var instance = M.Modal.getInstance(modalElem);
instance.close();
M.toast({text: "Group Saved!"})
await fetchGroup()
await populateInfo()
await populateReferences(group[3])
})
</script>
</html>

448
templates/groups/index.html Normal file
View File

@ -0,0 +1,448 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry - Groups</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<!-- 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 href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<style>
.hand-pointer {
cursor: pointer;
}
[type="radio"]:checked + span:after {
border: 2px solid rgb(177 156 217 / 30%); /* Outline color */
background-color: rgb(177 156 217 / 30%); /* Fill color */
}
header, main, footer, body {
padding-left: 300px;
}
@media only screen and (max-width : 992px) {
header, main, footer, body {
padding-left: 0;
}
}
.dropdown-disabled {
pointer-events: none;
opacity: 0.5; /* or your desired degree of transparency */
}
</style>
<!-- NAVBAR -->
<ul id='dropdown1' class='dropdown-content'>
{% for site in sites %}
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
{% endfor %}
</ul>
<ul id="slide-out" class="sidenav sidenav-fixed purple lighten-4" style="width: 250px;">
<li>
<div class="user-view">
<!-- <div class="background">
<img src="images/office.jpg">
</div> -->
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
<a href="#name"><span class="black-text name">John Doe</span></a>
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
</div>
</li>
<li><a class="dropdown-trigger" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
<li><a href="/items">Site Items</a></li>
<li><a href="/groups">Site Groups</a></li>
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
</ul>
<body>
<div class="container section" style="padding-bottom: 72px;">
<div class="row">
<!-- menu button, search bar, filters button -->
<div class="row col s12">
<div class="col-s3">
<a href="#" data-target="slide-out" class="sidenav-trigger hide-on-large-only left"><i class="material-icons">menu</i></a>
</div>
<div class="col s6 m6 offset-m3 input-field outlined align-center">
<i class="material-icons prefix">search</i>
<input style="border-radius: 20px; border: 1px solid #ccc;" id="search" name="search" type="search" placeholder="Search Items" value="">
</div>
<div class="col s3">
<a class="btn waves-effect waves-light center-align right tooltipped purple lighten-4 black-text text-darken-2 z-depth-0" data-position="bottom" data-tooltip="Open up filter options." style="margin-right: 5px; margin-top:0px; border-radius: 20px 10px 20px 10px;" onclick="hideFilters()"><i class="material-icons">tune</i></a>
</div>
</div>
<div class="col s12 hide" id="filter_options" style="padding: 20px">
<!-- Set the number of items -->
<div class="row center">
<div class="col s12">
<p>Set Items Per Page</p>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" checked onclick="changeLimit(25)"/>
<span>25 items</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(50)"/>
<span>50 items</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(75)"/>
<span>75 itesm</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(100)"/>
<span>100 items</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(150)"/>
<span>150 items</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(200)"/>
<span>200 items</span>
</label>
</div>
<div class="col s12 divider"></div>
</div>
</div>
<div class="col s12 z-depth-0">
<div class="z-depth-0" id="cards">
</div>
</div>
<div class="col s12 center" id="pagination_list">
<ul class="pagination">
<li id="first" class="waves-effect hand-pointer"><a class="purple lighten-4" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px;"><i class="material-icons">first_page</i></a></li>
<li id="back" class="waves-effect hand-pointer" ><a class="purple lighten-4" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px;"><i class="material-icons">chevron_left</i></a></li>
<li id="current_page" style="padding-top: 7px; padding-left: 5px; padding-right: 5px; font-size: 18px;">page_number</li>
<li id="forward" class="waves-effect hand-pointer"><a class="purple lighten-4" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px;"><i class="material-icons">chevron_right</i></a></li>
<li id="last" class="waves-effect hand-pointer"><a class="purple lighten-4" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px;"><i class="material-icons">last_page</i></a></li>
</ul>
</div>
</div>
</div>
<div class="fixed-action-btn">
<a class="btn-floating btn-large">
<i class="large material-icons">more_vert</i>
</a>
<ul>
<li><a class="btn-floating blue darken-1 modal-trigger" href="#modal1"><i class="material-icons">playlist_add</i></a></li>
<li><a class="btn-floating green darken-1"><i class="material-icons">download</i></a></li>
</ul>
</div>
<div id="modal1" class="modal">
<div class="modal-content">
<h4>Adding a Group...</h4>
<div class="row">
<div class="col s12">
<div class="card-panel purple lighten-4">
<span>Groups are internal "grouping" of items that can be of the same type, tag, process, etc. The point of them is that you
can keep track of all of one thing in a list for quick reference. There is three pieces of key information needed in order to
create a group; a name, a type, and a basic description.
<br><br>There are two types of groups you can create:
<br><br>
<b>Plain</b> - This group has no other extra features beyond being a place to keep a bunch of items categorized.
<br><br>
<b>Calculated</b> - This group will act a a substitutive group. The QOH of each item will be calculated together for you and when added to a
recipe the group will act as if the items can be used in place of each other.
<br><br>
</span>
</div>
</div>
<div class="col s12 m6 input-field p-2">
<input id="group_name" type="text" placeholder=" " maxlength="64">
<label for="group_name">Group Name</label>
</div>
<div class="input-field col s12 m6 p-2">
<select id="group_type">
<option value="plain" selected>Plain Group</option>
<option value="calculated">Calculated</option>
</select>
<label for="group_type">Group Type</label>
</div>
<div class="input-field col s12 p-2">
<textarea id="group_description" class="materialize-textarea" placeholder="A short description for what this group represents..."></textarea>
<label for="group_description">Group Description</label>
</div>
</div>
</div>
<div class="modal-footer">
<a onclick="addGroup()" class="waves-effect btn">Add</a>
</div>
</div>
</body>
<script>
let current_page = 1
let end_page = 10
let limit = 50
let filter_state = "hidden"
let site = {{ current_site|tojson }}
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.collapsible');
var instances = M.Collapsible.init(elems, {
// specify options here
});
});
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.fixed-action-btn');
var instances = M.FloatingActionButton.init(elems, {
// specify options here
});
});
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.dropdown-trigger');
var instances = M.Dropdown.init(elems, {
alignment: 'right',
});
});
document.addEventListener('DOMContentLoaded', function() {
update_list()
});
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, {
// specify options here
});
M.AutoInit();
});
async function changeSite(new_site){
console.log(`current_site: ${site}`)
console.log(`new site: ${new_site}`)
site = new_site
console.log(`current_site: ${site}`)
const url = new URL('/changeSite', window.location.origin);
url.searchParams.append('site', site)
await fetch(url)
location.reload();
}
function update_list(){
if (current_page === 1){
document.getElementById('back').classList.add("disabled")
document.getElementById('back').classList.remove("waves-effect")
document.getElementById('first').classList.add("disabled")
document.getElementById('first').classList.remove("waves-effect")
} else {
document.getElementById('back').classList.remove("disabled")
document.getElementById('back').classList.add("waves-effect")
document.getElementById('first').classList.remove("disabled")
document.getElementById('first').classList.add("waves-effect")
};
const url = new URL('/getGroups', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', limit);
fetch(url)
.then(response => response.json())
.then(data => {
end_page = parseInt(data.end)
if (current_page === end_page){
document.getElementById('forward').classList.add("disabled")
document.getElementById('forward').classList.remove("waves-effect")
document.getElementById('last').classList.add("disabled")
document.getElementById('last').classList.remove("waves-effect")
} else {
document.getElementById('forward').classList.remove("disabled")
document.getElementById('forward').classList.add("waves-effect")
document.getElementById('last').classList.remove("disabled")
document.getElementById('last').classList.add("waves-effect")
};
const cards = document.getElementById("cards")
const dummy_div = document.createElement('div')
const list_collapsible = document.createElement('ul')
list_collapsible.classList.add("collapsible")
list_collapsible.classList.add("popout")
list_collapsible.style = "background: white;"
data.groups.forEach(item => {
let qty = item[5]
let roundedQty = qty.toFixed(2)
var list_item = document.createElement('li')
list_item.classList.add("z-depth-0")
list_item.style = "background: white;"
var header = document.createElement("div")
header.classList.add("collapsible-header")
header.style = "box-shadow: none !important; background: white; border: 1px solid rgb(150 150 150 / 30%); border-radius: 10px 10px 0px 0px; align-items: center;"
if(item[4] == 'calculated'){
header.innerHTML = `<i class='material-symbols-outlined'>fastfood</i>${item[1]}
<span class="badge purple lighten-4 black-text text-darken-2 z-depth-0" style="border-radius: 5px;">QOH: ${roundedQty}</span>`
} else {
header.innerHTML = `<i class='material-symbols-outlined'>fastfood</i>${item[1]}`
}
var body = document.createElement('div')
body.classList.add("collapsible-body")
body.style = "box-shadow: none !important; background: rgb(177 156 217 / 10%); border: 1px solid rgb(177 156 217 / 30%); border-radius: 0px 0px 10px 10px;"
var description = document.createElement('div')
description.classList.add("col")
description.classList.add("s12")
var span_desc = document.createElement('span')
span_desc.innerHTML = `<span>${item[2]}</span><div class="divider s12" style="margin-bottom: 5px; margin-top: 5px;"></div>`
description.appendChild(span_desc)
var items_table = document.createElement('table')
items_table.classList.add("striped")
items_table.classList.add("hide-on-small-only")
var table_header = document.createElement('thead')
table_header.innerHTML = `<th>Barcode</th><th>Item Name</th><th>QOH</th>`
var table_body = document.createElement('tbody')
console.log(item[3])
let items = item[3]
for (let i=0; i < items.length; i++){
console.log(items[i])
var table_row = document.createElement('tr')
table_row.innerHTML = `
<td>${items[i][0]}</td>
<td>${items[i][1]}</td>
<td>${items[i][2]}</td>
`
table_body.appendChild(table_row)
}
items_table.append(table_header)
items_table.append(table_body)
description.appendChild(items_table)
var button_group = document.createElement('div')
button_group.classList.add("row")
button_group.style = "margin-bottom: 0px; padding-bottom: 0px;"
button_group.innerHTML = `
<div class="col s12" style="align-items: center; padding-top:10px;">
<a class="btn right purple lighten-4 black-text text-darken-2 z-depth-0 tooltipped" data-position="bottom" data-tooltip="Edit Group" style="display: inline-flex; border-radius: 20px 10px 20px 10px;" href="/group/${item[0]}">
<i class='material-icons'>edit</i>
</a>
<a class="btn right purple lighten-4 black-text text-darken-2 z-depth-0 tooltipped" data-position="left" data-tooltip="Transactions" style="display: inline-flex; margin-right: 5px; margin-top:0px; border-radius: 20px 10px 20px 10px;" disabled>
<i class='material-icons'>list_alt</i>
</a>
</div>`
body.appendChild(description)
body.appendChild(button_group)
list_item.appendChild(header)
list_item.appendChild(body)
list_collapsible.appendChild(list_item)
});
dummy_div.appendChild(list_collapsible)
cards.innerHTML = dummy_div.innerHTML
var elems = document.querySelectorAll('.collapsible');
var instances = M.Collapsible.init(elems, {
// specify options here
});
var elems = document.querySelectorAll('.tooltipped');
var instances = M.Tooltip.init(elems, {
// specify options here
});
document.getElementById("current_page").innerHTML = `${String(current_page)} / ${String(end_page)}`
})
};
function addGroup(){
var name = document.getElementById("group_name").value
var description = document.getElementById("group_description").value
var type = document.getElementById("group_type").value
const url = new URL('/addGroup', window.location.origin);
url.searchParams.append('name', name);
url.searchParams.append('description', description);
url.searchParams.append('type', type);
fetch(url)
.then(response => response.json())
.then(data => {
M.toast({text: `Adding ${name}: ${data.state}`});
})
update_list()
var elem = document.getElementById("modal1");
var instance = M.Modal.getInstance(elem);
instance.close()
};
function changeLimit(limit_value){
limit = limit_value
current_page = 1
update_list()
};
function hideFilters(){
if (filter_state == "hidden"){
document.getElementById("filter_options").classList.remove("hide");
filter_state = "shown";
} else {
document.getElementById("filter_options").classList.add("hide");
filter_state = "hidden";
}
}
document.getElementById('forward').addEventListener('click', () =>{
if (!(document.getElementById("forward").classList.contains("disabled"))){
current_page++
update_list();
};
});
document.getElementById('back').addEventListener('click', () =>{
if (!(document.getElementById("back").classList.contains("disabled"))){
current_page--
update_list();
}
});
document.getElementById('last').addEventListener('click', () =>{
if(!(document.getElementById("last").classList.contains("disabled"))){
current_page = end_page
update_list();
};
});
document.getElementById('first').addEventListener('click', () =>{
if (!(document.getElementById("first").classList.contains("disabled"))){
current_page = 1
update_list();
};
});
</script>
</html>

View File

@ -1,138 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<body>
<div class="container section">
<div class="row g-4">
<div class="col s12">
<h3>{{item[2]}}</h3>
<h5>Database ID: {{item[0]}}</h5>
<h5>Barcode: {{item[1]}}</h5>
</div>
<div class="s12 m6" style="margin-right: 5px;">
<label for="entry_type">Entry Type</label>
<select id="entry_type" class="browser-default" >
<option value="" disabled selected>Choose your option</option>
<option value="item">item</option>
<option value="linked list">linked list</option>
</select>
</div>
<div class="s12 m6">
<label for="item_type">Item Type</label>
<select id="item_type" class="browser-default">
<option value="" disabled selected>Choose your option</option>
<option value="FOOD">FOOD</option>
<option value="FOOD (PLU)">FOOD (PLU)</option>
<option value="OTHER">OTHER</option>
</select>
</div>
<!-- Weblinks Input perhaps a modal to add a link or a text input..?-->
<div class="divider col s12" style="margin-top: 5px;"></div>
<div class="col s12">
<p style="font-weight: bold; font-size: 16px; margin-top: 5px;">Links</p>
<div id="weblinks">
</div>
</div>
<div class="divider col s12" style="margin-top: 5px;"></div>
<div class="col s12">
<div class="row">
<div class="col s12">
<ul class="tabs tabs-fixed-width" id="info_tabs" style="background-color: white;">
<li class="tab col s3"><a class="active" href="#item_info">Item Info</a></li>
<li class="tab col s3"><a href="#food_info">Food Info</a></li>
<li class="tab col s3"><a href="#logistics_info">Logistics Info</a></li>
<li class="tab col s3 disabled"><a href="#linked_items">Linked Items</a></li>
</ul>
</div>
<div id="item_info" class="col s12">
<table class="" id="reference_table">
<thead>
<tr>
<th>Reference Type</th>
<th>Reference Name</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div id="food_info" class="col s12">Food Info</div>
<div id="logistics_info" class="col s12">Logistics Info</div>
<div id="linked_items" class="col s12 disabled">Linked Items</div>
</div>
</div>
</div>
</div>
</body>
<script>
const item = {{ item|tojson }}
var reference_state = 1
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("title").innerHTML = String(item[2])
var elemsSelects = document.querySelectorAll('select');
var instancesSelects = M.FormSelect.init(elemsSelects, {});
var el = document.querySelector('.tabs');
var instance = M.Tabs.init(el, {});
setEntryType()
setItemType()
populateLinks(item[5])
populateReferences(item[22], 'shopping_list')
populateReferences(item[23], 'recipe')
populateReferences(item[24], 'group')
});
function setEntryType(){
const selectElement = document.getElementById('entry_type');
selectElement.value = item[9];
}
function setItemType(){
const selectElement = document.getElementById('item_type');
selectElement.value = item[10];
}
function populateLinks(links){
var element = document.getElementById("weblinks");
for (let key in links){
var link = document.createElement("a");
link.classList.add("btn-small");
link.classList.add("outlined");
link.target = "_blank";
link.style = "border-radius: 20px; margin-right: 5px;";
link.innerHTML = String(key);;
link.href = links[key];
element.appendChild(link);
};
};
function populateReferences(references, reference_type){
var table = document.getElementById("reference_table")
for (let i = 0; i < references.length; i++){
var row = table.insertRow();
var row_type = row.insertCell();
var row_name = row.insertCell();
row_type.innerHTML = reference_type
row_name.innerHTML = String(references[i])
if ((reference_state % 2) == 0){
row.style = "background-color: gainsboro;"
}
reference_state++
}
}
</script>
</html>

View File

@ -2,7 +2,7 @@
<html lang="en" dir="ltr"> <html lang="en" dir="ltr">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry</title> <title>My Pantry - Items</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<!-- Material Icons --> <!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
@ -22,18 +22,58 @@
border: 2px solid rgb(0 128 0 / 30%); /* Outline color */ border: 2px solid rgb(0 128 0 / 30%); /* Outline color */
background-color: rgb(0 128 0 / 30%); /* Fill color */ background-color: rgb(0 128 0 / 30%); /* Fill color */
} }
</style> header, main, footer, body {
padding-left: 300px;
}
@media only screen and (max-width : 992px) {
header, main, footer, body {
padding-left: 0;
}
}
.dropdown-disabled {
pointer-events: none;
opacity: 0.5; /* or your desired degree of transparency */
}
</style>
<ul id='dropdown1' class='dropdown-content'>
{% for site in sites %}
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
{% endfor %}
</ul>
<ul id="slide-out" class="sidenav sidenav-fixed green lighten-4" style="width: 250px;">
<li>
<div class="user-view">
<!-- <div class="background">
<img src="images/office.jpg">
</div> -->
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
<a href="#name"><span class="black-text name">John Doe</span></a>
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
</div>
</li>
<li><a class="dropdown-trigger" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
<li class="active"><a href="/items">Site Items</a></li>
<li><a href="/groups">Site Groups</a></li>
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
</ul>
<body> <body>
<div class="container section" style="padding-bottom: 72px;"> <div class="container section" style="padding-bottom: 72px;">
<div class="row"> <div class="row">
<div class="col s9 m6 offset-m3 input-field outlined align-center"> <div class="row col s12">
<div class="col-s3">
<a href="#" data-target="slide-out" class="sidenav-trigger hide-on-large-only left"><i class="material-icons">menu</i></a>
</div>
<div class="col s6 m6 offset-m3 input-field outlined align-center">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
<input style="border-radius: 20px; border: 1px solid #ccc;" id="search" name="search" type="search" placeholder="Search" value=""> <input style="border-radius: 20px; border: 1px solid #ccc;" id="search" name="search" type="search" placeholder="Search Items" value="">
</div> </div>
<div class="col s3"> <div class="col s3">
<a class="btn waves-effect waves-light center-align right tooltipped green lighten-3 black-text text-darken-2 z-depth-0" data-position="bottom" data-tooltip="Open up filter options." style="margin-right: 5px; margin-top:0px; border-radius: 20px 10px 20px 10px;" onclick="hideFilters()"><i class="material-icons">tune</i></a> <a class="btn waves-effect waves-light center-align right tooltipped green lighten-3 black-text text-darken-2 z-depth-0" data-position="bottom" data-tooltip="Open up filter options." style="margin-right: 5px; margin-top:0px; border-radius: 20px 10px 20px 10px;" onclick="hideFilters()"><i class="material-icons">tune</i></a>
</div> </div>
</div>
<div class="col s12 hide" id="filter_options" style="padding: 20px"> <div class="col s12 hide" id="filter_options" style="padding: 20px">
<!-- This is for basic views --> <!-- This is for basic views -->
<div class="row center"> <div class="row center">
@ -146,10 +186,64 @@
<i class="large material-icons">more_vert</i> <i class="large material-icons">more_vert</i>
</a> </a>
<ul> <ul>
<li><a class="btn-floating blue darken-1"><i class="material-icons">playlist_add</i></a></li> <li><a class="btn-floating blue darken-1 modal-trigger" href="#modal1"><i class="material-icons">playlist_add</i></a></li>
<li><a class="btn-floating green darken-1"><i class="material-icons">download</i></a></li> <li><a class="btn-floating green darken-1"><i class="material-icons">download</i></a></li>
</ul> </ul>
</div> </div>
<div id="modal1" class="modal">
<div class="modal-content">
<h4>Adding a Item...</h4>
<div class="row">
<div class="col s12">
<div class="card-panel green lighten-4">
<span>Adding an item is the main feature of your inventory. Items are used to store information and the most pertinant variables
around the items in your system and act as the foundation of all other aspects and features of this system. To create an Item
the 4 most important fields you need is the barcode (this is the unique number the item is tracked under), a name, an item type,
and finally a subtype.
<br><br>There are two item types of items you can create:
<br><br>
<b>Single Item</b> - These are the bare bone item as you might think it to be. Its a barcode that gets moved about your system and tracked by that
barcode.
<br><br>
<b>Linked Item</b> - Linked Items are a more advanced feature that allows you to chain together barcodes into one. Whenever one of those barcodes
are transacted on it is instead replaced with its linked barcode. This allows you to chain together the same types of items together if you want.
I.E. you have multiple barcodes for Chicken Noodle Soup and want them all to be the same item instead of seperate items in your system.
<br><br>
</span>
</div>
</div>
<div class="col s12 m6 input-field p-2">
<input id="barcode" type="text" placeholder=" " maxlength="64">
<label for="barcode">Barcode</label>
</div>
<div class="col s12 m6 input-field p-2">
<input id="item_name" type="text" placeholder=" " maxlength="64">
<label for="item_name">Item Name</label>
</div>
<div class="input-field col s12 m6 p-2">
<select id="item_type">
<option value="single" selected>Single Item</option>
<option value="linked">Linked Item</option>
</select>
<label for="item_type">Item Type</label>
</div>
<div class="input-field col s12 m6 p-2">
<select id="sub_type">
<option value="FOOD" selected>Food</option>
<option value="OTHER">Other</option>
</select>
<label for="sub_type">Sub Type</label>
</div>
<div class="input-field col s12 p-2">
<textarea id="item_description" class="materialize-textarea" placeholder="A short description for what this group represents..."></textarea>
<label for="item_description">Item Description</label>
</div>
</div>
</div>
<div class="modal-footer">
<a onclick="addItem()" class="waves-effect btn green lighten-4">Add</a>
</div>
</div>
</body> </body>
<script> <script>
@ -160,6 +254,8 @@
let limit = 50 let limit = 50
let filter_state = "hidden" let filter_state = "hidden"
let searchText = "" let searchText = ""
let site = {{ current_site|tojson }}
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.collapsible'); var elems = document.querySelectorAll('.collapsible');
@ -174,9 +270,15 @@
// specify options here // specify options here
}); });
}); });
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
update_list() var elems = document.querySelectorAll('.dropdown-trigger');
var instances = M.Dropdown.init(elems, {
alignment: 'right',
});
});
document.addEventListener('DOMContentLoaded', async function() {
await update_list()
}); });
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
@ -187,8 +289,18 @@
M.AutoInit(); M.AutoInit();
}); });
async function changeSite(new_site){
console.log(`current_site: ${site}`)
console.log(`new site: ${new_site}`)
site = new_site
console.log(`current_site: ${site}`)
const url = new URL('/changeSite', window.location.origin);
url.searchParams.append('site', site)
await fetch(url)
location.reload();
}
function update_list(){ async function update_list(){
console.log(current_page) console.log(current_page)
if (current_page === 1){ if (current_page === 1){
document.getElementById('back').classList.add("disabled") document.getElementById('back').classList.add("disabled")
@ -212,7 +324,7 @@
fetch(url) await fetch(url)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -246,7 +358,7 @@
list_collapsible.style = "background: white;" list_collapsible.style = "background: white;"
data.items.forEach(item => { data.items.forEach(item => {
let qty = item[18] let qty = item[19]
let roundedQty = qty.toFixed(2) let roundedQty = qty.toFixed(2)
var list_item = document.createElement('li') var list_item = document.createElement('li')
list_item.classList.add("z-depth-0") list_item.classList.add("z-depth-0")
@ -273,7 +385,7 @@
button_group.style = "margin-bottom: 0px; padding-bottom: 0px;" button_group.style = "margin-bottom: 0px; padding-bottom: 0px;"
button_group.innerHTML = ` button_group.innerHTML = `
<div class="col s12" style="align-items: center;"> <div class="col s12" style="align-items: center;">
<a class="btn right green lighten-3 black-text text-darken-2 z-depth-0 tooltipped" data-position="bottom" data-tooltip="Edit Item" style="display: inline-flex; border-radius: 20px 10px 20px 10px;"> <a href="/getItem?id=${item[0]}"class="btn right green lighten-3 black-text text-darken-2 z-depth-0 tooltipped" data-position="bottom" data-tooltip="Edit Item" style="display: inline-flex; border-radius: 20px 10px 20px 10px;">
<i class='material-icons'>edit</i> <i class='material-icons'>edit</i>
</a> </a>
<a class="btn right green lighten-3 black-text text-darken-2 z-depth-0 tooltipped" data-position="left" data-tooltip="Transactions" style="display: inline-flex; margin-right: 5px; margin-top:0px; border-radius: 20px 10px 20px 10px;"> <a class="btn right green lighten-3 black-text text-darken-2 z-depth-0 tooltipped" data-position="left" data-tooltip="Transactions" style="display: inline-flex; margin-right: 5px; margin-top:0px; border-radius: 20px 10px 20px 10px;">
@ -309,20 +421,20 @@
}; };
function changeLimit(limit_value){ async function changeLimit(limit_value){
limit = limit_value limit = limit_value
current_page = 1 current_page = 1
update_list() await update_list()
}; };
function changeSort(order){ async function changeSort(order){
sort_order = order sort_order = order
update_list() await update_list()
} }
function changeView(view_num){ async function changeView(view_num){
view = view_num view = view_num
update_list() await update_list()
} }
function hideFilters(){ function hideFilters(){
@ -335,40 +447,68 @@
} }
} }
document.getElementById('forward').addEventListener('click', () =>{ document.getElementById('forward').addEventListener('click', async () =>{
if (!(document.getElementById("forward").classList.contains("disabled"))){ if (!(document.getElementById("forward").classList.contains("disabled"))){
current_page++ current_page++
update_list(); await update_list();
}; };
}); });
document.getElementById('back').addEventListener('click', () =>{ document.getElementById('back').addEventListener('click', async () =>{
if (!(document.getElementById("back").classList.contains("disabled"))){ if (!(document.getElementById("back").classList.contains("disabled"))){
current_page-- current_page--
update_list(); await update_list();
} }
}); });
document.getElementById('last').addEventListener('click', () =>{ document.getElementById('last').addEventListener('click', async () =>{
if(!(document.getElementById("last").classList.contains("disabled"))){ if(!(document.getElementById("last").classList.contains("disabled"))){
current_page = end_page current_page = end_page
update_list(); await update_list();
}; };
}); });
document.getElementById('first').addEventListener('click', () =>{ document.getElementById('first').addEventListener('click', async () =>{
if (!(document.getElementById("first").classList.contains("disabled"))){ if (!(document.getElementById("first").classList.contains("disabled"))){
current_page = 1 current_page = 1
update_list(); await update_list();
}; };
}); });
document.querySelector("#search").addEventListener('change', () =>{ document.querySelector("#search").addEventListener('change', async () => {
searchText = document.getElementById("search").value; searchText = document.getElementById("search").value;
current_page = 1 current_page = 1;
update_list() await update_list();
}); });
async function addItem(){
let barcode = document.getElementById("barcode")
let item_name = document.getElementById("item_name")
let item_type = document.getElementById("item_type")
let sub_type = document.getElementById("sub_type")
let description = document.getElementById("item_description")
const url = new URL('/addItem', window.location.origin);
url.searchParams.append('barcode', barcode.value);
url.searchParams.append('item_name', item_name.value);
url.searchParams.append('item_type', item_type.value);
url.searchParams.append('sub_type', sub_type.value);
url.searchParams.append('item_description', description.value);
await fetch(url)
.then(response => response.json())
.then(data => {
M.toast({text: `Adding ${name}: ${data.state}`});
})
await update_list()
var elem = document.getElementById("modal1");
var instance = M.Modal.getInstance(elem);
instance.close()
}
</script> </script>
</html> </html>

270
templates/items/item.html Normal file
View File

@ -0,0 +1,270 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<!-- 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 href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<style>
header, main, footer, body {
padding-left: 300px;
}
@media only screen and (max-width : 992px) {
header, main, footer, body {
padding-left: 0;
}
}
.dropdown-disabled {
pointer-events: none;
opacity: 0.5; /* or your desired degree of transparency */
}
</style>
<ul id='dropdown1' class='dropdown-content'>
{% for site in sites %}
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
{% endfor %}
</ul>
<ul id="slide-out" class="sidenav sidenav-fixed green lighten-4" style="width: 250px;">
<li>
<div class="user-view">
<!-- <div class="background">
<img src="images/office.jpg">
</div> -->
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
<a href="#name"><span class="black-text name">John Doe</span></a>
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
</div>
</li>
<li><a class="dropdown-trigger dropdown-disabled" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
<li><a href="/items">Site Items</a></li>
<li><a href="/groups">Site Groups</a></li>
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
</ul>
<body>
<div class="container section">
<div class="row g-4">
<div class="col s12">
<h3>{{item[2]}}</h3>
<h5>Database ID: {{item[0]}}</h5>
<h5>Barcode: {{item[1]}}</h5>
</div>
<div class="s12 m6" style="margin-right: 5px;">
<label for="entry_type">Entry Type</label>
<select id="entry_type" class="browser-default" >
<option value="" disabled selected>Choose your option</option>
<option value="single">item</option>
<option value="linked">linked list</option>
</select>
</div>
<div class="s12 m6">
<label for="item_type">Item Type</label>
<select id="item_type" class="browser-default">
<option value="" disabled selected>Choose your option</option>
<option value="FOOD">Food</option>
<option value="FOOD (PLU)">Food(PLU)</option>
<option value="OTHER">Other</option>
</select>
</div>
<!-- Weblinks Input perhaps a modal to add a link or a text input..?-->
<div class="divider col s12" style="margin-top: 5px;"></div>
<div class="col s12">
<div class="row">
<div class="col s6" style="padding-top: 10px;">
<span style="font-size: 16px;">Links</span>
</div>
<div class="col s6">
<button class="btn btn-small btn-flat right modal-trigger green lighten-4 black-text" data-target="web-modal" style="margin-top: 5px; padding-bottom: 10px;">Add Link</button>
</div>
<div class="col s12 p-3">
<div id="weblinks">
</div>
</div>
</div>
</div>
<div class="divider col s12" style="margin-top: 5px;"></div>
<div class="col s12">
<div class="row">
<div class="col s12">
<ul class="tabs tabs-fixed-width" id="info_tabs" style="background-color: white;">
<li class="tab col s3"><a class="active" href="#item_info">Item Info</a></li>
<li class="tab col s3"><a href="#food_info">Food Info</a></li>
<li class="tab col s3"><a href="#logistics_info">Logistics Info</a></li>
<li class="tab col s3 disabled"><a href="#linked_items">Linked Items</a></li>
</ul>
</div>
<div id="item_info" class="col s12">
<div class="row" style="gap: 10px; padding-top: 10px;">
<div class="col s6 m4 input-field outlined item_info_target">
<input onchange="updatePackaging()" id="packaging" type="text" placeholder=" " maxlength="32">
<label for="packaging">Packaging</label>
</div>
<div class="col s6 m4 input-field outlined item_info_target">
<input onchange="updateUOM()" id="uom" type="text" placeholder=" " maxlength="32">
<label for="uom">Unit of Measure</label>
</div>
<div class="col s6 m4 input-field outlined item_info_target">
<input onchange="updateCost()" id="cost" type="number" placeholder=" " maxlength="32">
<label for="cost">Cost</label>
</div>
<div class="col s6 m4 input-field outlined item_info_target">
<input onchange="updateSafetyStock()" id="safety_stock" type="number" placeholder=" " maxlength="32">
<label for="safety_stock">Safety Stock</label>
</div>
<div class="col s6 m4 input-field outlined item_info_target">
<input onchange="updateLeadTimeDays()" id="lead_time_days" type="number" placeholder=" " maxlength="32">
<label for="lead_time_days">Leadtime (Days)</label>
</div>
<div class="col s6 m4 center">
<p>
<label>
<input onclick="updateAiPickable()" id="ai_pickable" type="checkbox" />
<span>AI Pickable</span>
</label>
</p>
</div>
</div>
<table class="" id="reference_table">
<thead>
<tr>
<th>Reference Type</th>
<th>Reference Name</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div id="food_info" class="col s12">Food Info</div>
<div id="logistics_info" class="col s12">Logistics Info</div>
<div id="linked_items" class="col s12 disabled">Linked Items</div>
</div>
</div>
</div>
</div>
<div id="web-modal" class="modal">
<div class="modal-content">
<div class="row">
<div class="col s12">
<h4>Add Weblink to Item...</h4>
</div>
<div class="col s12">
<div class="card-panel green lighten-4">
<span>Add a link to your favorite sites! Provide a name for the link and the link itself
and it will show up in this list. You should always try to provide a link with the name "<b>main</b>", as this
will be used in recipes and shopping lists for where the item will lead to.
</span>
</div>
</div>
<div class="s12 m6 input-field">
<input id="link_name" type="text" placeholder="main" maxlength="20">
<label for="link_name">Link Name</label>
<!--<span class="supporting-text">Supporting Text</span>-->
</div>
<div class="input-field col s12">
<textarea id="link" class="materialize-textarea" placeholder="a weblink to a website..."></textarea>
<label for="link">Textarea with placeholder</label>
</div>
</div>
</div>
<div class="modal-footer">
<button onclick="addLink()" class="waves-effect green lighten-4 btn-flat">Add</button>
</div>
</div>
</body>
<script src="{{ url_for('static', filename='itemHandler.js') }}"></script>
<script>
const item = {{ item|tojson }}
var reference_state = 1
let links = {};
let updated = {};
let item_info = {};
let food_info = {};
let logistics_info = {};
document.addEventListener('DOMContentLoaded', async function() {
document.getElementById("title").innerHTML = String(item[2])
var elemsSelects = document.querySelectorAll('select');
var instancesSelects = M.FormSelect.init(elemsSelects, {});
var el = document.querySelector('.tabs');
var instance = M.Tabs.init(el, {});
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, {});
await propagateInfo()
populateReferences(item[23], 'shopping_list')
populateReferences(item[24], 'recipe')
populateReferences(item[25], 'group')
});
async function propagateInfo(){
const entryType = document.getElementById('entry_type');
entryType.value = item[10];
const itemType = document.getElementById('item_type');
itemType.value = item[11];
await propagateLinks()
}
async function propagateLinks(){
var element = document.getElementById("weblinks");
element.innerHTML = ""
for (let key in links){
var link = document.createElement("a");
link.classList.add("btn-small");
link.classList.add("btn-flat");
link.classList.add("green");
link.classList.add("lighten-4");
link.target = "_blank";
link.style = "border-radius: 20px; margin-right: 5px; vertical-align: middle;";
link.innerHTML = `<i class="material-icons">open_in_new</i>${String(key)}`;
link.href = links[key];
element.appendChild(link);
}
}
async function addLink(){
event.preventDefault()
let key = document.getElementById('link_name').value;
let link = document.getElementById('link').value;
links[key] = link;
console.log(links)
await propagateLinks()
}
function populateReferences(references, reference_type){
var table = document.getElementById("reference_table")
for (let i = 0; i < references.length; i++){
var row = table.insertRow();
var row_type = row.insertCell();
var row_name = row.insertCell();
row_type.innerHTML = reference_type
row_name.innerHTML = String(references[i][1])
if ((reference_state % 2) == 0){
row.style = "background-color: gainsboro;"
}
reference_state++
}
}
</script>
</html>

View File

@ -0,0 +1,525 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<!-- 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 href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<style>
header, main, footer, body {
padding-left: 300px;
}
@media only screen and (max-width : 992px) {
header, main, footer, body {
padding-left: 0;
}
}
.dropdown-disabled {
pointer-events: none;
opacity: 0.5; /* or your desired degree of transparency */
}
</style>
<ul id='dropdown1' class='dropdown-content'>
{% for site in sites %}
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
{% endfor %}
</ul>
<ul id="slide-out" class="sidenav sidenav-fixed orange lighten-4" style="width: 250px;">
<li>
<div class="user-view">
<!-- <div class="background">
<img src="images/office.jpg">
</div> -->
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
<a href="#name"><span class="black-text name">John Doe</span></a>
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
</div>
</li>
<li><a class="dropdown-trigger dropdown-disabled" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
<li><a href="/items">Site Items</a></li>
<li><a href="/groups">Site Groups</a></li>
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
</ul>
<body>
<div class="container">
<div class="section">
<div class="row">
<div class="col s12">
<div class="row">
<div class="col s2">
<h3><a href="#" data-target="slide-out" class="sidenav-trigger hide-on-large-only left"><i class="material-icons">menu</i></a></h3>
</div>
<div class="col s8 orange lighten-4" style="border-radius: 10px;">
<h3 id="list_name" class="center"></h3>
</div>
<div class="col s2">
</div>
</div>
</div>
<div class="col s12 p-2">
<h5 id="database_id"></h5>
</div>
<div class="col s12 m6 p-2">
<label for="list_type">List Type</label>
<select id="list_type" class="browser-default">
<option value="plain">Plain</option>
<option value="calculated">Calculated</option>
</select>
</div>
<div class="input-field col s12 p-2">
<textarea id="description" class="materialize-textarea" placeholder=" "></textarea>
<label for="description">Group Description</label>
</div>
<div class="divider col s12"></div>
<div class="col s12 p-2">
<button class="btn filled icon-left modal-trigger orange lighten-4 black-text z-depth-0" data-target="item_modal">Add Item</button>
<button class="btn filled icon-left modal-trigger orange lighten-4 black-text z-depth-0" data-target="custom_modal">Add Custom</button>
<button class="btn filled icon-left modal-trigger orange lighten-4 black-text z-depth-0 disabled" data-target="item_modal">Add Recipe</button>
<button class="btn filled icon-left modal-trigger orange lighten-4 black-text z-depth-0 disabled" data-target="item_modal">Add Group</button>
</div>
<div id="table_div" class="col s12 p-2">
</div>
</div>
</div>
</div>
<div class="fixed-action-btn">
<button class="btn-floating btn-large" onclick="saveList()">
<i class="large material-icons">save</i>
</button>
</div>
<div id="item_modal" class="modal">
<div class="modal-content">
<div class="row" style="gap: 5px;">
<div class="col s12">
<h4>Add Items...</h4>
</div>
<div class="col s12">
<div class="card-panel orange lighten-4 z-depth-0">
<span class="black-text"> Here is where you can search, add, and remove items from this list by checking or unchecking the items below. You can select
multiple at a time or simply one. Utilize the search bar to filter down quickly to items you need or simply scroll to your hearts content.
<br><br>
<b>WARNING:</b> clicking the checkbox will not save the changes right off the bat! You <b>MUST</b> click the Update Items button!
<br><br>
</span>
</div>
</div>
<div class="col s9 m6 offset-m3 input-field outlined align-center">
<i class="material-icons prefix">search</i>
<input style="border-radius: 20px; border: 1px solid #ccc;" id="search" name="search" type="search" placeholder="Search Items" value="">
</div>
<div class="col s12 center">
<a id="back" class="btn icon-left orange lighten-4 black-text z-depth-0"><i class="material-icons">chevron_left</i></a>
<a id="update_items" class="btn orange lighten-4 black-text z-depth-0">Update Items</a>
<a id="forward" class="btn icon-right orange lighten-4 black-text z-depth-0"><i class="material-icons">chevron_right</i></a>
</div>
<div class="divider col s12"></div>
<div id="checkbox-container" class="col s12">
</div>
</div>
</div>
</div>
<div id="custom_modal" class="modal">
<div class="modal-content">
<div class="row" style="gap: 5px;">
<div class="col s12">
<h4>Add Custom...</h4>
</div>
<div class="col s12">
<div class="card-panel orange lighten-4 z-depth-0">
<span class="black-text"> Here is where you can add custom items to the list.</span>
</div>
</div>
<div class="s12 m6 input-field outlined">
<input id="item_name" type="text" placeholder=" ">
<label for="item_name">Item Name</label>
</div>
<div class="s12 input-field outlined">
<input id="weblink" type="text" placeholder=" ">
<label for="weblink">Weblink</label>
</div>
</div>
</div>
<div class="modal-footer">
<button onclick="addCustom()" class="modal-close waves-effect btn-flat">Add</button>
</div>
</div>
</body>
<script>
const list_id = {{id|tojson}}
let shoppingList;
let current_page = 1
let limit = 25
let pantry_checked_items = new Array();
let groups_checked_items = new Array();
let search_text = "";
let custom_items = {};
let quantities = {};
document.addEventListener("DOMContentLoaded", async function(){
await fetchList()
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {});
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, {});
var elems = document.querySelectorAll('.fixed-action-btn');
var instances = M.FloatingActionButton.init(elems, {});
var elems = document.querySelectorAll('.sidenav');
var instances = M.Sidenav.init(elems, {});
var elems = document.querySelectorAll('.dropdown-trigger');
var instances = M.Dropdown.init(elems, {});
var elems = document.querySelectorAll('.collapsible');
var instances = M.Collapsible.init(elems, {});
M.AutoInit();
await populateInfo()
await populateReferences()
update_list()
});
async function fetchList(){
const url = new URL('/getList', window.location.origin);
url.searchParams.append('id', list_id);
const response = await fetch(url);
data = await response.json();
shoppingList = data.shopping_list;
};
async function populateInfo(){
var itemName = document.getElementById('list_name');
var databaseID = document.getElementById('database_id');
var selectElement = document.getElementById('list_type');
var description = document.getElementById('description');
itemName.innerHTML = shoppingList[1];
databaseID.innerHTML = `Database ID: ${shoppingList[0]}`;
selectElement.value = shoppingList[10];
selectElement.dispatchEvent(new Event('change'));
description.value = shoppingList[2];
M.Forms.textareaAutoResize(description);
pantry_checked_items = [];
for(let i=0; i < shoppingList[3].length; i++){
pantry_checked_items.push(shoppingList[3][i][0]);
}
custom_items = shoppingList[4];
if(shoppingList[7]){
quantities = shoppingList[7];
}
}
async function populateReferences(){
console.log(shoppingList[4])
var table_div = document.getElementById('table_div')
table_div.innerHTML = ""
var references_table = document.createElement('table')
references_table.classList.add("striped")
var tbl_header = document.createElement('thead')
tbl_header.innerHTML = `
<tr>
<th>Database ID</th>
<th>Barcode</th>
<th>Item Name</th>
<th>Qty/UOM</th>
<th>type</th>
</tr>`
var tbl_body = document.createElement('tbody')
let inventory_items = shoppingList[3];
for (let i=0; i < inventory_items.length; i++){
var row = document.createElement('tr')
let database_id_cell = document.createElement('td')
database_id_cell.innerHTML = `${inventory_items[i][0]}`
let barcode_cell = document.createElement('td')
barcode_cell.innerHTML = `${inventory_items[i][1]}`
let name_cell = document.createElement('td')
if (inventory_items[i][3]){
name_cell.innerHTML = `<a href=${inventory_items[i][3].main} target="_blank">${inventory_items[i][2]}</a>`
} else {
name_cell.innerHTML = `${inventory_items[i][2]}`
}
let qty_uom_cell = document.createElement('td')
console.log(shoppingList[7])
if(shoppingList[10] == 'calculated'){
qty_uom_cell.innerHTML = `
<input class="item_qty" id="${inventory_items[i][0]}@item" value='0.00' type="text" placeholder=" " style="width: 60px; height: 30px;" disabled>
<input class="item_uom" id="${inventory_items[i][0]}@item" value='' type="text" placeholder=" " style="width: 60px; height: 30px;" disabled>`
} else {
console.log(inventory_items[i][0])
qty = quantities[`${inventory_items[i][0]}@item`]['qty']
uom = quantities[`${inventory_items[i][0]}@item`]['uom']
qty_uom_cell.innerHTML = `
<input class="item_qty" id="${inventory_items[i][0]}@item" value="${qty}" type="text" placeholder=" " style="width: 60px; height: 30px;">
<input class="item_uom" id="${inventory_items[i][0]}@item" value="${uom}" type="text" placeholder=" " style="width: 60px; height: 30px;">`
}
let type_cell = document.createElement('td')
type_cell.innerHTML = "Item"
row.appendChild(database_id_cell)
row.appendChild(barcode_cell)
row.appendChild(name_cell)
row.appendChild(qty_uom_cell)
row.appendChild(type_cell)
tbl_body.appendChild(row)
};
<!-- Reference creation for Custom Items -->
Object.entries(custom_items).forEach(([key, value]) =>{
var row = document.createElement('tr')
let database_id_cell = document.createElement('td')
database_id_cell.innerHTML = `${value[0]}`
let barcode_cell = document.createElement('td')
barcode_cell.innerHTML = `${value[1]}`
let name_cell = document.createElement('td')
if (value[3]){
name_cell.innerHTML = `<a href=${value[3].main} target="_blank">${value[2]}</a>`
} else {
name_cell.innerHTML = `${value[2]}`
}
let qty_uom_cell = document.createElement('td')
console.log(shoppingList[10])
if(shoppingList[10] == 'calculated'){
qty_uom_cell.innerHTML = `
<input class="item_qty" id="${value[0]}@custom" value=${quantities[`${value[0]}@custom`]['qty']} type="text" placeholder=" " style="width: 60px; height: 30px;">
<input class="item_uom" id="${value[0]}@custom" value=${quantities[`${value[0]}@custom`]['uom']} style="width: 60px; height: 30px;">`
} else {
qty_uom_cell.innerHTML = `
<input class="item_qty" id="${value[0]}@custom" value=${quantities[`${value[0]}@custom`]['qty']} type="text" placeholder=" " style="width: 60px; height: 30px;">
<input class="item_uom" id="${value[0]}@custom" value=${quantities[`${value[0]}@custom`]['uom']} type="text" placeholder=" " style="width: 60px; height: 30px;">`
}
let type_cell = document.createElement('td')
type_cell.innerHTML = "Custom"
row.appendChild(database_id_cell)
row.appendChild(barcode_cell)
row.appendChild(name_cell)
row.appendChild(qty_uom_cell)
row.appendChild(type_cell)
tbl_body.appendChild(row)
});
references_table.appendChild(tbl_header)
references_table.appendChild(tbl_body)
table_div.appendChild(references_table)
}
async function save_checked(){
let checkboxes = document.querySelectorAll('.checkbox-class');
checkboxes.forEach((checkbox) => {
if (pantry_checked_items.includes(Number(checkbox.id)) && !checkbox.checked){
pantry_checked_items.splice(pantry_checked_items.indexOf(Number(Number(checkbox.id))), 1);
delete quantities[`${Number(checkbox.id)}@item`]
}
if (!pantry_checked_items.includes(Number(checkbox.id)) && checkbox.checked){
pantry_checked_items.push(Number(checkbox.id))
quantities[`${Number(checkbox.id)}@item`] = {
qty: 0.00,
uom: "each",
}
}
});
}
async function save_quantities(){
let x = document.querySelectorAll(".item_qty")
console.log(x)
for(let i=0; i<x.length; i++){
if(shoppingList[10]== 'calculated'){
quantities[`${x[i].id}`] = {qty: Number(x[i].value)}
} else {
quantities[`${x[i].id}`] = {qty: Number(x[i].value)}
}
}
let y = document.querySelectorAll(".item_uom")
for(let i=0; i<y.length; i++){
if(shoppingList[10]== 'calculated'){
quantities[`${y[i].id}`]['uom'] = y[i].value
} else {
quantities[`${y[i].id}`]['uom'] = y[i].value
}
}
}
function update_list(){
if (current_page == 1){
document.getElementById('back').classList.add("disabled")
}else{
document.getElementById('back').classList.remove("disabled")
};
const url = new URL('/getItems', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', limit);
url.searchParams.append('search_text', search_text);
let container = document.getElementById('checkbox-container')
fetch(url)
.then(response => response.json())
.then(data => {
if (data.items.length < limit){
document.getElementById('forward').classList.add("disabled")
} else {
document.getElementById('forward').classList.remove("disabled")
}
const divi = document.createElement('div')
data.items.forEach(item => {
const checkboxHTML = document.createElement('p')
if (pantry_checked_items.includes(item[0])){
checkboxHTML.innerHTML = `<label>
<input class="checkbox-class" id=${item[0]} name=${item[0]} checked="checked" type="checkbox" />
<span>${item[1]} - ${item[2]}</span>
</label>`;
} else {
checkboxHTML.innerHTML = `<div class="col s12"><label>
<input class="checkbox-class" id=${item[0]} name=${item[0]} type="checkbox" />
<span>${item[1]} - ${item[2]}</span>
</label></div>`;
}
divi.appendChild(checkboxHTML)
})
container.innerHTML = divi.innerHTML
})
}
document.getElementById("search").addEventListener('keydown', function(event){
if(event.key === 'Enter'){
console.log(document.getElementById("search").value);
search_text = document.getElementById("search").value;
update_list()
}
})
document.getElementById('forward').addEventListener('click', () =>{
current_page++
save_checked()
update_list()
})
document.getElementById('back').addEventListener('click', () =>{
current_page--
save_checked()
update_list()
})
async function updateShoppingList(){
var itemName = document.getElementById('list_name');
var selectElement = document.getElementById('list_type');
var description = document.getElementById('description');
await fetch(`/updateList`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: shoppingList[0],
name: itemName.innerHTML,
description:description.value,
items: pantry_checked_items,
custom: custom_items,
list_type:selectElement.value,
quantities:quantities,
}),
});
M.toast({text: "List Saved!"})
}
document.getElementById('update_items').addEventListener('click', async function(){
await save_checked()
//await save_quantities()
await updateShoppingList()
var modalElem = document.querySelector('#item_modal');
var instance = M.Modal.getInstance(modalElem);
instance.close();
await fetchList()
await populateInfo()
await populateReferences()
})
function generateRandomString() {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < 6; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
async function saveList(){
await save_checked()
await save_quantities()
await updateShoppingList()
await fetchList()
await populateInfo()
await populateReferences()
}
async function addCustom(){
let item_id = generateRandomString()
let name = document.getElementById('item_name').value
let weblink = document.getElementById('weblink').value
//await save_quantities()
item = new Array();
item[0] = item_id
item[1] = item_id
item[2] = name
item[3] = {
main: weblink
}
custom_items[item_id] = item
quantities[`${item_id}@custom`] = {
qty: 0.00,
uom: 'each',
}
await updateShoppingList()
await populateInfo()
await populateReferences()
}
</script>
</html>

View File

@ -0,0 +1,332 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<!-- 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 href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<style>
.hand-pointer {
cursor: pointer;
}
[type="radio"]:checked + span:after {
border: 2px solid rgb(0 128 0 / 30%); /* Outline color */
background-color: rgb(0 128 0 / 30%); /* Fill color */
}
header, main, footer, body {
padding-left: 300px;
}
@media only screen and (max-width : 992px) {
header, main, footer, body {
padding-left: 0;
}
}
.dropdown-disabled {
pointer-events: none;
opacity: 0.5; /* or your desired degree of transparency */
}
</style>
<ul id='dropdown1' class='dropdown-content'>
{% for site in sites %}
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
{% endfor %}
</ul>
<ul id="slide-out" class="sidenav sidenav-fixed orange lighten-4" style="width: 250px;">
<li>
<div class="user-view">
<!-- <div class="background">
<img src="images/office.jpg">
</div> -->
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
<a href="#name"><span class="black-text name">John Doe</span></a>
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
</div>
</li>
<li><a class="dropdown-trigger" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
<li><a href="/items">Site Items</a></li>
<li><a href="/groups">Site Groups</a></li>
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
</ul>
<body>
<div class="container section" style="padding-bottom: 72px;">
<div class="row">
<div class="row col s12">
<div class="col-s3">
<a href="#" data-target="slide-out" class="sidenav-trigger hide-on-large-only left"><i class="material-icons">menu</i></a>
</div>
<div class="col s6 m6 offset-m3 input-field outlined align-center">
<i class="material-icons prefix">search</i>
<input style="border-radius: 20px; border: 1px solid #ccc;" id="search" name="search" type="search" placeholder="Search Items" value="">
</div>
<div class="col s3">
<a class="btn waves-effect waves-light center-align right tooltipped orange lighten-4 black-text text-darken-2 z-depth-0" data-position="bottom" data-tooltip="Open up filter options." style="margin-right: 5px; margin-top:0px; border-radius: 20px 10px 20px 10px;" onclick="hideFilters()"><i class="material-icons">tune</i></a>
</div>
</div>
<div class="col s12 hide" id="filter_options" style="padding: 20px">
<!-- Set the number of items -->
<div class="row center">
<div class="col s12">
<p>Set Items Per Page</p>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" checked onclick="changeLimit(25)"/>
<span>25 items</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(50)"/>
<span>50 items</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(75)"/>
<span>75 itesm</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(100)"/>
<span>100 items</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(150)"/>
<span>150 items</span>
</label>
</div>
<div class="col s6 m4 l2">
<label>
<input name="group1" type="radio" onclick="changeLimit(200)"/>
<span>200 items</span>
</label>
</div>
<div class="col s12 divider"></div>
</div>
</div>
<div class="col s12 z-depth-0">
<div class="z-depth-0" id="cards">
</div>
</div>
<div class="col s12 center" id="pagination_list">
<ul class="pagination">
<li id="first" class="waves-effect hand-pointer"><a class="orange lighten-4" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px;"><i class="material-icons">first_page</i></a></li>
<li id="back" class="waves-effect hand-pointer" ><a class="orange lighten-4" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px;"><i class="material-icons">chevron_left</i></a></li>
<li id="current_page" style="padding-top: 7px; padding-left: 5px; padding-right: 5px; font-size: 18px;">page_number</li>
<li id="forward" class="waves-effect hand-pointer"><a class="orange lighten-4" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px;"><i class="material-icons">chevron_right</i></a></li>
<li id="last" class="waves-effect hand-pointer"><a class="orange lighten-4" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px;"><i class="material-icons">last_page</i></a></li>
</ul>
</div>
</div>
</div>
<div class="fixed-action-btn">
<a class="btn-floating btn-large">
<i class="large material-icons">more_vert</i>
</a>
<ul>
<li><button class="btn-floating blue darken-1 modal-trigger" data-target="list_modal"><i class="material-icons">playlist_add</i></button></li>
<li><a class="btn-floating green darken-1"><i class="material-icons">download</i></a></li>
</ul>
</div>
<div id="list_modal" class="modal">
<div class="modal-content">
<h4>Adding a Shopping List...</h4>
<div class="row">
<div class="col s12">
<div class="card-panel orange lighten-4">
<span>Shopping Lists are a main feature of this system as a provides a simple interface and way to track when something
needs to be purchased either based on someone adding a custom item or through adding items from your inventory
that will use the items safety stock or a set quantity to create the list dynamically.
<br><br>There are two types of shopping lists you can create:
<br><br>
<b>Plain</b> - This shopping list has no other extra features beyond being a place to keep a list of items that need to be
purchsed and usually is a one time list or a list that isnt reoccuring.
<br><br>
<b>Safety Stock</b> - This list uses items added from the pantry to calculate the needed quantites based on what you have set
the safety stock to for said items. Usually used to create a reoccuring list that is calculated every time you open the list.
<br><br>
</span>
</div>
</div>
<div class="col s12 m6 input-field p-2">
<input id="list_name" type="text" placeholder=" " maxlength="64">
<label for="list_name">List Name</label>
</div>
<div class="col s12 m6 p=2">
<label for="list_type">List Type</label>
<select id="list_type" class="browser-default">
<option value="plain" selected>Plain Group</option>
<option value="calculated">Safety Stock</option>
</select>
</div>
<div class="input-field col s12 p-2">
<textarea id="list_description" class="materialize-textarea" placeholder="A short description for what this shopping list represents..."></textarea>
<label for="list_description">List Description</label>
</div>
</div>
</div>
<div class="modal-footer">
<a onclick="addList()" class="waves-effect btn orange lighten-4 black-text">Add</a>
</div>
</div>
</body>
<script>
let current_page = 1
let end_page = 10
let sort_order = 1
let view = 0
let limit = 10
let filter_state = "hidden"
let searchText = ""
let site = {{ current_site|tojson }}
document.addEventListener("DOMContentLoaded", async function(){
var elems = document.querySelectorAll('.dropdown-trigger');
var instances = M.Dropdown.init(elems, {});
M.AutoInit();
update_list()
});
async function changeSite(new_site){
console.log(`current_site: ${site}`)
console.log(`new site: ${new_site}`)
site = new_site
console.log(`current_site: ${site}`)
const url = new URL('/changeSite', window.location.origin);
url.searchParams.append('site', site)
await fetch(url)
location.reload();
}
function update_list(){
if (current_page === 1){
document.getElementById('back').classList.add("disabled")
document.getElementById('back').classList.remove("waves-effect")
document.getElementById('first').classList.add("disabled")
document.getElementById('first').classList.remove("waves-effect")
} else {
document.getElementById('back').classList.remove("disabled")
document.getElementById('back').classList.add("waves-effect")
document.getElementById('first').classList.remove("disabled")
document.getElementById('first').classList.add("waves-effect")
};
const url = new URL('/getLists', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', limit);
url.searchParams.append('site', site)
fetch(url)
.then(response => response.json())
.then(data => {
end_page = parseInt(data.end)
if (current_page === end_page){
document.getElementById('forward').classList.add("disabled")
document.getElementById('forward').classList.remove("waves-effect")
document.getElementById('last').classList.add("disabled")
document.getElementById('last').classList.remove("waves-effect")
} else {
document.getElementById('forward').classList.remove("disabled")
document.getElementById('forward').classList.add("waves-effect")
document.getElementById('last').classList.remove("disabled")
document.getElementById('last').classList.add("waves-effect")
};
const cards = document.getElementById("cards")
cards.classList.add("row")
cards.style = "gap: 1em; padding-top: 10px;"
const dummy_div = document.createElement('div')
data.lists.forEach(item => {
let item_card = document.createElement('div')
item_card.classList.add('card')
item_card.classList.add('col')
item_card.classList.add('s12')
let card_content = document.createElement('div')
card_content.classList.add('card-content')
card_content.classList.add('orange')
card_content.classList.add('lighten-4')
card_content.innerHTML = `
<span class="card-title">${item[1]}<span class="badge green lighten-1 white-text" data-badge-caption="Items" style="border-radius: 10px;">${item[11]}</span></span>
<p>${item[2]}
</p>`
let card_action = document.createElement('div')
card_action.classList.add('card-action')
card_action.innerHTML = `
<a class="left" style="color: black;">${item[9]}</a>
<a class="transparent z-depth-0 left" href="#!">
<i class="material-icons" style="color: white;">star</i>
</a>
<a class="right" href="/shopping-list/view/${item[0]}">View</a>
<a class="right" href="/shopping-list/edit/${item[0]}">Edit</a>
`
item_card.appendChild(card_content)
item_card.appendChild(card_action)
dummy_div.appendChild(item_card)
});
cards.innerHTML = dummy_div.innerHTML
var elems = document.querySelectorAll('.collapsible');
var instances = M.Collapsible.init(elems, {
// specify options here
});
var elems = document.querySelectorAll('.tooltipped');
var instances = M.Tooltip.init(elems, {
// specify options here
});
document.getElementById("current_page").innerHTML = `${String(current_page)} / ${String(end_page)}`
})
};
async function addList(){
var name = document.getElementById("list_name").value
var description = document.getElementById("list_description").value
var type = document.getElementById("list_type").value
const url = new URL('/addList', window.location.origin);
url.searchParams.append('name', name);
url.searchParams.append('description', description);
url.searchParams.append('type', type);
await fetch(url)
.then(response => response.json())
.then(data => {
M.toast({text: `Adding ${name}: ${data.state}`});
})
update_list()
var elem = document.getElementById("list_modal");
var instance = M.Modal.getInstance(elem);
instance.close()
};
</script>
</html>

View File

@ -0,0 +1,173 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<!-- 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 href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<style>
header, main, footer, body {
padding-left: 250px;
}
@media only screen and (max-width : 992px) {
header, main, footer, body {
padding-left: 0;
}
}
.dropdown-disabled {
pointer-events: none;
opacity: 0.5; /* or your desired degree of transparency */
}
</style>
<ul id='dropdown1' class='dropdown-content'>
{% for site in sites %}
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
{% endfor %}
</ul>
<ul id="slide-out" class="sidenav sidenav-fixed orange lighten-4" style="width: 250px;">
<li>
<div class="user-view">
<!-- <div class="background">
<img src="images/office.jpg">
</div> -->
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
<a href="#name"><span class="black-text name">John Doe</span></a>
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
</div>
</li>
<li><a class="dropdown-trigger dropdown-disabled" data-target="dropdown1">Current Site > {{session['selected_site']}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
<li><a href="/items">Site Items</a></li>
<li><a href="/groups">Site Groups</a></li>
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
</ul>
<body>
<div class="container">
<div class="section">
<div class="row">
<p id="temp"></p>
<div class="col s12">
<h3 id="list_name"></h3>
</div>
<div class="col s12">
<p id="description"></p>
</div>
<div id="table_div" class="col s12 p-2">
</div>
</div>
</div>
</div>
</body>
<script>
const list_id = {{id|tojson}}
let shoppingList;
let quantities = {};
document.addEventListener("DOMContentLoaded", async function(){
await fetchList()
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {});
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, {});
var elems = document.querySelectorAll('.fixed-action-btn');
var instances = M.FloatingActionButton.init(elems, {});
var elems = document.querySelectorAll('.sidenav');
var instances = M.Sidenav.init(elems, {});
var elems = document.querySelectorAll('.dropdown-trigger');
var instances = M.Dropdown.init(elems, {});
var elems = document.querySelectorAll('.collapsible');
var instances = M.Collapsible.init(elems, {});
M.AutoInit();
await populateInfo()
await populateReferences()
console.log(quantities)
})
async function fetchList(){
const url = new URL('/getListView', window.location.origin);
url.searchParams.append('id', list_id);
const response = await fetch(url);
data = await response.json();
shoppingList = data.shopping_list;
quantities = shoppingList[7]
}
function changeSite(site){
console.log(site)
}
async function populateInfo(){
let listName = document.getElementById('list_name')
let description = document.getElementById('description')
listName.innerHTML = shoppingList[1];
description.innerHTML = shoppingList[2];
}
async function populateReferences(){
var table_div = document.getElementById('table_div')
table_div.innerHTML = ""
var references_table = document.createElement('table')
references_table.classList.add("striped")
var tbl_header = document.createElement('thead')
tbl_header.innerHTML = `<tr><th>Item Name</th><th>Product Qty</th></tr>`
var tbl_body = document.createElement('tbody')
let inventory_items = shoppingList[3];
console.log(inventory_items)
// if it is plain then we want to grab the qty from the quantities dictionary
// if it is calculated we need to calculate qty for all inventory items
for (let i=0; i < inventory_items.length; i++){
var row = document.createElement('tr')
let name_cell = document.createElement('td')
if (inventory_items[i][3]){
name_cell.innerHTML = `<a href=${inventory_items[i][3].main} target="_blank">${inventory_items[i][2]}</a>`
} else {
name_cell.innerHTML = `${inventory_items[i][2]}`
}
console.log(inventory_items[i])
let qty_uom_cell = document.createElement('td')
if(shoppingList[10] == 'calculated'){
qty = Number(inventory_items[i][5]) - Number(inventory_items[i][4])
uom = inventory_items[i][6]
} else {
qty = quantities[`${inventory_items[i][0]}@item`]['qty']
uom = quantities[`${inventory_items[i][0]}@item`]['uom']
}
qty_uom_cell.innerHTML = `${qty} ${uom}`
row.appendChild(name_cell)
row.appendChild(qty_uom_cell)
tbl_body.appendChild(row)
};
// all custom items will pull quantities form the quantities dictionary.
references_table.appendChild(tbl_header)
references_table.appendChild(tbl_body)
table_div.appendChild(references_table)
}
</script>
</html>

View File

@ -1,15 +1,48 @@
from flask import Flask, render_template from flask import Flask, render_template, session
import api import api, config
app = Flask(__name__) app = Flask(__name__)
app.secret_key = '11gs22h2h1a4h6ah8e413a45'
app.register_blueprint(api.database_api) app.register_blueprint(api.database_api)
@app.route("/group/<id>")
def group(id):
sites = config.sites_config()
return render_template("groups/group.html", id=id, current_site=session['selected_site'], sites=sites['sites'])
@app.route("/workshop") @app.route("/workshop")
def workshop(): def workshop():
return render_template("workshop.html") sites = config.sites_config()
return render_template("workshop.html", current_site=session['selected_site'], sites=sites['sites'])
@app.route("/shopping-list/view/<id>")
def shopping_lists_view(id):
sites = config.sites_config()
return render_template("shopping-lists/view.html", id=id, current_site=session['selected_site'], sites=sites['sites'])
@app.route("/shopping-list/edit/<id>")
def shopping_lists_edit(id):
sites = config.sites_config()
return render_template("shopping-lists/edit.html", id=id, current_site=session['selected_site'], sites=sites['sites'])
@app.route("/shopping-lists")
def shopping_lists():
sites = config.sites_config()
return render_template("shopping-lists/index.html", current_site=session['selected_site'], sites=sites['sites'])
@app.route("/groups")
def groups():
sites = config.sites_config()
return render_template("groups/index.html", current_site=session['selected_site'], sites=sites['sites'])
@app.route("/items")
def items():
sites = config.sites_config()
return render_template("items/index.html", current_site=session['selected_site'], sites=sites['sites'])
@app.route("/") @app.route("/")
def home(): def home():
return render_template("home.html") session['selected_site'] = 'main'
sites = config.sites_config()
return render_template("items/index.html", current_site=session['selected_site'], sites=sites['sites'])
app.run(host="0.0.0.0", port=5002, debug=True) app.run(host="0.0.0.0", port=5002, debug=True)