Lots of work
This commit is contained in:
parent
f042c7ca82
commit
e8697ffceb
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
453
api.py
453
api.py
@ -1,9 +1,14 @@
|
||||
from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response
|
||||
import psycopg2, math
|
||||
from config import config
|
||||
import psycopg2, math, json, datetime, main, copy
|
||||
from config import config, sites_config
|
||||
|
||||
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):
|
||||
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]
|
||||
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")
|
||||
def pagninate_items():
|
||||
print("hello")
|
||||
page = int(request.args.get('page', 1))
|
||||
limit = int(request.args.get('limit', 10))
|
||||
search_string = str(request.args.get('search_text', ""))
|
||||
sort_order = int(request.args.get('sort_order', 1))
|
||||
view = int(request.args.get('view', 0))
|
||||
site_name = session['selected_site']
|
||||
|
||||
offset = (page - 1) * limit
|
||||
|
||||
@ -67,30 +144,25 @@ def pagninate_items():
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
pantry_inventory, count = paginate_with_params(
|
||||
cur, limit, offset,
|
||||
{'search_string': search_string,
|
||||
'view': view}
|
||||
)
|
||||
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};"
|
||||
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;"
|
||||
cur.execute(sql)
|
||||
pantry_inventory = cur.fetchall()
|
||||
cur.execute(count)
|
||||
count = cur.fetchone()[0]
|
||||
except (Exception, psycopg2.DatabaseError) as 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)})
|
||||
|
||||
@database_api.route("/getItem")
|
||||
def get_item():
|
||||
id = int(request.args.get('id', 1))
|
||||
database_config = config()
|
||||
site_name = "main"
|
||||
site_name = session['selected_site']
|
||||
sites = sites_config()
|
||||
|
||||
|
||||
item = []
|
||||
with psycopg2.connect(**database_config) as conn:
|
||||
try:
|
||||
@ -99,24 +171,351 @@ def get_item():
|
||||
sql = file.read()
|
||||
cur.execute(sql, (id, ))
|
||||
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'}
|
||||
item[22] = ['test_list', 'main_list']
|
||||
item[23] = ['test_recipe',]
|
||||
item[24] = ['test_group', 'main_group', 'test2_group']
|
||||
SQL_groups = f"SELECT * FROM {site_name}_groups WHERE included_items @> ARRAY[%s];"
|
||||
cur.execute(SQL_groups, (item[0], ))
|
||||
item[25] = list(cur.fetchall())
|
||||
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:
|
||||
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")
|
||||
def addGroup():
|
||||
name = str(request.args.get('name', ""))
|
||||
description = str(request.args.get('description', ""))
|
||||
group_type = str(request.args.get('type', ""))
|
||||
|
||||
site_name = session['selected_site']
|
||||
state = "FAILED"
|
||||
|
||||
if name or description or group_type == "":
|
||||
print("this is empty")
|
||||
database_config = config()
|
||||
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"})
|
||||
47
config.py
47
config.py
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
from configparser import ConfigParser
|
||||
from configparser import ConfigParser
|
||||
import json
|
||||
|
||||
|
||||
def config(filename='database.ini', section='postgresql'):
|
||||
@ -19,3 +20,47 @@ def config(filename='database.ini', section='postgresql'):
|
||||
|
||||
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)
|
||||
|
||||
|
||||
14
database.ini
14
database.ini
@ -1,6 +1,10 @@
|
||||
[postgresql]
|
||||
host=192.168.1.67
|
||||
database=test
|
||||
user=test
|
||||
password=test
|
||||
port=5432
|
||||
host = 192.168.1.67
|
||||
database = test
|
||||
user = test
|
||||
password = test
|
||||
port = 5432
|
||||
|
||||
[manage]
|
||||
sites = test,test2,main
|
||||
|
||||
|
||||
21
main.py
21
main.py
@ -35,7 +35,7 @@ def update_item_primary(site_name, barcode, new_primary: str):
|
||||
return False
|
||||
|
||||
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
|
||||
try:
|
||||
database_config = config()
|
||||
@ -85,7 +85,7 @@ def create_logistics_info(conn, site_name, barcode, payload):
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
return error
|
||||
|
||||
return logistics_info_id
|
||||
|
||||
@ -103,7 +103,7 @@ def create_item_info(conn, site_name, barcode, payload):
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
return error
|
||||
|
||||
return item_info_id
|
||||
|
||||
@ -136,7 +136,7 @@ def add_location(site_name, name, zone_id):
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
return error
|
||||
|
||||
uuid = f"{zone_name}@{name}"
|
||||
try:
|
||||
@ -145,7 +145,7 @@ def add_location(site_name, name, zone_id):
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
return error
|
||||
|
||||
def add_zone(site_name, name):
|
||||
database_config = config()
|
||||
@ -157,7 +157,7 @@ def add_zone(site_name, name):
|
||||
except (Exception, psycopg2.DatabaseError) as error:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
return error
|
||||
|
||||
def add_transaction(site_name, barcode, qty, user_id, transaction_type = "info", description = "", data = {}, location=None):
|
||||
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:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
return error
|
||||
|
||||
if not location:
|
||||
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:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
return error
|
||||
|
||||
if logistics_info[3] in location_items.keys():
|
||||
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:
|
||||
print(error)
|
||||
conn.rollback()
|
||||
return False
|
||||
return error
|
||||
|
||||
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 '
|
||||
|
||||
@ -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=qty, user_id=1, description="scan in")
|
||||
|
||||
def drop_table(sql_file: str):
|
||||
database_config = config()
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import sys, os, shutil
|
||||
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
|
||||
@ -90,6 +91,7 @@ def create():
|
||||
config.write(f"default_primary_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} created!")
|
||||
@ -108,6 +110,8 @@ if __name__ == "__main__":
|
||||
if func_name == "delete" and argument == "site":
|
||||
main.delete_site(sys.argv[3])
|
||||
shutil.rmtree(f"sites/{sys.argv[3]}")
|
||||
cfg.delete_site(sys.argv[3])
|
||||
|
||||
|
||||
if func_name == "item":
|
||||
if argument == "add":
|
||||
|
||||
@ -3,7 +3,7 @@ CREATE TABLE IF NOT EXISTS %sitename%_items(
|
||||
barcode VARCHAR(255) NOT NULL,
|
||||
item_name VARCHAR(255) NOT NULL,
|
||||
brand INTEGER,
|
||||
description TEXT;
|
||||
description TEXT,
|
||||
tags TEXT [],
|
||||
links JSONB,
|
||||
item_info_id INTEGER NOT NULL,
|
||||
|
||||
@ -2,10 +2,11 @@ CREATE TABLE IF NOT EXISTS %sitename%_shopping_lists (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
pantry_items JSONB,
|
||||
pantry_items INTEGER [],
|
||||
custom_items JSONB,
|
||||
recipes JSONB,
|
||||
groups JSONB,
|
||||
recipes INTEGER [],
|
||||
groups INTEGER [],
|
||||
quantities JSONB,
|
||||
author INTEGER,
|
||||
creation_date TIMESTAMP,
|
||||
type VARCHAR(64),
|
||||
|
||||
@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS main_items(
|
||||
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,
|
||||
|
||||
@ -2,10 +2,11 @@ CREATE TABLE IF NOT EXISTS main_shopping_lists (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
pantry_items JSONB,
|
||||
pantry_items INTEGER [],
|
||||
custom_items JSONB,
|
||||
recipes JSONB,
|
||||
groups JSONB,
|
||||
recipes INTEGER [],
|
||||
groups INTEGER [],
|
||||
quantities JSONB,
|
||||
author INTEGER,
|
||||
creation_date TIMESTAMP,
|
||||
type VARCHAR(64),
|
||||
|
||||
@ -9,36 +9,37 @@ WHERE main_items.id=%s;
|
||||
01 - barcode
|
||||
02 - item_name
|
||||
03 - brand (id)
|
||||
04 - tags
|
||||
05 - links
|
||||
06 - item_info_id
|
||||
07 - logistics_info_id
|
||||
08 - food_info_id
|
||||
09 - row_type
|
||||
10 - item_type
|
||||
11 - search_string
|
||||
12 - logistics_info_id
|
||||
13 - barcode
|
||||
14 - primary_location
|
||||
15 - auto_issue_location
|
||||
16 - dynamic_locations
|
||||
17 - location_data
|
||||
18 - quantity_on_hand
|
||||
19 - item_info_id
|
||||
20 - barcode
|
||||
21 - linked_items
|
||||
22 - shopping_lists
|
||||
23 - recipes
|
||||
24 - groups
|
||||
25 - packaging
|
||||
26 - uom
|
||||
27 - cost
|
||||
28 - safety_stock
|
||||
29 - lead_time_days
|
||||
30 - ai_pick
|
||||
31 - food_info_id
|
||||
32 - food_groups
|
||||
33 - ingrediants
|
||||
34 - nutrients
|
||||
35 - expires
|
||||
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/test/site.ini
Normal file
9
sites/test/site.ini
Normal 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
|
||||
4
sites/test/sql/create/brands.sql
Normal file
4
sites/test/sql/create/brands.sql
Normal file
@ -0,0 +1,4 @@
|
||||
CREATE TABLE IF NOT EXISTS test_brands (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255)
|
||||
);
|
||||
7
sites/test/sql/create/food_info.sql
Normal file
7
sites/test/sql/create/food_info.sql
Normal 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
|
||||
);
|
||||
8
sites/test/sql/create/groups.sql
Normal file
8
sites/test/sql/create/groups.sql
Normal 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)
|
||||
);
|
||||
28
sites/test/sql/create/item.sql
Normal file
28
sites/test/sql/create/item.sql
Normal 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)
|
||||
);
|
||||
15
sites/test/sql/create/item_info.sql
Normal file
15
sites/test/sql/create/item_info.sql
Normal 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)
|
||||
);
|
||||
8
sites/test/sql/create/linked_items.sql
Normal file
8
sites/test/sql/create/linked_items.sql
Normal 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)
|
||||
);
|
||||
11
sites/test/sql/create/locations.sql
Normal file
11
sites/test/sql/create/locations.sql
Normal 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)
|
||||
);
|
||||
16
sites/test/sql/create/logins.sql
Normal file
16
sites/test/sql/create/logins.sql
Normal 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
|
||||
);
|
||||
|
||||
10
sites/test/sql/create/logistics_info.sql
Normal file
10
sites/test/sql/create/logistics_info.sql
Normal 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)
|
||||
);
|
||||
9
sites/test/sql/create/receipt_items.sql
Normal file
9
sites/test/sql/create/receipt_items.sql
Normal 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)
|
||||
);
|
||||
10
sites/test/sql/create/receipts.sql
Normal file
10
sites/test/sql/create/receipts.sql
Normal 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)
|
||||
);
|
||||
12
sites/test/sql/create/recipes.sql
Normal file
12
sites/test/sql/create/recipes.sql
Normal 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
|
||||
);
|
||||
14
sites/test/sql/create/shopping_lists.sql
Normal file
14
sites/test/sql/create/shopping_lists.sql
Normal 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)
|
||||
);
|
||||
15
sites/test/sql/create/transactions.sql
Normal file
15
sites/test/sql/create/transactions.sql
Normal 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)
|
||||
);
|
||||
8
sites/test/sql/create/vendors.sql
Normal file
8
sites/test/sql/create/vendors.sql
Normal 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)
|
||||
);
|
||||
5
sites/test/sql/create/zones.sql
Normal file
5
sites/test/sql/create/zones.sql
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE TABLE IF NOT EXISTS test_zones(
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(32) NOT NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
1
sites/test/sql/drop/brands.sql
Normal file
1
sites/test/sql/drop/brands.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_brands CASCADE;
|
||||
1
sites/test/sql/drop/food_info.sql
Normal file
1
sites/test/sql/drop/food_info.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_food_info CASCADE;
|
||||
1
sites/test/sql/drop/groups.sql
Normal file
1
sites/test/sql/drop/groups.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_groups CASCADE;
|
||||
1
sites/test/sql/drop/item_info.sql
Normal file
1
sites/test/sql/drop/item_info.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_item_info CASCADE;
|
||||
1
sites/test/sql/drop/items.sql
Normal file
1
sites/test/sql/drop/items.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_items CASCADE;
|
||||
1
sites/test/sql/drop/linked_items.sql
Normal file
1
sites/test/sql/drop/linked_items.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_itemlinks CASCADE;
|
||||
1
sites/test/sql/drop/locations.sql
Normal file
1
sites/test/sql/drop/locations.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_locations CASCADE;
|
||||
1
sites/test/sql/drop/logistics_info.sql
Normal file
1
sites/test/sql/drop/logistics_info.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_logistics_info CASCADE;
|
||||
1
sites/test/sql/drop/receipt_items.sql
Normal file
1
sites/test/sql/drop/receipt_items.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_receipt_items CASCADE;
|
||||
1
sites/test/sql/drop/receipts.sql
Normal file
1
sites/test/sql/drop/receipts.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_receipts CASCADE;
|
||||
1
sites/test/sql/drop/recipes.sql
Normal file
1
sites/test/sql/drop/recipes.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_recipes CASCADE;
|
||||
1
sites/test/sql/drop/shopping_lists.sql
Normal file
1
sites/test/sql/drop/shopping_lists.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_shopping_lists CASCADE;
|
||||
1
sites/test/sql/drop/transactions.sql
Normal file
1
sites/test/sql/drop/transactions.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_transactions CASCADE;
|
||||
1
sites/test/sql/drop/vendors.sql
Normal file
1
sites/test/sql/drop/vendors.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_vendors CASCADE;
|
||||
1
sites/test/sql/drop/zones.sql
Normal file
1
sites/test/sql/drop/zones.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test_zones CASCADE;
|
||||
45
sites/test/sql/unique/select_item_all.sql
Normal file
45
sites/test/sql/unique/select_item_all.sql
Normal 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
9
sites/test2/site.ini
Normal 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
|
||||
4
sites/test2/sql/create/brands.sql
Normal file
4
sites/test2/sql/create/brands.sql
Normal file
@ -0,0 +1,4 @@
|
||||
CREATE TABLE IF NOT EXISTS test2_brands (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255)
|
||||
);
|
||||
7
sites/test2/sql/create/food_info.sql
Normal file
7
sites/test2/sql/create/food_info.sql
Normal 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
|
||||
);
|
||||
8
sites/test2/sql/create/groups.sql
Normal file
8
sites/test2/sql/create/groups.sql
Normal 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)
|
||||
);
|
||||
28
sites/test2/sql/create/item.sql
Normal file
28
sites/test2/sql/create/item.sql
Normal 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)
|
||||
);
|
||||
15
sites/test2/sql/create/item_info.sql
Normal file
15
sites/test2/sql/create/item_info.sql
Normal 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)
|
||||
);
|
||||
8
sites/test2/sql/create/linked_items.sql
Normal file
8
sites/test2/sql/create/linked_items.sql
Normal 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)
|
||||
);
|
||||
11
sites/test2/sql/create/locations.sql
Normal file
11
sites/test2/sql/create/locations.sql
Normal 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)
|
||||
);
|
||||
16
sites/test2/sql/create/logins.sql
Normal file
16
sites/test2/sql/create/logins.sql
Normal 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
|
||||
);
|
||||
|
||||
10
sites/test2/sql/create/logistics_info.sql
Normal file
10
sites/test2/sql/create/logistics_info.sql
Normal 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)
|
||||
);
|
||||
9
sites/test2/sql/create/receipt_items.sql
Normal file
9
sites/test2/sql/create/receipt_items.sql
Normal 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)
|
||||
);
|
||||
10
sites/test2/sql/create/receipts.sql
Normal file
10
sites/test2/sql/create/receipts.sql
Normal 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)
|
||||
);
|
||||
12
sites/test2/sql/create/recipes.sql
Normal file
12
sites/test2/sql/create/recipes.sql
Normal 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
|
||||
);
|
||||
14
sites/test2/sql/create/shopping_lists.sql
Normal file
14
sites/test2/sql/create/shopping_lists.sql
Normal 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)
|
||||
);
|
||||
15
sites/test2/sql/create/transactions.sql
Normal file
15
sites/test2/sql/create/transactions.sql
Normal 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)
|
||||
);
|
||||
8
sites/test2/sql/create/vendors.sql
Normal file
8
sites/test2/sql/create/vendors.sql
Normal 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)
|
||||
);
|
||||
5
sites/test2/sql/create/zones.sql
Normal file
5
sites/test2/sql/create/zones.sql
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE TABLE IF NOT EXISTS test2_zones(
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(32) NOT NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
1
sites/test2/sql/drop/brands.sql
Normal file
1
sites/test2/sql/drop/brands.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_brands CASCADE;
|
||||
1
sites/test2/sql/drop/food_info.sql
Normal file
1
sites/test2/sql/drop/food_info.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_food_info CASCADE;
|
||||
1
sites/test2/sql/drop/groups.sql
Normal file
1
sites/test2/sql/drop/groups.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_groups CASCADE;
|
||||
1
sites/test2/sql/drop/item_info.sql
Normal file
1
sites/test2/sql/drop/item_info.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_item_info CASCADE;
|
||||
1
sites/test2/sql/drop/items.sql
Normal file
1
sites/test2/sql/drop/items.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_items CASCADE;
|
||||
1
sites/test2/sql/drop/linked_items.sql
Normal file
1
sites/test2/sql/drop/linked_items.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_itemlinks CASCADE;
|
||||
1
sites/test2/sql/drop/locations.sql
Normal file
1
sites/test2/sql/drop/locations.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_locations CASCADE;
|
||||
1
sites/test2/sql/drop/logistics_info.sql
Normal file
1
sites/test2/sql/drop/logistics_info.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_logistics_info CASCADE;
|
||||
1
sites/test2/sql/drop/receipt_items.sql
Normal file
1
sites/test2/sql/drop/receipt_items.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_receipt_items CASCADE;
|
||||
1
sites/test2/sql/drop/receipts.sql
Normal file
1
sites/test2/sql/drop/receipts.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_receipts CASCADE;
|
||||
1
sites/test2/sql/drop/recipes.sql
Normal file
1
sites/test2/sql/drop/recipes.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_recipes CASCADE;
|
||||
1
sites/test2/sql/drop/shopping_lists.sql
Normal file
1
sites/test2/sql/drop/shopping_lists.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_shopping_lists CASCADE;
|
||||
1
sites/test2/sql/drop/transactions.sql
Normal file
1
sites/test2/sql/drop/transactions.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_transactions CASCADE;
|
||||
1
sites/test2/sql/drop/vendors.sql
Normal file
1
sites/test2/sql/drop/vendors.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_vendors CASCADE;
|
||||
1
sites/test2/sql/drop/zones.sql
Normal file
1
sites/test2/sql/drop/zones.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE test2_zones CASCADE;
|
||||
45
sites/test2/sql/unique/select_item_all.sql
Normal file
45
sites/test2/sql/unique/select_item_all.sql
Normal 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
|
||||
*/
|
||||
352
templates/groups/group.html
Normal file
352
templates/groups/group.html
Normal 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
448
templates/groups/index.html
Normal 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>
|
||||
|
||||
@ -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>
|
||||
@ -2,7 +2,7 @@
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<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" />
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
@ -22,17 +22,57 @@
|
||||
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 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>
|
||||
<div class="container section" style="padding-bottom: 72px;">
|
||||
<div class="row">
|
||||
<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" value="">
|
||||
</div>
|
||||
<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>
|
||||
<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 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 class="col s12 hide" id="filter_options" style="padding: 20px">
|
||||
<!-- This is for basic views -->
|
||||
@ -146,10 +186,64 @@
|
||||
<i class="large material-icons">more_vert</i>
|
||||
</a>
|
||||
<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>
|
||||
</ul>
|
||||
</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>
|
||||
<script>
|
||||
@ -160,6 +254,8 @@
|
||||
let limit = 50
|
||||
let filter_state = "hidden"
|
||||
let searchText = ""
|
||||
let site = {{ current_site|tojson }}
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var elems = document.querySelectorAll('.collapsible');
|
||||
@ -174,9 +270,15 @@
|
||||
// specify options here
|
||||
});
|
||||
});
|
||||
|
||||
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() {
|
||||
@ -187,8 +289,18 @@
|
||||
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)
|
||||
if (current_page === 1){
|
||||
document.getElementById('back').classList.add("disabled")
|
||||
@ -212,7 +324,7 @@
|
||||
|
||||
|
||||
|
||||
fetch(url)
|
||||
await fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
|
||||
@ -246,7 +358,7 @@
|
||||
list_collapsible.style = "background: white;"
|
||||
|
||||
data.items.forEach(item => {
|
||||
let qty = item[18]
|
||||
let qty = item[19]
|
||||
let roundedQty = qty.toFixed(2)
|
||||
var list_item = document.createElement('li')
|
||||
list_item.classList.add("z-depth-0")
|
||||
@ -273,7 +385,7 @@
|
||||
button_group.style = "margin-bottom: 0px; padding-bottom: 0px;"
|
||||
button_group.innerHTML = `
|
||||
<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>
|
||||
</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;">
|
||||
@ -309,20 +421,20 @@
|
||||
|
||||
};
|
||||
|
||||
function changeLimit(limit_value){
|
||||
async function changeLimit(limit_value){
|
||||
limit = limit_value
|
||||
current_page = 1
|
||||
update_list()
|
||||
await update_list()
|
||||
};
|
||||
|
||||
function changeSort(order){
|
||||
async function changeSort(order){
|
||||
sort_order = order
|
||||
update_list()
|
||||
await update_list()
|
||||
}
|
||||
|
||||
function changeView(view_num){
|
||||
async function changeView(view_num){
|
||||
view = view_num
|
||||
update_list()
|
||||
await update_list()
|
||||
}
|
||||
|
||||
function hideFilters(){
|
||||
@ -335,40 +447,68 @@
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('forward').addEventListener('click', () =>{
|
||||
document.getElementById('forward').addEventListener('click', async () =>{
|
||||
if (!(document.getElementById("forward").classList.contains("disabled"))){
|
||||
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"))){
|
||||
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"))){
|
||||
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"))){
|
||||
current_page = 1
|
||||
update_list();
|
||||
await update_list();
|
||||
};
|
||||
});
|
||||
|
||||
document.querySelector("#search").addEventListener('change', () =>{
|
||||
document.querySelector("#search").addEventListener('change', async () => {
|
||||
searchText = document.getElementById("search").value;
|
||||
current_page = 1
|
||||
update_list()
|
||||
current_page = 1;
|
||||
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>
|
||||
</html>
|
||||
|
||||
233
templates/items/item.html
Normal file
233
templates/items/item.html
Normal file
@ -0,0 +1,233 @@
|
||||
<!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">
|
||||
<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>
|
||||
const item = {{ item|tojson }}
|
||||
var reference_state = 1
|
||||
let links = {};
|
||||
|
||||
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>
|
||||
522
templates/shopping-lists/edit.html
Normal file
522
templates/shopping-lists/edit.html
Normal file
@ -0,0 +1,522 @@
|
||||
<!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(){
|
||||
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[10])
|
||||
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 {
|
||||
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")
|
||||
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>
|
||||
332
templates/shopping-lists/index.html
Normal file
332
templates/shopping-lists/index.html
Normal 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>
|
||||
173
templates/shopping-lists/view.html
Normal file
173
templates/shopping-lists/view.html
Normal 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>
|
||||
42
webserver.py
42
webserver.py
@ -1,15 +1,47 @@
|
||||
from flask import Flask, render_template
|
||||
import api
|
||||
from flask import Flask, render_template, session
|
||||
import api, config
|
||||
app = Flask(__name__)
|
||||
|
||||
app.secret_key = '11gs22h2h1a4h6ah8e413a45'
|
||||
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")
|
||||
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("/")
|
||||
def home():
|
||||
return render_template("home.html")
|
||||
session['selected_site'] = 'main'
|
||||
return render_template("items/index.html")
|
||||
|
||||
app.run(host="0.0.0.0", port=5002, debug=True)
|
||||
Loading…
x
Reference in New Issue
Block a user