Start with a proper setup
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.bundle
|
||||
3
python_flask/.gitignore
vendored
3
python_flask/.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
__pycache__/
|
||||
/venv/
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
*.bak
|
||||
*.bundle
|
||||
|
||||
42
python_flask/NOTES.md
Normal file
42
python_flask/NOTES.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# ideas
|
||||
|
||||
Trip info:
|
||||
* Itinerary
|
||||
* Members
|
||||
|
||||
---
|
||||
|
||||
Item info:
|
||||
|
||||
Comments
|
||||
Links
|
||||
n:1 Product
|
||||
|
||||
---
|
||||
|
||||
Item groups that can be selected together. 1:1 product.
|
||||
|
||||
---
|
||||
|
||||
State management.
|
||||
|
||||
---
|
||||
|
||||
Item comments per trip (that are added to item overview page)
|
||||
|
||||
"This item was already on the following trips:"
|
||||
|
||||
---
|
||||
|
||||
Todos, with preparation time window.
|
||||
|
||||
---
|
||||
|
||||
Review when setting to "done", review per item and for the whole trip.
|
||||
|
||||
"Would take again" (default yes)
|
||||
|
||||
# todos
|
||||
|
||||
Category CRUD
|
||||
|
||||
Binary file not shown.
@@ -2,6 +2,7 @@ import uuid
|
||||
import sqlalchemy
|
||||
import csv
|
||||
from flask import Flask
|
||||
from flask_migrate import Migrate
|
||||
|
||||
from .helpers import *
|
||||
|
||||
@@ -10,56 +11,11 @@ from flask_sqlalchemy import SQLAlchemy
|
||||
app = Flask(__name__)
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{app.root_path}/../db.sqlite"
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
migrate = Migrate(app, db, render_as_batch=True)
|
||||
|
||||
from packager.models import *
|
||||
import packager.views
|
||||
|
||||
db.create_all()
|
||||
|
||||
try:
|
||||
categories = (
|
||||
{"id": uuid.uuid4(), "name": "Sleeping"},
|
||||
{"id": uuid.uuid4(), "name": "Shelter"},
|
||||
{"id": uuid.uuid4(), "name": "Fire"},
|
||||
{"id": uuid.uuid4(), "name": "Cooking"},
|
||||
{"id": uuid.uuid4(), "name": "Water"},
|
||||
{"id": uuid.uuid4(), "name": "Protection"},
|
||||
{"id": uuid.uuid4(), "name": "Tools"},
|
||||
{"id": uuid.uuid4(), "name": "Insulation"},
|
||||
{"id": uuid.uuid4(), "name": "Electronics"},
|
||||
{"id": uuid.uuid4(), "name": "Carry"},
|
||||
{"id": uuid.uuid4(), "name": "Medic"},
|
||||
{"id": uuid.uuid4(), "name": "Hygiene"},
|
||||
)
|
||||
|
||||
for category in categories:
|
||||
db.session.add(
|
||||
PackageListItemCategory(
|
||||
id=str(category['id']),
|
||||
name=category['name'],
|
||||
description="",
|
||||
)
|
||||
)
|
||||
|
||||
with open("./items.csv") as csvfile:
|
||||
reader = csv.reader(csvfile, delimiter=',')
|
||||
for row in reader:
|
||||
print(row)
|
||||
(name, category, weight) = row
|
||||
|
||||
db.session.add(
|
||||
PackageListItem(
|
||||
id=str(uuid.uuid4()),
|
||||
name=name,
|
||||
description="",
|
||||
weight=weight,
|
||||
category_id=str([c['id'] for c in categories if c['name'] == category][0])
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print("db init done")
|
||||
db.session.commit()
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
pass
|
||||
|
||||
55
python_flask/packager/components/Base.py
Normal file
55
python_flask/packager/components/Base.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import os
|
||||
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class Base:
|
||||
def __init__(self, element, root_path, active_page=None):
|
||||
doc = dominate.document(title="Packager")
|
||||
with doc.head:
|
||||
t.script(src="https://unpkg.com/htmx.org@1.7.0")
|
||||
t.script(src="https://cdn.tailwindcss.com")
|
||||
t.script(src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js", defer=True)
|
||||
t.link(
|
||||
rel="stylesheet",
|
||||
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css",
|
||||
)
|
||||
with doc:
|
||||
t.script(raw(open(os.path.join(root_path, "js/app.js")).read()))
|
||||
with t.header(
|
||||
_class=cls(
|
||||
"bg-gray-200",
|
||||
"p-5",
|
||||
"flex",
|
||||
"flex-row",
|
||||
"flex-nowrap",
|
||||
"justify-between",
|
||||
"items-center",
|
||||
)
|
||||
):
|
||||
t.span("Packager", _class=cls("text-xl", "font-semibold"))
|
||||
with t.nav(
|
||||
_class=cls("grow", "flex", "flex-row", "justify-center", "gap-x-6")
|
||||
):
|
||||
basecls = ["text-lg"]
|
||||
activecls = ["font-bold", "underline"]
|
||||
t.a(
|
||||
"Inventory",
|
||||
href="/inventory/",
|
||||
_class=cls(
|
||||
*basecls, *(activecls if active_page == "inventory" else [])
|
||||
),
|
||||
)
|
||||
t.a(
|
||||
"Trips",
|
||||
href="/trips/",
|
||||
_class=cls(
|
||||
*basecls, *(activecls if active_page == "trips" else [])
|
||||
),
|
||||
)
|
||||
doc.add(element.doc)
|
||||
self.doc = doc
|
||||
@@ -1,35 +0,0 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
def CategoryList(categories):
|
||||
with t.div(id="packagelist-table") as doc:
|
||||
t.h1("Categories", _class=cls("text-2xl", "mb-5"))
|
||||
with t.table(
|
||||
id="packagelist-table",
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
),
|
||||
):
|
||||
with t.tbody() as b:
|
||||
for category in categories:
|
||||
with t.tr(_class=cls("h-10", "hover:bg-purple-200")) as doc:
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a(
|
||||
category.name,
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/category/{category.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("block", "p-2", "m-2"),
|
||||
)
|
||||
|
||||
return doc
|
||||
@@ -4,19 +4,15 @@ import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class Home:
|
||||
def __init__(self, element, root_path):
|
||||
doc = dominate.document(title="Packager")
|
||||
with doc.head:
|
||||
t.script(src="https://unpkg.com/htmx.org@1.7.0")
|
||||
t.script(src="https://cdn.tailwindcss.com")
|
||||
t.script(src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js", defer=True)
|
||||
t.link(
|
||||
rel="stylesheet",
|
||||
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css",
|
||||
)
|
||||
with doc:
|
||||
t.script(raw(open(os.path.join(root_path, "js/app.js")).read()))
|
||||
doc.add(element.doc)
|
||||
def __init__(self):
|
||||
with t.div(id="home", _class=cls("p-8", "max-w-xl")) as doc:
|
||||
with t.p():
|
||||
t.a("Inventory", href="/inventory/")
|
||||
with t.p():
|
||||
t.a("Trips", href="/trips/")
|
||||
|
||||
self.doc = doc
|
||||
|
||||
85
python_flask/packager/components/InventoryCategoryList.py
Normal file
85
python_flask/packager/components/InventoryCategoryList.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
def InventoryCategoryList(categories):
|
||||
with t.div() as doc:
|
||||
t.h1("Categories", _class=cls("text-2xl", "mb-5"))
|
||||
with t.table(
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
)
|
||||
):
|
||||
with t.thead(_class=cls("bg-gray-200")):
|
||||
t.tr(
|
||||
t.th("Name", _class=cls("border", "p-2")),
|
||||
t.th("Weight", _class=cls("border", "p-2")),
|
||||
_class="h-10",
|
||||
)
|
||||
with t.tbody() as b:
|
||||
biggest_category_weight = max(
|
||||
[sum([i.weight for i in c.items]) for c in categories] or [0]
|
||||
)
|
||||
for category in categories:
|
||||
with t.tr(
|
||||
_class=cls("h-10", "hover:bg-purple-100", "m-3", "h-full")
|
||||
) as doc:
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"p-0",
|
||||
"m-0",
|
||||
*["font-bold"] if category.active else [],
|
||||
)
|
||||
):
|
||||
t.a(
|
||||
category.name,
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/inventory/category/{category.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
)
|
||||
with t.td(
|
||||
_class=cls("border", "p-0", "m-0"),
|
||||
style="position:relative;",
|
||||
):
|
||||
with t.a(
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/inventory/category/{category.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
):
|
||||
weight = sum([i.weight for i in category.items])
|
||||
width = int(weight / biggest_category_weight * 100)
|
||||
t.p(weight)
|
||||
t.div(
|
||||
_class=cls("bg-blue-600", "h-1.5"),
|
||||
style=f"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
|
||||
)
|
||||
# t.progress(max=biggest_category_weight, value=weight)
|
||||
with t.tr(
|
||||
_class=cls(
|
||||
"h-10", "hover:bg-purple-200", "bg-gray-300", "font-bold"
|
||||
)
|
||||
) as doc:
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a("Sum", _class=cls("block", "p-2", "m-2"))
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a(
|
||||
sum([sum([i.weight for i in c.items]) for c in categories]),
|
||||
_class=cls("block", "p-2", "m-2"),
|
||||
)
|
||||
|
||||
return doc
|
||||
143
python_flask/packager/components/InventoryItemDetails.py
Normal file
143
python_flask/packager/components/InventoryItemDetails.py
Normal file
@@ -0,0 +1,143 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
import urllib
|
||||
|
||||
class InventoryItemInfoEditRow:
|
||||
def __init__(self, baseurl, name, value, attribute, inputtype="text"):
|
||||
|
||||
with t.tr() as doc:
|
||||
t.form(
|
||||
id=f"edit-{attribute}",
|
||||
action=urllib.parse.urljoin(baseurl, f"edit/{attribute}/submit/"),
|
||||
target="_self",
|
||||
method="post",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
)
|
||||
with t.tr(_class=cls("h-full")):
|
||||
t.td(name, _class=cls("border", "p-2", "h-full"))
|
||||
with t.td(_class=cls("border", "p-0")):
|
||||
t._input(
|
||||
_class=cls("bg-blue-200", "w-full", "h-full", "px-2"),
|
||||
type=inputtype,
|
||||
id="item-edit-name",
|
||||
form=f"edit-{attribute}",
|
||||
name=attribute,
|
||||
value=value,
|
||||
)
|
||||
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-red-200",
|
||||
"hover:bg-red-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
id=f"edit-{attribute}-abort",
|
||||
):
|
||||
with t.a(href=baseurl):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
|
||||
with t.td(
|
||||
id=f"edit-{attribute}-save",
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-green-200",
|
||||
"hover:bg-green-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
):
|
||||
with t.button(type="submit", form=f"edit-{attribute}"):
|
||||
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class InventoryItemInfoNormalRow:
|
||||
def __init__(self, editable, baseurl, name, value, attribute):
|
||||
with t.tr() as doc:
|
||||
t.td(name, _class=cls("border", "p-2"))
|
||||
t.td(value, _class=cls("border", "p-2"))
|
||||
if editable:
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-blue-200",
|
||||
"hover:bg-blue-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
)
|
||||
):
|
||||
if editable:
|
||||
with t.a(
|
||||
# data_hx_post=f"/item/{item.id}/edit",
|
||||
href=f"?edit={attribute}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-pencil", "text-xl")),
|
||||
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class InventoryItemInfo:
|
||||
def __init__(self, item, edit, baseurl):
|
||||
with t.table(
|
||||
id="trip-table",
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
),
|
||||
) as doc:
|
||||
with t.tbody() as b:
|
||||
if edit == "name":
|
||||
InventoryItemInfoEditRow(baseurl, "Name", item.name, "name")
|
||||
else:
|
||||
InventoryItemInfoNormalRow(
|
||||
True, baseurl, "Name", item.name, "name"
|
||||
)
|
||||
|
||||
if edit == "weight":
|
||||
InventoryItemInfoEditRow(
|
||||
baseurl,
|
||||
"Weight",
|
||||
str(item.weight),
|
||||
"weight",
|
||||
inputtype="number",
|
||||
)
|
||||
else:
|
||||
InventoryItemInfoNormalRow(
|
||||
True, baseurl, "Weight", str(item.weight), "weight"
|
||||
)
|
||||
|
||||
self.doc = doc
|
||||
|
||||
|
||||
|
||||
class InventoryItemDetails:
|
||||
def __init__(
|
||||
self,
|
||||
item, edit, baseurl
|
||||
):
|
||||
with t.div(_class=cls("p-8")
|
||||
) as doc:
|
||||
t.h1("Item", _class=cls("text-2xl", "font-semibold"))
|
||||
with t.div(_class=cls("my-6")):
|
||||
InventoryItemInfo(item, edit, baseurl)
|
||||
|
||||
self.doc = doc
|
||||
|
||||
194
python_flask/packager/components/InventoryItemList.py
Normal file
194
python_flask/packager/components/InventoryItemList.py
Normal file
@@ -0,0 +1,194 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class InventoryItemRowEdit(object):
|
||||
def __init__(self, item, biggest_item_weight):
|
||||
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100")) as doc:
|
||||
with t.td(colspan=2, _class=cls("border-none", "bg-purple-100", "h-10")):
|
||||
with t.div():
|
||||
t.form(
|
||||
id="edit-item",
|
||||
action=f"/inventory/item/{item.id}/edit/submit/",
|
||||
target="_self",
|
||||
method="post",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
)
|
||||
with t.div(_class=cls("flex", "flex-row", "h-full")):
|
||||
with t.span(
|
||||
_class=cls(
|
||||
"border",
|
||||
"border-1",
|
||||
"border-purple-500",
|
||||
"bg-purple-100",
|
||||
"mr-1",
|
||||
)
|
||||
):
|
||||
t._input(
|
||||
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
|
||||
type="text",
|
||||
id="item-edit-name",
|
||||
form="edit-item",
|
||||
name="name",
|
||||
value=item.name,
|
||||
**{
|
||||
"x-on:input": "edit_submit_enabled = $event.srcElement.value.trim().length !== 0;"
|
||||
},
|
||||
)
|
||||
with t.span(
|
||||
_class=cls(
|
||||
"border", "border-1", "border-purple-500", "bg-purple-100"
|
||||
)
|
||||
):
|
||||
t._input(
|
||||
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
|
||||
type="text",
|
||||
id="item-edit-weight",
|
||||
name="weight",
|
||||
form="edit-item",
|
||||
value=item.weight,
|
||||
)
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-red-200",
|
||||
"hover:bg-red-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
id="edit-item-abort",
|
||||
):
|
||||
with t.a(
|
||||
href=f"/inventory/category/{item.category.id}",
|
||||
# data_hx_post=f"/item/{item.id}/edit/cancel",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
|
||||
with t.td(
|
||||
id="edit-item-save",
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-green-200",
|
||||
"hover:bg-green-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
# **{
|
||||
# "x-bind:class": 'edit_submit_enabled || "cursor-not-allowed opacity-50"',
|
||||
# "x-on:htmx:before-request": "(e) => edit_submit_enabled || e.preventDefault()",
|
||||
# },
|
||||
):
|
||||
with t.button(type="submit", form="edit-item"):
|
||||
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class InventoryItemRowNormal(object):
|
||||
def __init__(self, item, biggest_item_weight):
|
||||
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100")) as doc:
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a(
|
||||
item.name,
|
||||
href=f"/inventory/item/{item.id}/",
|
||||
_class=cls("p-2", "w-full", "inline-block"),
|
||||
)
|
||||
width = int(item.weight / biggest_item_weight * 100)
|
||||
with t.td(_class=cls("border", "px-2"), style="position:relative;"):
|
||||
t.p(str(item.weight))
|
||||
t.div(
|
||||
_class=cls("bg-blue-600", "h-1.5"),
|
||||
style=";".join(
|
||||
[
|
||||
f"width: {width}%",
|
||||
"position:absolute",
|
||||
"left:0",
|
||||
"bottom:0",
|
||||
"right:0",
|
||||
]
|
||||
),
|
||||
)
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-blue-200",
|
||||
"hover:bg-blue-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
)
|
||||
):
|
||||
with t.a(
|
||||
# data_hx_post=f"/item/{item.id}/edit",
|
||||
href=f"?edit={item.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
id="start-edit-item",
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-pencil", "text-xl")),
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-red-200",
|
||||
"hover:bg-red-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
)
|
||||
):
|
||||
with t.a(
|
||||
# data_hx_delete=f"/item/{item.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
href=f"/inventory/item/{item.id}/delete"
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-delete", "text-xl"))
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class InventoryItemRow(object):
|
||||
def __init__(self, item, biggest_item_weight):
|
||||
if item.edit:
|
||||
self.doc = InventoryItemRowEdit(item, biggest_item_weight)
|
||||
else:
|
||||
self.doc = InventoryItemRowNormal(item, biggest_item_weight)
|
||||
|
||||
|
||||
def InventoryItemList(items):
|
||||
with t.div() as doc:
|
||||
t.h1("Items", _class=cls("text-2xl", "mb-5"))
|
||||
with t.table(
|
||||
id="item-table",
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
),
|
||||
):
|
||||
with t.thead(_class=cls("bg-gray-200")):
|
||||
t.tr(
|
||||
t.th("Name", _class=cls("border", "p-2")),
|
||||
t.th("Weight", _class=cls("border", "p-2")),
|
||||
_class="h-10",
|
||||
)
|
||||
with t.tbody() as b:
|
||||
biggest_item_weight = max([i.weight for i in items] or [0])
|
||||
if biggest_item_weight <= 0:
|
||||
biggest_item_weight = 1
|
||||
for item in items:
|
||||
InventoryItemRow(item, biggest_item_weight).doc
|
||||
|
||||
return doc
|
||||
33
python_flask/packager/components/InventoryItemManager.py
Normal file
33
python_flask/packager/components/InventoryItemManager.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from .InventoryItemList import InventoryItemList
|
||||
from .InventoryCategoryList import InventoryCategoryList
|
||||
from .InventoryNewItem import InventoryNewItem
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class InventoryItemManager:
|
||||
def __init__(
|
||||
self,
|
||||
categories,
|
||||
items,
|
||||
active_category,
|
||||
name=None,
|
||||
description=None,
|
||||
error=False,
|
||||
errormsg=None,
|
||||
):
|
||||
assert not (error and not errormsg)
|
||||
with t.div(
|
||||
id="pkglist-item-manager", _class=cls("p-8", "grid", "grid-cols-4", "gap-3")
|
||||
) as doc:
|
||||
with t.div(_class=cls("col-span-2")):
|
||||
InventoryCategoryList(categories),
|
||||
with t.div(_class=cls("col-span-2")):
|
||||
InventoryItemList(items),
|
||||
InventoryNewItem(categories, active_category)
|
||||
|
||||
self.doc = doc
|
||||
152
python_flask/packager/components/InventoryNewItem.py
Normal file
152
python_flask/packager/components/InventoryNewItem.py
Normal file
@@ -0,0 +1,152 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
def InventoryNewItem(categories, active_category, name=None, weight=None):
|
||||
with t.form(
|
||||
id="new-item",
|
||||
name="new_item",
|
||||
# data_hx_post="/list/",
|
||||
# data_hx_target="#item-manager",
|
||||
# data_hx_swap="outerHTML",
|
||||
action="/inventory/item/",
|
||||
target="_self",
|
||||
method="post",
|
||||
_class=cls("mt-8", "p-5", "border-2", "border-gray-200"),
|
||||
# **{
|
||||
# "x-on:htmx:before-request": "(e) => submit_enabled || e.preventDefault()",
|
||||
# "x-data": alpinedata(
|
||||
# {
|
||||
# "submit_enabled": (
|
||||
# jsbool(not error)
|
||||
# + '&& document.getElementById("listname").value.trim().length !== 0'
|
||||
# )
|
||||
# }
|
||||
# ),
|
||||
# },
|
||||
) as doc:
|
||||
with t.div(_class=cls("mb-5", "flex", "flex-row", "items-center")):
|
||||
t.span(_class=cls("mdi", "mdi-playlist-plus", "text-2xl", "mr-4"))
|
||||
t.p("Add new item", _class=cls("inline", "text-xl"))
|
||||
with t.div(_class=cls("w-11/12", "mx-auto")):
|
||||
with t.div(_class=cls("pb-8")):
|
||||
with t.div(
|
||||
_class=cls("flex", "flex-row", "justify-center", "items-start")
|
||||
):
|
||||
t.label(
|
||||
"Name",
|
||||
_for="item-name",
|
||||
_class=cls("font-bold", "w-1/2", "p-2", "text-center"),
|
||||
)
|
||||
with t.span(_class=cls("w-1/2")):
|
||||
t._input(
|
||||
type="text",
|
||||
id="item-name",
|
||||
name="name",
|
||||
**{"value": name} if name is not None else {},
|
||||
# data_hx_target="#new-item",
|
||||
# data_hx_post="/item/name/validate",
|
||||
# data_hx_swap="outerHTML",
|
||||
# data_hx_trigger="changed",
|
||||
_class=cls(
|
||||
"block",
|
||||
"w-full",
|
||||
"p-2",
|
||||
"bg-gray-50",
|
||||
# "appearance-none" if error else None,
|
||||
"border-2",
|
||||
# "border-red-400" if error else "border-gray-300",
|
||||
"rounded",
|
||||
"focus:outline-none",
|
||||
"focus:bg-white",
|
||||
# "focus:border-purple-500" if not error else None,
|
||||
),
|
||||
# **{
|
||||
# "x-on:input": "submit_enabled = $event.srcElement.value.trim().length !== 0;"
|
||||
# },
|
||||
)
|
||||
# t.p(
|
||||
# errormsg, _class=cls("mt-1", "text-red-400", "text-sm")
|
||||
# ) if error else None
|
||||
with t.div(
|
||||
_class=cls("flex", "flex-row", "justify-center", "items-center", "pb-8")
|
||||
):
|
||||
t.label(
|
||||
"Weight",
|
||||
_for="item-weight",
|
||||
_class=cls("font-bold", "w-1/2", "text-center"),
|
||||
)
|
||||
with t.span(_class=cls("w-1/2")):
|
||||
t._input(
|
||||
type="text",
|
||||
id="item-weight",
|
||||
name="weight",
|
||||
**{"value": weight} if weight is not None else {},
|
||||
_class=cls(
|
||||
"block",
|
||||
"w-full",
|
||||
"p-2",
|
||||
"bg-gray-50",
|
||||
"appearance-none",
|
||||
"border-2",
|
||||
"border-gray-300",
|
||||
"rounded",
|
||||
"focus:outline-none",
|
||||
"focus:bg-white",
|
||||
"focus:border-purple-500",
|
||||
),
|
||||
)
|
||||
with t.div(
|
||||
_class=cls("flex", "flex-row", "justify-center", "items-center", "pb-8")
|
||||
):
|
||||
t.label(
|
||||
"Category",
|
||||
_for="item-category",
|
||||
_class=cls("font-bold", "w-1/2", "text-center"),
|
||||
)
|
||||
with t.span(_class=cls("w-1/2")):
|
||||
with t.select(
|
||||
id="item-category",
|
||||
name="category",
|
||||
_class=cls(
|
||||
"block",
|
||||
"w-full",
|
||||
"p-2",
|
||||
"bg-gray-50",
|
||||
# "appearance-none",
|
||||
"border-2",
|
||||
"border-gray-300",
|
||||
"rounded",
|
||||
"focus:outline-none",
|
||||
"focus:bg-white",
|
||||
"focus:border-purple-500",
|
||||
),
|
||||
):
|
||||
for category in categories:
|
||||
if active_category and category.id == active_category.id:
|
||||
t.option(
|
||||
category.name, value=category.id, selected=True
|
||||
)
|
||||
else:
|
||||
t.option(category.name, value=category.id)
|
||||
t._input(
|
||||
type="submit",
|
||||
value="Add",
|
||||
# **{
|
||||
# "x-bind:class": 'submit_enabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"'
|
||||
# },
|
||||
_class=cls(
|
||||
"py-2",
|
||||
"border-2",
|
||||
"rounded",
|
||||
"border-gray-300",
|
||||
"mx-auto",
|
||||
"w-full",
|
||||
# "hover:border-purple-500" if not error else None,
|
||||
# "hover:bg-purple-200" if not error else None,
|
||||
),
|
||||
)
|
||||
return doc
|
||||
@@ -1,33 +0,0 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
def ItemList(items):
|
||||
with t.div(id="packagelist-table") as doc:
|
||||
t.h1("Items", _class=cls("text-2xl", "mb-5"))
|
||||
with t.table(
|
||||
id="packagelist-table",
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
),
|
||||
):
|
||||
with t.thead(_class=cls("bg-gray-200")):
|
||||
t.tr(
|
||||
t.th("Name", _class=cls("border", "p-2")),
|
||||
t.th("Weight", _class=cls("border", "p-2")),
|
||||
_class="h-10",
|
||||
)
|
||||
with t.tbody() as b:
|
||||
for item in items:
|
||||
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-200")) as doc:
|
||||
t.td(item.name, _class=cls("border", "px-2")),
|
||||
t.td(str(item.weight), _class=cls("border", "px-2")),
|
||||
|
||||
return doc
|
||||
@@ -1,120 +0,0 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
def NewPackageList(name=None, description=None, error=False, errormsg=None):
|
||||
assert not (error and not errormsg)
|
||||
with t.form(
|
||||
id="new-pkglist",
|
||||
name="new_pkglist",
|
||||
data_hx_post="/list/",
|
||||
data_hx_target="#pkglist-manager",
|
||||
data_hx_swap="outerHTML",
|
||||
action="/list/",
|
||||
target="_self",
|
||||
method="post",
|
||||
_class=cls("mt-8", "p-5", "border-2", "border-gray-200"),
|
||||
**{
|
||||
"x-on:htmx:before-request": "(e) => submit_enabled || e.preventDefault()",
|
||||
"x-data": alpinedata(
|
||||
{
|
||||
"submit_enabled": (
|
||||
jsbool(not error)
|
||||
+ '&& document.getElementById("listname").value.trim().length !== 0'
|
||||
)
|
||||
}
|
||||
),
|
||||
},
|
||||
) as doc:
|
||||
with t.div(_class=cls("mb-5", "flex", "flex-row", "items-center")):
|
||||
t.span(_class=cls("mdi", "mdi-playlist-plus", "text-2xl", "mr-4"))
|
||||
t.p("Add new package list", _class=cls("inline", "text-xl"))
|
||||
with t.div(_class=cls("w-11/12", "mx-auto")):
|
||||
with t.div(_class=cls("pb-8")):
|
||||
with t.div(
|
||||
_class=cls("flex", "flex-row", "justify-center", "items-start")
|
||||
):
|
||||
t.label(
|
||||
"Name",
|
||||
_for="listname",
|
||||
_class=cls("font-bold", "w-1/2", "p-2", "text-center"),
|
||||
)
|
||||
with t.span(_class=cls("w-1/2")):
|
||||
t._input(
|
||||
type="text",
|
||||
id="listname",
|
||||
name="name",
|
||||
**{"value": name} if name is not None else {},
|
||||
data_hx_target="#new-pkglist",
|
||||
data_hx_post="/list/name/validate",
|
||||
data_hx_swap="outerHTML",
|
||||
data_hx_trigger="changed",
|
||||
_class=cls(
|
||||
"block",
|
||||
"w-full",
|
||||
"p-2",
|
||||
"bg-gray-50",
|
||||
"appearance-none" if error else None,
|
||||
"border-2",
|
||||
"border-red-400" if error else "border-gray-300",
|
||||
"rounded",
|
||||
"focus:outline-none",
|
||||
"focus:bg-white",
|
||||
"focus:border-purple-500" if not error else None,
|
||||
),
|
||||
**{
|
||||
"x-on:input": "submit_enabled = $event.srcElement.value.trim().length !== 0;"
|
||||
},
|
||||
)
|
||||
t.p(
|
||||
errormsg, _class=cls("mt-1", "text-red-400", "text-sm")
|
||||
) if error else None
|
||||
with t.div(
|
||||
_class=cls("flex", "flex-row", "justify-center", "items-center", "pb-8")
|
||||
):
|
||||
t.label(
|
||||
"Description",
|
||||
_for="listdesc",
|
||||
_class=cls("font-bold", "w-1/2", "text-center"),
|
||||
)
|
||||
with t.span(_class=cls("w-1/2")):
|
||||
t._input(
|
||||
type="text",
|
||||
id="listdesc",
|
||||
name="description",
|
||||
**{"value": description} if description is not None else {},
|
||||
_class=cls(
|
||||
"block",
|
||||
"w-full",
|
||||
"p-2",
|
||||
"bg-gray-50",
|
||||
"appearance-none",
|
||||
"border-2",
|
||||
"border-gray-300",
|
||||
"rounded",
|
||||
"focus:outline-none",
|
||||
"focus:bg-white",
|
||||
"focus:border-purple-500",
|
||||
),
|
||||
)
|
||||
t._input(
|
||||
type="submit",
|
||||
value="Add",
|
||||
**{
|
||||
"x-bind:class": 'submit_enabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"'
|
||||
},
|
||||
_class=cls(
|
||||
"py-2",
|
||||
"border-2",
|
||||
"rounded",
|
||||
"border-gray-300",
|
||||
"mx-auto",
|
||||
"w-full",
|
||||
"hover:border-purple-500" if not error else None,
|
||||
"hover:bg-purple-200" if not error else None,
|
||||
),
|
||||
)
|
||||
return doc
|
||||
145
python_flask/packager/components/NewTrip.py
Normal file
145
python_flask/packager/components/NewTrip.py
Normal file
@@ -0,0 +1,145 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
def NewTrip(name=None):
|
||||
with t.form(
|
||||
id="new-trip",
|
||||
name="new_trip",
|
||||
# data_hx_post="/list/",
|
||||
# data_hx_target="#trip-manager",
|
||||
# data_hx_swap="outerHTML",
|
||||
action="/trip/",
|
||||
target="_self",
|
||||
method="post",
|
||||
_class=cls("mt-8", "p-5", "border-2", "border-gray-200"),
|
||||
# **{
|
||||
# "x-on:htmx:before-request": "(e) => submit_enabled || e.preventDefault()",
|
||||
# "x-data": alpinedata(
|
||||
# {
|
||||
# "submit_enabled": (
|
||||
# jsbool(not error)
|
||||
# + '&& document.getElementById("listname").value.trim().length !== 0'
|
||||
# )
|
||||
# }
|
||||
# ),
|
||||
# },
|
||||
) as doc:
|
||||
with t.div(_class=cls("mb-5", "flex", "flex-row", "trips-center")):
|
||||
t.span(_class=cls("mdi", "mdi-playlist-plus", "text-2xl", "mr-4"))
|
||||
t.p("Add new trip", _class=cls("inline", "text-xl"))
|
||||
with t.div(_class=cls("w-11/12", "mx-auto")):
|
||||
with t.div(_class=cls("pb-8")):
|
||||
with t.div(
|
||||
_class=cls("flex", "flex-row", "justify-center", "trips-start")
|
||||
):
|
||||
t.label(
|
||||
"Name",
|
||||
_for="trip-name",
|
||||
_class=cls("font-bold", "w-1/2", "p-2", "text-center"),
|
||||
)
|
||||
with t.span(_class=cls("w-1/2")):
|
||||
t._input(
|
||||
type="text",
|
||||
id="trip-name",
|
||||
name="name",
|
||||
**{"value": name} if name is not None else {},
|
||||
# data_hx_target="#new-trip",
|
||||
# data_hx_post="/trip/name/validate",
|
||||
# data_hx_swap="outerHTML",
|
||||
# data_hx_trigger="changed",
|
||||
_class=cls(
|
||||
"block",
|
||||
"w-full",
|
||||
"p-2",
|
||||
"bg-gray-50",
|
||||
# "appearance-none" if error else None,
|
||||
"border-2",
|
||||
# "border-red-400" if error else "border-gray-300",
|
||||
"rounded",
|
||||
"focus:outline-none",
|
||||
"focus:bg-white",
|
||||
# "focus:border-purple-500" if not error else None,
|
||||
),
|
||||
# **{
|
||||
# "x-on:input": "submit_enabled = $event.srcElement.value.trim().length !== 0;"
|
||||
# },
|
||||
)
|
||||
# t.p(
|
||||
# errormsg, _class=cls("mt-1", "text-red-400", "text-sm")
|
||||
# ) if error else None
|
||||
with t.div(
|
||||
_class=cls("flex", "flex-row", "justify-center", "trips-center", "pb-8")
|
||||
):
|
||||
t.label(
|
||||
"Start date",
|
||||
_for="start-date",
|
||||
_class=cls("font-bold", "w-1/2", "text-center"),
|
||||
)
|
||||
with t.span(_class=cls("w-1/2")):
|
||||
t._input(
|
||||
type="date",
|
||||
id="start-date",
|
||||
name="start-date",
|
||||
_class=cls(
|
||||
"block",
|
||||
"w-full",
|
||||
"p-2",
|
||||
"bg-gray-50",
|
||||
"appearance-none",
|
||||
"border-2",
|
||||
"border-gray-300",
|
||||
"rounded",
|
||||
"focus:outline-none",
|
||||
"focus:bg-white",
|
||||
"focus:border-purple-500",
|
||||
),
|
||||
)
|
||||
with t.div(
|
||||
_class=cls("flex", "flex-row", "justify-center", "trips-center", "pb-8")
|
||||
):
|
||||
t.label(
|
||||
"End date",
|
||||
_for="end-date",
|
||||
_class=cls("font-bold", "w-1/2", "text-center"),
|
||||
)
|
||||
with t.span(_class=cls("w-1/2")):
|
||||
t._input(
|
||||
type="date",
|
||||
id="end-date",
|
||||
name="end-date",
|
||||
_class=cls(
|
||||
"block",
|
||||
"w-full",
|
||||
"p-2",
|
||||
"bg-gray-50",
|
||||
"appearance-none",
|
||||
"border-2",
|
||||
"border-gray-300",
|
||||
"rounded",
|
||||
"focus:outline-none",
|
||||
"focus:bg-white",
|
||||
"focus:border-purple-500",
|
||||
),
|
||||
)
|
||||
t._input(
|
||||
type="submit",
|
||||
value="Add",
|
||||
# **{
|
||||
# "x-bind:class": 'submit_enabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"'
|
||||
# },
|
||||
_class=cls(
|
||||
"py-2",
|
||||
"border-2",
|
||||
"rounded",
|
||||
"border-gray-300",
|
||||
"mx-auto",
|
||||
"w-full",
|
||||
# "hover:border-purple-500" if not error else None,
|
||||
# "hover:bg-purple-200" if not error else None,
|
||||
),
|
||||
)
|
||||
return doc
|
||||
@@ -1,21 +0,0 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from . import CategoryList, ItemList
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class PackageListItemManager:
|
||||
def __init__(
|
||||
self, categories, items, name=None, description=None, error=False, errormsg=None
|
||||
):
|
||||
assert not (error and not errormsg)
|
||||
with t.div(id="pkglist-item-manager", _class=cls("p-8", "grid", "grid-cols-4", "gap-3")) as doc:
|
||||
with t.div(_class=cls("col-span-1")):
|
||||
CategoryList(categories),
|
||||
with t.div(_class=cls("col-span-3")):
|
||||
ItemList(items),
|
||||
|
||||
self.doc = doc
|
||||
@@ -1,21 +0,0 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from . import NewPackageList, PackageListTable
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class PackageListManager:
|
||||
def __init__(
|
||||
self, pkglists, name=None, description=None, error=False, errormsg=None
|
||||
):
|
||||
assert not (error and not errormsg)
|
||||
with t.div(id="pkglist-manager", _class=cls("p-8", "max-w-xl")) as doc:
|
||||
PackageListTable(pkglists),
|
||||
NewPackageList(
|
||||
name=name, description=description, error=error, errormsg=errormsg
|
||||
)
|
||||
|
||||
self.doc = doc
|
||||
@@ -1,197 +0,0 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class PackageListTableRowEdit:
|
||||
def __init__(self, pkglist):
|
||||
error, errormsg = pkglist.error, pkglist.errormsg
|
||||
assert not (error and not errormsg)
|
||||
with t.tr(
|
||||
_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-200"),
|
||||
id="pkglist-edit-row",
|
||||
**{
|
||||
"x-data": '{ edit_submit_enabled: document.getElementById("listedit-name").value.trim().length !== 0 }'
|
||||
},
|
||||
) as doc:
|
||||
with t.td(colspan=3, _class=cls("border-none", "bg-purple-100", "h-10")):
|
||||
with t.div():
|
||||
t.form(
|
||||
id="edit-pkglist",
|
||||
action=f"/list/{pkglist.id}/edit/submit/",
|
||||
target="_self",
|
||||
method="post",
|
||||
data_hx_post=f"/list/{pkglist.id}/edit/submit",
|
||||
data_hx_target="closest tr",
|
||||
data_hx_swap="outerHTML",
|
||||
)
|
||||
if error:
|
||||
t.p(errormsg, _class=cls("text-red-400", "text-sm"))
|
||||
with t.div(_class=cls("flex", "flex-row", "h-full")):
|
||||
with t.span(
|
||||
_class=cls(
|
||||
"border",
|
||||
"border-1",
|
||||
"border-purple-500",
|
||||
"bg-purple-100",
|
||||
"mr-1",
|
||||
)
|
||||
):
|
||||
t._input(
|
||||
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
|
||||
type="text",
|
||||
id="listedit-name",
|
||||
form="edit-pkglist",
|
||||
name="name",
|
||||
value=pkglist.name if error else pkglist.name,
|
||||
**{
|
||||
"x-on:input": "edit_submit_enabled = $event.srcElement.value.trim().length !== 0;"
|
||||
},
|
||||
)
|
||||
with t.span(
|
||||
_class=cls(
|
||||
"border", "border-1", "border-purple-500", "bg-purple-100"
|
||||
)
|
||||
):
|
||||
t._input(
|
||||
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
|
||||
type="text",
|
||||
name="description",
|
||||
form="edit-pkglist",
|
||||
value=pkglist.description,
|
||||
)
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-red-200",
|
||||
"hover:bg-red-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
id="edit-packagelist-abort",
|
||||
):
|
||||
with t.a(
|
||||
href="/",
|
||||
data_hx_post=f"/list/{pkglist.id}/edit/cancel",
|
||||
data_hx_target="closest tr",
|
||||
data_hx_swap="outerHTML",
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
|
||||
with t.td(
|
||||
id="edit-packagelist-save",
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-green-200",
|
||||
"hover:bg-green-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
**{
|
||||
"x-bind:class": 'edit_submit_enabled || "cursor-not-allowed opacity-50"',
|
||||
"x-on:htmx:before-request": "(e) => edit_submit_enabled || e.preventDefault()",
|
||||
},
|
||||
):
|
||||
with t.button(type="submit", form="edit-pkglist"):
|
||||
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class PackageListTableRowNormal:
|
||||
def __init__(self, pkglist):
|
||||
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-200")) as doc:
|
||||
t.td(pkglist.name, _class=cls("border", "px-2")),
|
||||
t.td(str(pkglist.description), _class=cls("border", "px-2")),
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-red-200",
|
||||
"hover:bg-red-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
)
|
||||
):
|
||||
with t.a(
|
||||
data_hx_delete=f"/list/{pkglist.id}",
|
||||
data_hx_target="closest tr",
|
||||
data_hx_swap="outerHTML",
|
||||
href=f"/list/{pkglist.id}/delete",
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-delete", "text-xl"))
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-blue-200",
|
||||
"hover:bg-blue-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
)
|
||||
):
|
||||
with t.a(
|
||||
data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/?edit={pkglist.id}",
|
||||
data_hx_target="closest tr",
|
||||
data_hx_swap="outerHTML",
|
||||
id="edit-packagelist",
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-pencil", "text-xl")),
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-green-200",
|
||||
"hover:bg-green-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
)
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-arrow-right", "text-xl"))
|
||||
# data_hx_post=f"/list/{pkglist.id}/show",
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class PackageListTableRow:
|
||||
def __init__(self, pkglist):
|
||||
if pkglist.edit:
|
||||
self.doc = PackageListTableRowEdit(pkglist).doc
|
||||
else:
|
||||
self.doc = PackageListTableRowNormal(pkglist).doc
|
||||
|
||||
|
||||
def PackageListTable(pkglists):
|
||||
with t.div(id="packagelist-table") as doc:
|
||||
t.h1("Package Lists", _class=cls("text-2xl", "mb-5"))
|
||||
with t.table(
|
||||
id="packagelist-table",
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
),
|
||||
):
|
||||
with t.thead(_class=cls("bg-gray-200")):
|
||||
t.tr(
|
||||
t.th("Name", _class=cls("border", "p-2")),
|
||||
t.th("Description", _class=cls("border", "p-2")),
|
||||
t.th(_class=cls("border p-2")),
|
||||
t.th(_class=cls("border p-2")),
|
||||
t.th(_class=cls("border p-2")),
|
||||
_class="h-10",
|
||||
)
|
||||
with t.tbody() as b:
|
||||
for pkglist in pkglists:
|
||||
PackageListTableRow(pkglist).doc
|
||||
|
||||
return doc
|
||||
134
python_flask/packager/components/TripCategoryList.py
Normal file
134
python_flask/packager/components/TripCategoryList.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class TripCategoryList:
|
||||
def __init__(self, trip, categories):
|
||||
with t.div() as doc:
|
||||
t.h1("Categories", _class=cls("text-xl", "mb-5"))
|
||||
with t.table(
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
)
|
||||
):
|
||||
with t.thead(_class=cls("bg-gray-200")):
|
||||
t.tr(
|
||||
t.th("Name", _class=cls("border", "p-2")),
|
||||
t.th("Weight", _class=cls("border", "p-2")),
|
||||
t.th("Max", _class=cls("border", "p-2")),
|
||||
_class="h-10",
|
||||
)
|
||||
with t.tbody() as b:
|
||||
for category in categories:
|
||||
items = [
|
||||
i
|
||||
for i in trip.items
|
||||
if i.inventory_item.category_id == category.id
|
||||
]
|
||||
biggest_category_weight = 1
|
||||
|
||||
for cat in categories:
|
||||
category_items = [
|
||||
i
|
||||
for i in trip.items
|
||||
if i.inventory_item.category_id == cat.id
|
||||
]
|
||||
weight_sum = sum(
|
||||
[
|
||||
i.inventory_item.weight
|
||||
for i in category_items
|
||||
if i.pick
|
||||
]
|
||||
)
|
||||
if weight_sum > biggest_category_weight:
|
||||
biggest_category_weight = weight_sum
|
||||
|
||||
weight = sum([i.inventory_item.weight for i in items if i.pick])
|
||||
|
||||
with t.tr(
|
||||
_class=cls(
|
||||
"h-10",
|
||||
"hover:bg-purple-100",
|
||||
"m-3",
|
||||
"h-full",
|
||||
*["bg-blue-100"]
|
||||
if category.active
|
||||
else (
|
||||
["bg-red-100"]
|
||||
if any([i.pick != i.pack for i in items])
|
||||
else []
|
||||
),
|
||||
)
|
||||
) as doc:
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"p-0",
|
||||
"m-0",
|
||||
*["font-bold"] if category.active else [],
|
||||
)
|
||||
):
|
||||
t.a(
|
||||
category.name,
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/trip/{trip.id}/category/{category.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
)
|
||||
|
||||
with t.td(
|
||||
_class=cls("border", "p-0", "m-0"),
|
||||
style="position:relative;",
|
||||
):
|
||||
with t.a(
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/category/{category.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
):
|
||||
width = int(weight / biggest_category_weight * 100)
|
||||
t.p(weight)
|
||||
t.div(
|
||||
_class=cls("bg-blue-600", "h-1.5"),
|
||||
style=f"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
|
||||
)
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a(
|
||||
sum([i.inventory_item.weight for i in items]),
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/category/{category.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
)
|
||||
# with t.tr(_class=cls("h-10", "hover:bg-purple-200", "bg-gray-300", "font-bold")) as doc:
|
||||
# with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
# t.a(
|
||||
# "Sum",
|
||||
# _class=cls("block", "p-2", "m-2"),
|
||||
# )
|
||||
# with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
# t.a(
|
||||
# sum(([i.inventory_item.weight for i in c.items]) for c in categories]),
|
||||
# _class=cls("block", "p-2", "m-2"),
|
||||
# )
|
||||
# with t.td(_class=cls("border", "p-0", "m-0", "font-normal")):
|
||||
# t.a(
|
||||
# sum(([i.inventory_item.weight for i in c.items]) for c in categories]),
|
||||
# _class=cls("block", "p-2", "m-2"),
|
||||
# )
|
||||
|
||||
self.doc = doc
|
||||
266
python_flask/packager/components/TripItemList.py
Normal file
266
python_flask/packager/components/TripItemList.py
Normal file
@@ -0,0 +1,266 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class TripItemRowEdit(object):
|
||||
def __init__(self, item, biggest_item_weight):
|
||||
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100")) as doc:
|
||||
with t.td(_class=cls("border")):
|
||||
with t.a(
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"?item_{'unpick' if item.pick else 'pick'}={item.inventory_item.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls(
|
||||
"inline-block",
|
||||
"p-2",
|
||||
"m-0",
|
||||
"w-full",
|
||||
"flex",
|
||||
"justify-center",
|
||||
"content-center",
|
||||
),
|
||||
):
|
||||
t._input(
|
||||
type="checkbox", **({"checked": True} if item.pick else {})
|
||||
)
|
||||
with t.td(_class=cls("border")):
|
||||
with t.a(
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"?item_{'unpack' if item.pack else 'pack'}={item.inventory_item.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls(
|
||||
"inline-block",
|
||||
"p-2",
|
||||
"m-0",
|
||||
"w-full",
|
||||
"flex",
|
||||
"justify-center",
|
||||
"content-center",
|
||||
),
|
||||
):
|
||||
t._input(
|
||||
type="checkbox", **({"checked": True} if item.pack else {})
|
||||
)
|
||||
# with t.td(item.name, _class=cls("border", "px-2")),
|
||||
with t.td(colspan=2, _class=cls("border-none", "bg-purple-100", "h-10")):
|
||||
with t.div():
|
||||
t.form(
|
||||
id="edit-item",
|
||||
action=f"/inventory/item/{item.inventory_item.id}/edit/submit/",
|
||||
target="_self",
|
||||
method="post",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
)
|
||||
with t.div(_class=cls("flex", "flex-row", "h-full")):
|
||||
with t.span(
|
||||
_class=cls(
|
||||
"border",
|
||||
"border-1",
|
||||
"border-purple-500",
|
||||
"bg-purple-100",
|
||||
"mr-1",
|
||||
)
|
||||
):
|
||||
t._input(
|
||||
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
|
||||
type="text",
|
||||
id="item-edit-name",
|
||||
form="edit-item",
|
||||
name="name",
|
||||
value=item.name,
|
||||
**{
|
||||
"x-on:input": "edit_submit_enabled = $event.srcElement.value.trim().length !== 0;"
|
||||
},
|
||||
)
|
||||
with t.span(
|
||||
_class=cls(
|
||||
"border", "border-1", "border-purple-500", "bg-purple-100"
|
||||
)
|
||||
):
|
||||
t._input(
|
||||
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
|
||||
type="text",
|
||||
id="item-edit-weight",
|
||||
name="weight",
|
||||
form="edit-item",
|
||||
value=item.weight,
|
||||
)
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-red-200",
|
||||
"hover:bg-red-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
id="edit-item-abort",
|
||||
):
|
||||
with t.a(
|
||||
href=f"/inventory/category/{item.category.id}",
|
||||
# data_hx_post=f"/item/{item.id}/edit/cancel",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
|
||||
with t.td(
|
||||
id="edit-item-save",
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-green-200",
|
||||
"hover:bg-green-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
# **{
|
||||
# "x-bind:class": 'edit_submit_enabled || "cursor-not-allowed opacity-50"',
|
||||
# "x-on:htmx:before-request": "(e) => edit_submit_enabled || e.preventDefault()",
|
||||
# },
|
||||
):
|
||||
with t.button(type="submit", form="edit-item"):
|
||||
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class TripItemRowNormal(object):
|
||||
def __init__(self, item, biggest_item_weight):
|
||||
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100")) as doc:
|
||||
with t.td(_class=cls("border")):
|
||||
with t.a(
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"?item_{'unpick' if item.pick else 'pick'}={item.inventory_item.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls(
|
||||
"inline-block",
|
||||
"p-2",
|
||||
"m-0",
|
||||
"w-full",
|
||||
"justify-center",
|
||||
"content-center",
|
||||
"flex",
|
||||
),
|
||||
):
|
||||
t._input(
|
||||
type="checkbox",
|
||||
form=f"toggle-item-pick",
|
||||
name="pick-{item.inventory_item.id}",
|
||||
**({"checked": True} if item.pick else {}),
|
||||
) # , xstyle="position: relative;z-index: 1;pointer-events: auto; ")
|
||||
with t.td(_class=cls("border")):
|
||||
with t.a(
|
||||
id="select-category",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"?item_{'unpack' if item.pack else 'pack'}={item.inventory_item.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls(
|
||||
"inline-block",
|
||||
"p-2",
|
||||
"m-0",
|
||||
"w-full",
|
||||
"flex",
|
||||
"justify-center",
|
||||
"content-center",
|
||||
),
|
||||
):
|
||||
t._input(
|
||||
type="checkbox",
|
||||
form=f"toggle-item-pack",
|
||||
name="pack-{item.inventory_item.id}",
|
||||
**({"checked": True} if item.pack else {}),
|
||||
) # , xstyle="position: relative;z-index: 1;pointer-events: auto; ")
|
||||
t.td(
|
||||
item.inventory_item.name,
|
||||
_class=cls(
|
||||
"border",
|
||||
"px-2",
|
||||
*(("bg-red-100",) if item.pick != item.pack else ()),
|
||||
),
|
||||
),
|
||||
width = int(item.inventory_item.weight / biggest_item_weight * 100)
|
||||
with t.td(_class=cls("border", "px-2"), style="position:relative;"):
|
||||
t.p(str(item.inventory_item.weight))
|
||||
t.div(
|
||||
_class=cls("bg-blue-600", "h-1.5"),
|
||||
style=";".join(
|
||||
[
|
||||
f"width: {width}%",
|
||||
"position:absolute",
|
||||
"left:0",
|
||||
"bottom:0",
|
||||
"right:0",
|
||||
]
|
||||
),
|
||||
)
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class TripItemRow(object):
|
||||
def __init__(self, item, biggest_item_weight):
|
||||
if item.edit:
|
||||
self.doc = TripItemRowEdit(item, biggest_item_weight)
|
||||
else:
|
||||
self.doc = TripItemRowNormal(item, biggest_item_weight)
|
||||
|
||||
|
||||
class TripItemList:
|
||||
def __init__(self, trip, active_category):
|
||||
with t.div() as doc:
|
||||
t.h1("Items", _class=cls("text-xl", "mb-5"))
|
||||
with t.table(
|
||||
id="item-table",
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
),
|
||||
):
|
||||
with t.thead(_class=cls("bg-gray-200")):
|
||||
t.tr(
|
||||
t.th("Take?", _class=cls("border", "p-2", "w-0")),
|
||||
t.th("Packed?", _class=cls("border", "p-2", "w-0")),
|
||||
t.th("Name", _class=cls("border", "p-2")),
|
||||
t.th("Weight", _class=cls("border", "p-2")),
|
||||
_class="h-10",
|
||||
)
|
||||
with t.tbody() as b:
|
||||
if active_category:
|
||||
biggest_item_weight = max(
|
||||
[
|
||||
i.inventory_item.weight
|
||||
for i in trip.items
|
||||
if i.inventory_item.category_id == active_category.id
|
||||
]
|
||||
or [0]
|
||||
)
|
||||
else:
|
||||
biggest_item_weight = max(
|
||||
[i.inventory_item.weight for i in trip.items] or [0]
|
||||
)
|
||||
if biggest_item_weight <= 0:
|
||||
biggest_item_weight = 1
|
||||
print(active_category)
|
||||
for item in trip.items:
|
||||
if (
|
||||
not active_category
|
||||
or item.inventory_item.category_id == active_category.id
|
||||
):
|
||||
TripItemRow(item, biggest_item_weight).doc
|
||||
|
||||
self.doc = doc
|
||||
21
python_flask/packager/components/TripItemManager.py
Normal file
21
python_flask/packager/components/TripItemManager.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from .TripCategoryList import TripCategoryList
|
||||
from .TripItemList import TripItemList
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class TripItemManager:
|
||||
def __init__(self, trip, categories, active_category):
|
||||
with t.div(
|
||||
id="pkglist-item-manager", _class=cls("grid", "grid-cols-4", "gap-3")
|
||||
) as doc:
|
||||
with t.div(_class=cls("col-span-2")):
|
||||
TripCategoryList(trip, categories),
|
||||
with t.div(_class=cls("col-span-2")):
|
||||
TripItemList(trip, active_category=active_category),
|
||||
|
||||
self.doc = doc
|
||||
19
python_flask/packager/components/TripList.py
Normal file
19
python_flask/packager/components/TripList.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from .TripTable import TripTable
|
||||
from .NewTrip import NewTrip
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class TripList:
|
||||
def __init__(self, trips):
|
||||
with t.div(id="trips-manager", _class=cls("p-8")) as doc:
|
||||
TripTable(trips),
|
||||
NewTrip(
|
||||
# name=name, description=description, error=error, errormsg=errormsg
|
||||
)
|
||||
|
||||
self.doc = doc
|
||||
381
python_flask/packager/components/TripManager.py
Normal file
381
python_flask/packager/components/TripManager.py
Normal file
@@ -0,0 +1,381 @@
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
import urllib
|
||||
import decimal
|
||||
|
||||
from .TripTable import TripTable
|
||||
from .NewTrip import NewTrip
|
||||
from .TripItemManager import TripItemManager
|
||||
|
||||
from ..helpers import *
|
||||
from ..models import TripState
|
||||
|
||||
|
||||
class TripInfoEditRow:
|
||||
def __init__(self, baseurl, name, value, attribute, inputtype="text"):
|
||||
|
||||
with t.tr() as doc:
|
||||
t.form(
|
||||
id=f"edit-{attribute}",
|
||||
action=urllib.parse.urljoin(baseurl, f"edit/{attribute}/submit/"),
|
||||
target="_self",
|
||||
method="post",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
)
|
||||
with t.tr(_class=cls("h-full")):
|
||||
t.td(name, _class=cls("border", "p-2", "h-full"))
|
||||
with t.td(_class=cls("border", "p-0")):
|
||||
t._input(
|
||||
_class=cls("bg-blue-200", "w-full", "h-full", "px-2"),
|
||||
type=inputtype,
|
||||
id="item-edit-name",
|
||||
form=f"edit-{attribute}",
|
||||
name=attribute,
|
||||
value=value,
|
||||
)
|
||||
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-red-200",
|
||||
"hover:bg-red-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
id=f"edit-{attribute}-abort",
|
||||
):
|
||||
with t.a(href=baseurl):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
|
||||
with t.td(
|
||||
id=f"edit-{attribute}-save",
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-green-200",
|
||||
"hover:bg-green-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
),
|
||||
):
|
||||
with t.button(type="submit", form=f"edit-{attribute}"):
|
||||
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class TripInfoNormalRow:
|
||||
def __init__(self, editable, baseurl, name, value, attribute):
|
||||
with t.tr() as doc:
|
||||
t.td(name, _class=cls("border", "p-2"))
|
||||
t.td(value, _class=cls("border", "p-2"))
|
||||
if editable:
|
||||
with t.td(
|
||||
_class=cls(
|
||||
"border",
|
||||
"bg-blue-200",
|
||||
"hover:bg-blue-400",
|
||||
"cursor-pointer",
|
||||
"w-8",
|
||||
"text-center",
|
||||
)
|
||||
):
|
||||
if editable:
|
||||
with t.a(
|
||||
# data_hx_post=f"/item/{item.id}/edit",
|
||||
href=f"?edit={attribute}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-pencil", "text-xl")),
|
||||
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class TripInfo:
|
||||
def __init__(self, trip, edit, baseurl, triptypes):
|
||||
with t.table(
|
||||
id="trip-table",
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
),
|
||||
) as doc:
|
||||
with t.tbody() as b:
|
||||
TripInfoNormalRow(False, "", "State", trip.state.name, "")
|
||||
|
||||
if edit == "location":
|
||||
TripInfoEditRow(baseurl, "Location", trip.location, "location")
|
||||
else:
|
||||
TripInfoNormalRow(
|
||||
True, baseurl, "Location", trip.location, "location"
|
||||
)
|
||||
|
||||
if edit == "start_date":
|
||||
TripInfoEditRow(
|
||||
baseurl,
|
||||
"From",
|
||||
str(trip.start_date),
|
||||
"start_date",
|
||||
inputtype="date",
|
||||
)
|
||||
else:
|
||||
TripInfoNormalRow(
|
||||
True, baseurl, "From", str(trip.start_date), "start_date"
|
||||
)
|
||||
|
||||
if edit == "end_date":
|
||||
TripInfoEditRow(
|
||||
baseurl, "To", str(trip.end_date), "end_date", inputtype="date"
|
||||
)
|
||||
else:
|
||||
TripInfoNormalRow(
|
||||
True, baseurl, "To", str(trip.end_date), "end_date"
|
||||
)
|
||||
|
||||
if edit == "temp_min":
|
||||
TripInfoEditRow(
|
||||
baseurl,
|
||||
"Temp (min)",
|
||||
trip.temp_min,
|
||||
"temp_min",
|
||||
inputtype="number",
|
||||
)
|
||||
else:
|
||||
TripInfoNormalRow(
|
||||
True, baseurl, "Temp (min)", trip.temp_min, "temp_min"
|
||||
)
|
||||
|
||||
if edit == "temp_max":
|
||||
TripInfoEditRow(
|
||||
baseurl,
|
||||
"Temp (max)",
|
||||
trip.temp_max,
|
||||
"temp_max",
|
||||
inputtype="number",
|
||||
)
|
||||
else:
|
||||
TripInfoNormalRow(
|
||||
True, baseurl, "Temp (max)", trip.temp_max, "temp_max"
|
||||
)
|
||||
|
||||
with t.tr():
|
||||
t.td(f"Types", _class=cls("border", "p-2"))
|
||||
with t.td(_class=cls("border", "p-2")):
|
||||
with t.ul(
|
||||
_class=cls(
|
||||
"flex",
|
||||
"flex-row",
|
||||
"flex-wrap",
|
||||
"gap-2",
|
||||
"justify-between",
|
||||
)
|
||||
):
|
||||
with t.div(
|
||||
_class=cls(
|
||||
"flex",
|
||||
"flex-row",
|
||||
"flex-wrap",
|
||||
"gap-2",
|
||||
"justify-start",
|
||||
)
|
||||
):
|
||||
for triptype in trip.types:
|
||||
with t.a(href=f"?type_remove={triptype.id}"):
|
||||
with t.li(
|
||||
_class=cls(
|
||||
"border",
|
||||
"rounded-2xl",
|
||||
"py-0.5",
|
||||
"px-2",
|
||||
"bg-green-100",
|
||||
"cursor-pointer",
|
||||
"flex",
|
||||
"flex-column",
|
||||
"items-center",
|
||||
"hover:bg-red-200",
|
||||
"gap-1",
|
||||
)
|
||||
):
|
||||
t.span(triptype.name)
|
||||
t.span(
|
||||
_class=cls(
|
||||
"mdi", "mdi-delete", "text-sm"
|
||||
)
|
||||
)
|
||||
|
||||
with t.div(
|
||||
_class=cls(
|
||||
"flex",
|
||||
"flex-row",
|
||||
"flex-wrap",
|
||||
"gap-2",
|
||||
"justify-start",
|
||||
)
|
||||
):
|
||||
for triptype in triptypes:
|
||||
if triptype not in trip.types:
|
||||
with t.a(href=f"?type_add={triptype.id}"):
|
||||
with t.li(
|
||||
_class=cls(
|
||||
"border",
|
||||
"rounded-2xl",
|
||||
"py-0.5",
|
||||
"px-2",
|
||||
"bg-gray-100",
|
||||
"cursor-pointer",
|
||||
"flex",
|
||||
"flex-column",
|
||||
"items-center",
|
||||
"hover:bg-green-200",
|
||||
"gap-1",
|
||||
"opacity-60",
|
||||
)
|
||||
):
|
||||
t.span(triptype.name)
|
||||
t.span(
|
||||
_class=cls(
|
||||
"mdi", "mdi-plus", "text-sm"
|
||||
)
|
||||
)
|
||||
|
||||
with t.tr():
|
||||
t.td(f"Carried weight", _class=cls("border", "p-2"))
|
||||
weight = sum(
|
||||
[i.inventory_item.weight for i in trip.items if i.pick]
|
||||
) / decimal.Decimal(1000)
|
||||
t.td(f"{weight} kg", _class=cls("border", "p-2"))
|
||||
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class TripComments:
|
||||
def __init__(self, trip, baseurl):
|
||||
with t.div() as doc:
|
||||
t.h1("Comments", _class=cls("text-xl", "mb-5"))
|
||||
t.form(
|
||||
id="edit-comment",
|
||||
action=urllib.parse.urljoin(baseurl, f"edit/comment/submit/"),
|
||||
target="_self",
|
||||
method="post",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
)
|
||||
# https://stackoverflow.com/a/48460773
|
||||
t.textarea(
|
||||
trip.comment or "",
|
||||
name="comment",
|
||||
form="edit-comment",
|
||||
_class=cls("border", "w-full", "h-48"),
|
||||
oninput='this.style.height = "";this.style.height = this.scrollHeight + 2 + "px"',
|
||||
)
|
||||
|
||||
with t.button(
|
||||
type="submit",
|
||||
form=f"edit-comment",
|
||||
_class=cls(
|
||||
"mt-2",
|
||||
"border",
|
||||
"bg-green-200",
|
||||
"hover:bg-green-400",
|
||||
"cursor-pointer",
|
||||
"flex",
|
||||
"flex-column",
|
||||
"p-2",
|
||||
"gap-2",
|
||||
"items-center",
|
||||
),
|
||||
):
|
||||
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
|
||||
t.span("Save")
|
||||
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class TripActions:
|
||||
def __init__(self, trip):
|
||||
with t.div() as doc:
|
||||
t.h1("Actions", _class=cls("text-xl", "mb-5"))
|
||||
|
||||
with t.div(_class=cls("flex", "flex-column", "gap-2")):
|
||||
if trip.state == TripState.Planning:
|
||||
t.button("Finish planning", _class=cls("border", "p-2"))
|
||||
if trip.state in (TripState.Planned, TripState.Active):
|
||||
t.button("Start review", _class=cls("border", "p-2"))
|
||||
if trip.state == TripState.Done:
|
||||
t.button("Back to review", _class=cls("border", "p-2"))
|
||||
|
||||
self.doc = doc
|
||||
|
||||
|
||||
class TripManager:
|
||||
def __init__(self, trip, categories, active_category, edit, baseurl, triptypes):
|
||||
with t.div(id="trips-manager", _class=cls("p-8")) as doc:
|
||||
if edit == "name":
|
||||
t.form(
|
||||
id=f"edit-name",
|
||||
action=urllib.parse.urljoin(baseurl, f"edit/name/submit/"),
|
||||
target="_self",
|
||||
method="post",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
)
|
||||
with t.div(_class=cls("flex", "flex-row", "items-center", "gap-x-3")):
|
||||
t._input(
|
||||
_class=cls("bg-blue-200", "w-full", "h-full", "px-2", "text-2xl", "font-semibold"),
|
||||
type="text",
|
||||
id="item-edit-name",
|
||||
form=f"edit-name",
|
||||
name="name",
|
||||
value=trip.name,
|
||||
)
|
||||
with t.a(href=baseurl):
|
||||
with t.button(_class=cls(
|
||||
"bg-red-200",
|
||||
"hover:bg-red-400",
|
||||
"cursor-pointer")):
|
||||
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
|
||||
with t.button(type="submit", form=f"edit-name", _class=cls(
|
||||
"bg-green-200",
|
||||
"hover:bg-green-400",
|
||||
"cursor-pointer")):
|
||||
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
|
||||
else:
|
||||
with t.div(_class=cls("flex", "flex-row", "items-center", "gap-x-3")):
|
||||
t.h1(trip.name, _class=cls("text-2xl", "font-semibold"))
|
||||
with t.span():
|
||||
with t.a(
|
||||
# data_hx_post=f"/item/{item.id}/edit",
|
||||
href=f"?edit=name",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
):
|
||||
with t.button():
|
||||
t.span(_class=cls("mdi", "mdi-pencil", "text-xl", "opacity-50")),
|
||||
|
||||
with t.div(_class=cls("my-6")):
|
||||
TripInfo(trip, edit, baseurl, triptypes).doc
|
||||
|
||||
with t.div(_class=cls("my-6")):
|
||||
TripComments(trip, baseurl).doc
|
||||
|
||||
with t.div(_class=cls("my-6")):
|
||||
TripActions(trip).doc
|
||||
|
||||
with t.div(_class=cls("my-6")):
|
||||
TripItemManager(
|
||||
trip, categories=categories, active_category=active_category
|
||||
).doc
|
||||
|
||||
self.doc = doc
|
||||
96
python_flask/packager/components/TripTable.py
Normal file
96
python_flask/packager/components/TripTable.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import datetime
|
||||
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from ..helpers import *
|
||||
|
||||
|
||||
class TripRow(object):
|
||||
def __init__(self, trip):
|
||||
with t.tr(
|
||||
_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100", "h-full")
|
||||
) as doc:
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a(
|
||||
trip.name,
|
||||
id="select-trip",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/trip/{trip.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
)
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a(
|
||||
t.p(str(trip.start_date)),
|
||||
id="select-trip",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/trip/{trip.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
)
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a(
|
||||
t.p(str(trip.end_date)),
|
||||
id="select-trip",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/trip/{trip.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
)
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a(
|
||||
t.p((trip.end_date - trip.start_date).days),
|
||||
id="select-trip",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/trip/{trip.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
)
|
||||
today = datetime.datetime.now().date()
|
||||
with t.td(_class=cls("border", "p-0", "m-0")):
|
||||
t.a(
|
||||
t.p(trip.state.name),
|
||||
id="select-trip",
|
||||
# data_hx_post=f"/list/{pkglist.id}/edit",
|
||||
href=f"/trip/{trip.id}",
|
||||
# data_hx_target="closest tr",
|
||||
# data_hx_swap="outerHTML",
|
||||
_class=cls("inline-block", "p-2", "m-0", "w-full"),
|
||||
)
|
||||
self.doc = doc
|
||||
|
||||
|
||||
def TripTable(trips):
|
||||
with t.div() as doc:
|
||||
t.h1("Trips", _class=cls("text-2xl", "mb-5"))
|
||||
with t.table(
|
||||
id="trip-table",
|
||||
_class=cls(
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
),
|
||||
):
|
||||
with t.thead(_class=cls("bg-gray-200")):
|
||||
t.tr(
|
||||
t.th("Name", _class=cls("border", "p-2")),
|
||||
t.th("From", _class=cls("border", "p-2")),
|
||||
t.th("To", _class=cls("border", "p-2")),
|
||||
t.th("Nights", _class=cls("border", "p-2")),
|
||||
t.th("State", _class=cls("border", "p-2")),
|
||||
_class="h-10",
|
||||
)
|
||||
with t.tbody() as b:
|
||||
for trip in trips:
|
||||
TripRow(trip).doc
|
||||
|
||||
return doc
|
||||
@@ -1,13 +1,6 @@
|
||||
from .NewPackageList import NewPackageList
|
||||
from .PackageListTable import (
|
||||
PackageListTable,
|
||||
PackageListTableRowEdit,
|
||||
PackageListTableRowNormal,
|
||||
PackageListTableRow,
|
||||
)
|
||||
|
||||
from .ItemList import ItemList
|
||||
from .CategoryList import CategoryList
|
||||
from .PackageListManager import PackageListManager
|
||||
from .PackageListItemManager import PackageListItemManager
|
||||
from .Base import Base
|
||||
from .Home import Home
|
||||
from .InventoryItemManager import InventoryItemManager
|
||||
from .InventoryItemDetails import InventoryItemDetails
|
||||
from .TripManager import TripManager
|
||||
from .TripList import TripList
|
||||
|
||||
@@ -1,36 +1,86 @@
|
||||
from . import db
|
||||
import enum
|
||||
|
||||
|
||||
class PackageList(db.Model):
|
||||
__tablename__ = "packagelist"
|
||||
class InventoryItemCategory(db.Model):
|
||||
__tablename__ = "inventoryitemcategories"
|
||||
id = db.Column(db.String(36), primary_key=True)
|
||||
name = db.Column(db.Text, unique=True, nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
# items = db.relationship("PackageListItem", backref="packagelist", lazy=True)
|
||||
items = db.relationship("InventoryItem", backref="category", lazy=True)
|
||||
|
||||
edit = False
|
||||
error = False
|
||||
errormsg = None
|
||||
active = False
|
||||
|
||||
|
||||
class PackageListItemCategory(db.Model):
|
||||
__tablename__ = "packagelistitemcategory"
|
||||
class InventoryItem(db.Model):
|
||||
__tablename__ = "inventoryitems"
|
||||
id = db.Column(db.String(36), primary_key=True)
|
||||
name = db.Column(db.Text, unique=True, nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
items = db.relationship("PackageListItem", backref="category", lazy=True)
|
||||
|
||||
|
||||
class PackageListItem(db.Model):
|
||||
__tablename__ = "packagelistitem"
|
||||
id = db.Column(db.String(36), primary_key=True)
|
||||
name = db.Column(db.Text, unique=True, nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
weight = db.Column(db.Integer)
|
||||
# packagelist_id = db.Column(
|
||||
# db.String(36), db.ForeignKey("packagelist.id"), nullable=False
|
||||
# )
|
||||
weight = db.Column(db.Integer, nullable=False)
|
||||
category_id = db.Column(
|
||||
db.String(36), db.ForeignKey("packagelistitemcategory.id"), nullable=False
|
||||
db.String(36), db.ForeignKey("inventoryitemcategories.id"), nullable=False
|
||||
)
|
||||
|
||||
edit = False
|
||||
|
||||
|
||||
class TripItems(db.Model):
|
||||
__tablename__ = "tripitems"
|
||||
item_id = db.Column(
|
||||
db.String(36),
|
||||
db.ForeignKey("inventoryitems.id"),
|
||||
nullable=False,
|
||||
primary_key=True,
|
||||
)
|
||||
trip_id = db.Column(
|
||||
db.String(36), db.ForeignKey("trips.id"), nullable=False, primary_key=True
|
||||
)
|
||||
inventory_item = db.relationship("InventoryItem", lazy=True)
|
||||
|
||||
pick = db.Column(db.Boolean, nullable=False)
|
||||
pack = db.Column(db.Boolean, nullable=False)
|
||||
|
||||
edit = False
|
||||
|
||||
|
||||
class TripType(db.Model):
|
||||
__tablename__ = "triptypes"
|
||||
id = db.Column(db.String(36), primary_key=True)
|
||||
name = db.Column(db.Text, unique=True, nullable=False)
|
||||
|
||||
|
||||
class TripToTripType(db.Model):
|
||||
__tablename__ = "trips_to_triptypes"
|
||||
trip_id = db.Column(
|
||||
db.String(36), db.ForeignKey("trips.id"), nullable=False, primary_key=True
|
||||
)
|
||||
trip_type_id = db.Column(
|
||||
db.String(36), db.ForeignKey("triptypes.id"), nullable=False, primary_key=True
|
||||
)
|
||||
|
||||
|
||||
class TripState(enum.Enum):
|
||||
Planning = 1
|
||||
Planned = 2
|
||||
Active = 3
|
||||
Review = 4
|
||||
Done = 5
|
||||
|
||||
|
||||
class Trip(db.Model):
|
||||
__tablename__ = "trips"
|
||||
id = db.Column(db.String(36), primary_key=True)
|
||||
name = db.Column(db.Text, unique=True, nullable=False)
|
||||
start_date = db.Column(db.Date, nullable=False)
|
||||
end_date = db.Column(db.Date, nullable=False)
|
||||
location = db.Column(db.Text, nullable=False)
|
||||
temp_min = db.Column(db.Integer, nullable=False)
|
||||
temp_max = db.Column(db.Integer, nullable=False)
|
||||
|
||||
comment = db.Column(db.Text, nullable=False)
|
||||
|
||||
types = db.relationship("TripType", secondary="trips_to_triptypes", lazy=True)
|
||||
items = db.relationship("TripItems", lazy=True)
|
||||
|
||||
state = db.Column(db.Enum(TripState), nullable=False)
|
||||
|
||||
@@ -5,121 +5,435 @@ from .helpers import *
|
||||
|
||||
import uuid
|
||||
import os
|
||||
import urllib
|
||||
import datetime
|
||||
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
from dominate.util import raw
|
||||
|
||||
from .components import (
|
||||
PackageListManager,
|
||||
PackageListItemManager,
|
||||
NewPackageList,
|
||||
InventoryItemManager,
|
||||
Base,
|
||||
Home,
|
||||
PackageListTableRowEdit,
|
||||
PackageListTableRowNormal,
|
||||
PackageListTableRow,
|
||||
TripList,
|
||||
TripManager,
|
||||
InventoryItemDetails,
|
||||
)
|
||||
|
||||
from flask import request, make_response
|
||||
|
||||
|
||||
def get_packagelists():
|
||||
return PackageList.query.all()
|
||||
|
||||
|
||||
def get_categories():
|
||||
return PackageListItemCategory.query.all()
|
||||
return InventoryItemCategory.query.all()
|
||||
|
||||
|
||||
def get_trips():
|
||||
return Trip.query.all()
|
||||
|
||||
|
||||
def get_all_items():
|
||||
return PackageListItem.query.all()
|
||||
return InventoryItem.query.all()
|
||||
|
||||
|
||||
def get_triptypes():
|
||||
return TripType.query.all()
|
||||
|
||||
|
||||
def get_items(category):
|
||||
return PackageListItem.query.filter_by(category_id=str(category.id))
|
||||
|
||||
def get_packagelist_by_id(id):
|
||||
return PackageList.query.filter_by(id=str(id)).first()
|
||||
return InventoryItem.query.filter_by(category_id=str(category.id))
|
||||
|
||||
|
||||
def add_packagelist(name, description):
|
||||
try:
|
||||
def get_item(id):
|
||||
return InventoryItem.query.filter_by(id=str(id)).first()
|
||||
|
||||
|
||||
def get_trip(id):
|
||||
return Trip.query.filter_by(id=str(id)).first()
|
||||
|
||||
|
||||
def pick_item(trip_id, item_id):
|
||||
item = TripItems.query.filter_by(item_id=str(item_id), trip_id=str(trip_id)).first()
|
||||
item.pick = True
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def unpick_item(trip_id, item_id):
|
||||
item = TripItems.query.filter_by(item_id=str(item_id), trip_id=str(trip_id)).first()
|
||||
item.pick = False
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def pack_item(trip_id, item_id):
|
||||
item = TripItems.query.filter_by(item_id=str(item_id), trip_id=str(trip_id)).first()
|
||||
item.pack = True
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def unpack_item(trip_id, item_id):
|
||||
item = TripItems.query.filter_by(item_id=str(item_id), trip_id=str(trip_id)).first()
|
||||
item.pack = False
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def add_item(name, weight, category_id):
|
||||
db.session.add(
|
||||
PackageList(id=str(uuid.uuid4()), name=name, description=description)
|
||||
InventoryItem(
|
||||
id=str(uuid.uuid4()),
|
||||
name=name,
|
||||
description="",
|
||||
weight=weight,
|
||||
category_id=category_id,
|
||||
)
|
||||
)
|
||||
db.session.commit()
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
db.session.rollback()
|
||||
return False
|
||||
|
||||
|
||||
def delete_packagelist(id):
|
||||
deletions = PackageList.query.filter_by(id=str(id)).delete()
|
||||
if deletions == 0:
|
||||
return False
|
||||
else:
|
||||
db.session.commit()
|
||||
return True
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def root():
|
||||
return make_response(Base(Home(), app.root_path).doc.render(), 200)
|
||||
|
||||
|
||||
@app.route("/inventory/")
|
||||
def inventory():
|
||||
categories = get_categories()
|
||||
items = get_all_items()
|
||||
|
||||
args = request.args.to_dict()
|
||||
|
||||
error = False
|
||||
if not is_htmx():
|
||||
edit = request.args.get("edit")
|
||||
if edit is not None:
|
||||
match = [p for p in packagelists if p.id == edit]
|
||||
match = [i for i in items if i.id == edit]
|
||||
if match:
|
||||
match[0].edit = True
|
||||
error = request.args.get("error")
|
||||
if error and bool(int(error)):
|
||||
match[0].error = True
|
||||
errormsg = request.args.get("msg")
|
||||
if errormsg:
|
||||
match[0].errormsg = errormsg
|
||||
else:
|
||||
name = request.args.get("name")
|
||||
if name:
|
||||
match[0].errormsg = f"Invalid name: {name}"
|
||||
else:
|
||||
match[0].errormsg = f"Invalid name"
|
||||
|
||||
return make_response(
|
||||
Home(PackageListItemManager(categories, items), app.root_path).doc.render(), 200
|
||||
Base(
|
||||
InventoryItemManager(categories, items, active_category=None),
|
||||
app.root_path,
|
||||
active_page="inventory",
|
||||
).doc.render(),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/category/<uuid:id>")
|
||||
@app.route("/inventory/item/<uuid:id>/")
|
||||
def inventory_item(id):
|
||||
item = get_item(id)
|
||||
|
||||
args = request.args.to_dict()
|
||||
edit = args.pop("edit", None)
|
||||
|
||||
return make_response(
|
||||
Base(
|
||||
InventoryItemDetails(item, edit=edit, baseurl=f"/inventory/item/{id}/",), app.root_path, active_page="inventory"
|
||||
).doc.render(),
|
||||
200,
|
||||
)
|
||||
|
||||
@app.route(
|
||||
"/inventory/item/<uuid:id>/edit/<string:attribute>/submit/",
|
||||
methods=["POST"],
|
||||
)
|
||||
def edit_inventory_submit(id, attribute):
|
||||
new_value = request.form[attribute]
|
||||
|
||||
if attribute in ("weight"):
|
||||
new_value = int(new_value)
|
||||
|
||||
updates = InventoryItem.query.filter_by(id=str(id)).update({attribute: new_value})
|
||||
db.session.commit()
|
||||
if updates == 0:
|
||||
# todo what to do without js?
|
||||
return make_response("", 404)
|
||||
|
||||
redirect = request.path[: -(len(f"edit/{attribute}/submit/"))]
|
||||
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = redirect
|
||||
return r
|
||||
|
||||
@app.route("/trips/")
|
||||
def trips():
|
||||
trips = get_trips()
|
||||
|
||||
return make_response(
|
||||
Base(TripList(trips), app.root_path, active_page="trips").doc.render(), 200
|
||||
)
|
||||
|
||||
|
||||
@app.route("/trip/<uuid:id>/")
|
||||
def trip(id):
|
||||
|
||||
args = request.args.to_dict()
|
||||
|
||||
item_to_pick = args.pop("item_pick", None)
|
||||
if item_to_pick:
|
||||
pick_item(id, item_to_pick)
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/"
|
||||
return r
|
||||
|
||||
item_to_unpick = args.pop("item_unpick", None)
|
||||
if item_to_unpick:
|
||||
unpick_item(id, item_to_unpick)
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/"
|
||||
return r
|
||||
|
||||
item_to_pack = args.pop("item_pack", None)
|
||||
if item_to_pack:
|
||||
pack_item(id, item_to_pack)
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/"
|
||||
return r
|
||||
|
||||
item_to_unpack = args.pop("item_unpack", None)
|
||||
if item_to_unpack:
|
||||
unpack_item(id, item_to_unpack)
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/"
|
||||
return r
|
||||
|
||||
type_to_add = args.pop("type_add", None)
|
||||
if type_to_add:
|
||||
newtype = TripToTripType(trip_id=str(id), trip_type_id=str(type_to_add))
|
||||
db.session.add(newtype)
|
||||
db.session.commit()
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/"
|
||||
return r
|
||||
|
||||
type_to_remove = args.pop("type_remove", None)
|
||||
if type_to_remove:
|
||||
newtype = TripToTripType.query.filter_by(
|
||||
trip_id=str(id), trip_type_id=str(type_to_remove)
|
||||
).delete()
|
||||
db.session.commit()
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/"
|
||||
return r
|
||||
|
||||
trip = get_trip(id)
|
||||
|
||||
items = get_all_items()
|
||||
|
||||
categories = get_categories()
|
||||
|
||||
edit = args.pop("edit", None)
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
db.session.add(
|
||||
TripItems(trip_id=str(id), item_id=str(item.id), pick=False, pack=False)
|
||||
)
|
||||
db.session.commit()
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
db.session.rollback()
|
||||
|
||||
return make_response(
|
||||
Base(
|
||||
TripManager(
|
||||
trip,
|
||||
categories=categories,
|
||||
active_category=None,
|
||||
edit=edit,
|
||||
baseurl=f"/trip/{id}/",
|
||||
triptypes=get_triptypes(),
|
||||
),
|
||||
app.root_path,
|
||||
active_page="trips",
|
||||
).doc.render(),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@app.route(
|
||||
"/trip/<uuid:id>/category/<uuid:category_id>/edit/<string:attribute>/submit/",
|
||||
methods=["POST"],
|
||||
)
|
||||
@app.route("/trip/<uuid:id>/edit/<string:attribute>/submit/", methods=["POST"])
|
||||
def edit_trip_category_location_edit_submit(id, category_id=None, attribute=None):
|
||||
new_value = request.form[attribute]
|
||||
|
||||
if attribute in ("start_date", "end_date"):
|
||||
new_value = datetime.date.fromisoformat(new_value)
|
||||
|
||||
updates = Trip.query.filter_by(id=str(id)).update({attribute: new_value})
|
||||
db.session.commit()
|
||||
if updates == 0:
|
||||
# todo what to do without js?
|
||||
return make_response("", 404)
|
||||
|
||||
redirect = request.path[: -(len(f"edit/{attribute}/submit/"))]
|
||||
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = redirect
|
||||
return r
|
||||
|
||||
|
||||
@app.route("/trip/<uuid:id>/category/<uuid:category_id>/")
|
||||
def trip_with_active_category(id, category_id):
|
||||
trip = get_trip(id)
|
||||
|
||||
args = request.args.to_dict()
|
||||
|
||||
item_to_pick = args.pop("item_pick", None)
|
||||
if item_to_pick:
|
||||
pick_item(id, item_to_pick)
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers[
|
||||
"Location"
|
||||
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
|
||||
return r
|
||||
|
||||
item_to_unpick = args.pop("item_unpick", None)
|
||||
if item_to_unpick:
|
||||
unpick_item(id, item_to_unpick)
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers[
|
||||
"Location"
|
||||
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
|
||||
return r
|
||||
|
||||
item_to_pack = args.pop("item_pack", None)
|
||||
if item_to_pack:
|
||||
pack_item(id, item_to_pack)
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers[
|
||||
"Location"
|
||||
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
|
||||
return r
|
||||
|
||||
item_to_unpack = args.pop("item_unpack", None)
|
||||
if item_to_unpack:
|
||||
unpack_item(id, item_to_unpack)
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers[
|
||||
"Location"
|
||||
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
|
||||
return r
|
||||
|
||||
type_to_add = args.pop("type_add", None)
|
||||
if type_to_add:
|
||||
newtype = TripToTripType(trip_id=str(id), trip_type_id=str(type_to_add))
|
||||
db.session.add(newtype)
|
||||
db.session.commit()
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers[
|
||||
"Location"
|
||||
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
|
||||
return r
|
||||
|
||||
type_to_remove = args.pop("type_remove", None)
|
||||
if type_to_remove:
|
||||
newtype = TripToTripType.query.filter_by(
|
||||
trip_id=str(id), trip_type_id=str(type_to_remove)
|
||||
).delete()
|
||||
db.session.commit()
|
||||
r = make_response("", 303)
|
||||
if args:
|
||||
r.headers[
|
||||
"Location"
|
||||
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
|
||||
else:
|
||||
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
|
||||
return r
|
||||
|
||||
items = get_all_items()
|
||||
|
||||
categories = get_categories()
|
||||
active_category = [c for c in categories if str(c.id) == str(category_id)][0]
|
||||
active_category.active = True
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
db.session.add(
|
||||
TripItems(trip_id=str(id), item_id=str(item.id), pick=False, pack=False)
|
||||
)
|
||||
db.session.commit()
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
db.session.rollback()
|
||||
|
||||
edit = args.pop("edit", None)
|
||||
|
||||
return make_response(
|
||||
Base(
|
||||
TripManager(
|
||||
trip,
|
||||
categories=categories,
|
||||
active_category=active_category,
|
||||
edit=edit,
|
||||
baseurl=f"/trip/{id}/category/{category_id}/",
|
||||
triptypes=get_triptypes(),
|
||||
),
|
||||
app.root_path,
|
||||
active_page="trips",
|
||||
).doc.render(),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/inventory/category/<uuid:id>/")
|
||||
def category(id):
|
||||
categories = get_categories()
|
||||
print(id)
|
||||
for c in categories:
|
||||
print(f"{c.id} | {c.name}")
|
||||
active_category = [c for c in categories if str(c.id) == str(id)][0]
|
||||
active_category.active = True
|
||||
|
||||
args = request.args.to_dict()
|
||||
|
||||
items = get_items(active_category)
|
||||
error = False
|
||||
if not is_htmx():
|
||||
edit = request.args.get("edit")
|
||||
if edit is not None:
|
||||
match = [p for p in packagelists if p.id == edit]
|
||||
match = [i for i in items if i.id == edit]
|
||||
if match:
|
||||
match[0].edit = True
|
||||
error = request.args.get("error")
|
||||
if error and bool(int(error)):
|
||||
match[0].error = True
|
||||
errormsg = request.args.get("msg")
|
||||
if errormsg:
|
||||
match[0].errormsg = errormsg
|
||||
else:
|
||||
name = request.args.get("name")
|
||||
if name:
|
||||
match[0].errormsg = f"Invalid name: {name}"
|
||||
else:
|
||||
match[0].errormsg = f"Invalid name"
|
||||
|
||||
return make_response(
|
||||
Home(PackageListItemManager(categories, items), app.root_path).doc.render(), 200
|
||||
Base(
|
||||
InventoryItemManager(categories, items, active_category),
|
||||
app.root_path,
|
||||
active_page="inventory",
|
||||
).doc.render(),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@@ -127,32 +441,32 @@ def is_htmx():
|
||||
return request.headers.get("HX-Request") is not None
|
||||
|
||||
|
||||
@app.route("/list/", methods=["POST"])
|
||||
def add_new_list():
|
||||
@app.route("/inventory/item/", methods=["POST"])
|
||||
def add_new_item():
|
||||
name = request.form["name"]
|
||||
description = request.form["description"]
|
||||
weight = int(request.form["weight"])
|
||||
category_id = request.form["category"]
|
||||
|
||||
error, errormsg = validate_name(name)
|
||||
add_item(name=name, weight=weight, category_id=category_id)
|
||||
|
||||
if not error:
|
||||
if add_packagelist(name=name, description=description) is False:
|
||||
error = True
|
||||
errormsg = f'Name "{name}" already exists'
|
||||
|
||||
if is_htmx():
|
||||
return make_response(
|
||||
PackageListManager(
|
||||
get_packagelists(),
|
||||
name=name,
|
||||
description=description,
|
||||
error=error,
|
||||
errormsg=errormsg,
|
||||
).doc.render(),
|
||||
200 if error else 201,
|
||||
)
|
||||
else:
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = "/"
|
||||
r.headers["Location"] = f"/inventory/category/{category_id}"
|
||||
return r
|
||||
|
||||
|
||||
@app.route("/trip/", methods=["POST"])
|
||||
def add_new_trip():
|
||||
name = request.form["name"]
|
||||
start_date = datetime.date.fromisoformat(request.form["start-date"])
|
||||
end_date = datetime.date.fromisoformat(request.form["end-date"])
|
||||
|
||||
newid = str(uuid.uuid4())
|
||||
db.session.add(Trip(id=newid, name=name, start_date=start_date, end_date=end_date))
|
||||
db.session.commit()
|
||||
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = f"/trip/{newid}" # TODO enable this
|
||||
r.headers["Location"] = f"/trip/"
|
||||
return r
|
||||
|
||||
|
||||
@@ -169,93 +483,92 @@ def validate_name(name):
|
||||
return error, errormsg
|
||||
|
||||
|
||||
@app.route("/list/name/validate", methods=["POST"])
|
||||
def validate_list_name():
|
||||
@app.route("/inventory/item/<uuid:id>/edit/submit/", methods=["POST"])
|
||||
def edit_item_submit(id):
|
||||
name = request.form["name"]
|
||||
weight = int(request.form["weight"])
|
||||
|
||||
error, errormsg = validate_name(name)
|
||||
|
||||
if not error:
|
||||
if PackageList.query.filter_by(name=name).first() is not None:
|
||||
error = True
|
||||
errormsg = f'Name "{name}" already exists'
|
||||
doc = NewPackageList(name=name, error=error, errormsg=errormsg)
|
||||
|
||||
return make_response(doc.render(), 200)
|
||||
|
||||
|
||||
@app.route("/list/<uuid:id>/edit/cancel", methods=["POST"])
|
||||
def edit_list_cancel(id):
|
||||
print("cancelling" * 20)
|
||||
pkglist = PackageList.query.filter_by(id=str(id)).first()
|
||||
return make_response(PackageListTableRowNormal(pkglist).doc.render(), 200)
|
||||
|
||||
|
||||
@app.route("/list/<uuid:id>/edit/submit/", methods=["POST"])
|
||||
def edit_list_submit(id):
|
||||
name = request.form["name"]
|
||||
description = request.form["description"]
|
||||
error, errormsg = validate_name(name)
|
||||
|
||||
if error:
|
||||
if is_htmx():
|
||||
return make_response(
|
||||
PackageListTableRowEdit(error=True, errormsg=errormsg).doc.render(), 200
|
||||
)
|
||||
else:
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = f"/?edit={id}&error=1&msg={errormsg}"
|
||||
return r
|
||||
|
||||
pkglist = PackageList.query.filter_by(id=str(id)).first()
|
||||
if pkglist is None:
|
||||
item = InventoryItem.query.filter_by(id=str(id)).first()
|
||||
if item is None:
|
||||
# todo what to do without js?
|
||||
return make_response("", 404)
|
||||
|
||||
pkglist.name = name
|
||||
pkglist.description = description
|
||||
item.name = name
|
||||
item.weight = weight
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
db.session.rollback()
|
||||
errormsg = f'Name "{name}" already exists'
|
||||
if is_htmx():
|
||||
pkglist.error = True
|
||||
pkglist.errormsg = errormsg
|
||||
return make_response(PackageListTableRowEdit(pkglist).doc.render(), 200)
|
||||
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = f"/inventory/category/{item.category.id}"
|
||||
return r
|
||||
|
||||
|
||||
@app.route("/inventory/item/<uuid:id>/pick/submit/", methods=["POST"])
|
||||
def edit_item_pick(id):
|
||||
print(request.form)
|
||||
if "pick" in request.form:
|
||||
pick = request.form["pick"] == "on"
|
||||
else:
|
||||
pick = False
|
||||
print(pick)
|
||||
|
||||
item = InventoryItem.query.filter_by(id=str(id)).first()
|
||||
if item is None:
|
||||
# todo what to do without js?
|
||||
return make_response("", 404)
|
||||
|
||||
item.picked = pick
|
||||
|
||||
db.session.commit()
|
||||
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = f"/?edit={id}&name={name}&error=1&msg={errormsg}"
|
||||
r.headers["Location"] = f"/inventory/category/{item.category.id}"
|
||||
return r
|
||||
|
||||
if is_htmx():
|
||||
return make_response(PackageListTableRowNormal(pkglist).doc.render(), 200)
|
||||
|
||||
@app.route("/inventory/item/<uuid:id>/pack/submit/", methods=["POST"])
|
||||
def edit_item_pack(id):
|
||||
print(request.form)
|
||||
if "pack" in request.form:
|
||||
pack = request.form["pack"] == "on"
|
||||
else:
|
||||
pack = False
|
||||
print(pack)
|
||||
|
||||
item = InventoryItem.query.filter_by(id=str(id)).first()
|
||||
if item is None:
|
||||
# todo what to do without js?
|
||||
return make_response("", 404)
|
||||
|
||||
item.pack = pack
|
||||
|
||||
db.session.commit()
|
||||
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = "/"
|
||||
r.headers["Location"] = f"/inventory/category/{item.category.id}"
|
||||
return r
|
||||
|
||||
|
||||
@app.route("/list/<uuid:id>/edit", methods=["POST"])
|
||||
def edit_list(id):
|
||||
pkglist = get_packagelist_by_id(id)
|
||||
|
||||
out = PackageListTableRowEdit(pkglist).doc
|
||||
return make_response(out.render(), 200)
|
||||
|
||||
|
||||
@app.route("/list/<uuid:id>/delete", methods=["GET"])
|
||||
def delete_list_via_get(id):
|
||||
if not delete_packagelist(id=id):
|
||||
return make_response("", 404)
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = "/"
|
||||
return r
|
||||
|
||||
|
||||
@app.route("/list/<uuid:id>", methods=["DELETE"])
|
||||
def delete_list(id):
|
||||
if not delete_packagelist(id=id):
|
||||
@app.route("/inventory/item/<uuid:id>", methods=["DELETE"])
|
||||
def delete_item(id):
|
||||
deletions = InventoryItem.query.filter_by(id=str(id)).delete()
|
||||
if deletions == 0:
|
||||
return make_response("", 404)
|
||||
else:
|
||||
db.session.commit()
|
||||
return make_response("", 200)
|
||||
|
||||
|
||||
@app.route("/inventory/item/<uuid:id>/delete", methods=["GET"])
|
||||
def delete_item_get(id):
|
||||
print(request.headers)
|
||||
print(request.args)
|
||||
print(f"deleting {id}")
|
||||
deletions = InventoryItem.query.filter_by(id=str(id)).delete()
|
||||
if deletions == 0:
|
||||
return make_response("", 404)
|
||||
else:
|
||||
db.session.commit()
|
||||
r = make_response("", 303)
|
||||
r.headers["Location"] = request.headers["Referer"]
|
||||
return r
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
alembic==1.8.1
|
||||
click==8.1.3
|
||||
dominate==2.6.0
|
||||
Flask==2.1.2
|
||||
Flask-Migrate==3.1.0
|
||||
Flask-SQLAlchemy==2.5.1
|
||||
greenlet==1.1.2
|
||||
importlib-metadata==4.12.0
|
||||
importlib-resources==5.9.0
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.2
|
||||
Mako==1.2.2
|
||||
MarkupSafe==2.1.1
|
||||
SQLAlchemy==1.4.39
|
||||
Werkzeug==2.1.2
|
||||
|
||||
@@ -5,4 +5,8 @@ source ./venv/bin/activate
|
||||
export FLASK_APP=packager
|
||||
export FLASK_ENV=development
|
||||
|
||||
python3 -m flask run --reload
|
||||
if (( $# == 0 )) ; then
|
||||
python3 -m flask run --reload --host 0.0.0.0 --port 5000
|
||||
else
|
||||
python3 -m flask "${@}"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user