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__/
|
__pycache__/
|
||||||
/venv/
|
/venv/
|
||||||
*.sqlite
|
*.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 sqlalchemy
|
||||||
import csv
|
import csv
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask_migrate import Migrate
|
||||||
|
|
||||||
from .helpers import *
|
from .helpers import *
|
||||||
|
|
||||||
@@ -10,56 +11,11 @@ from flask_sqlalchemy import SQLAlchemy
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{app.root_path}/../db.sqlite"
|
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{app.root_path}/../db.sqlite"
|
||||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||||
|
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
|
migrate = Migrate(app, db, render_as_batch=True)
|
||||||
|
|
||||||
from packager.models import *
|
from packager.models import *
|
||||||
import packager.views
|
import packager.views
|
||||||
|
|
||||||
db.create_all()
|
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
|
import dominate.tags as t
|
||||||
from dominate.util import raw
|
from dominate.util import raw
|
||||||
|
|
||||||
|
from ..helpers import *
|
||||||
|
|
||||||
|
|
||||||
class Home:
|
class Home:
|
||||||
def __init__(self, element, root_path):
|
def __init__(self):
|
||||||
doc = dominate.document(title="Packager")
|
with t.div(id="home", _class=cls("p-8", "max-w-xl")) as doc:
|
||||||
with doc.head:
|
with t.p():
|
||||||
t.script(src="https://unpkg.com/htmx.org@1.7.0")
|
t.a("Inventory", href="/inventory/")
|
||||||
t.script(src="https://cdn.tailwindcss.com")
|
with t.p():
|
||||||
t.script(src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js", defer=True)
|
t.a("Trips", href="/trips/")
|
||||||
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)
|
|
||||||
self.doc = doc
|
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 .Base import Base
|
||||||
from .PackageListTable import (
|
|
||||||
PackageListTable,
|
|
||||||
PackageListTableRowEdit,
|
|
||||||
PackageListTableRowNormal,
|
|
||||||
PackageListTableRow,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .ItemList import ItemList
|
|
||||||
from .CategoryList import CategoryList
|
|
||||||
from .PackageListManager import PackageListManager
|
|
||||||
from .PackageListItemManager import PackageListItemManager
|
|
||||||
from .Home import Home
|
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
|
from . import db
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
class PackageList(db.Model):
|
class InventoryItemCategory(db.Model):
|
||||||
__tablename__ = "packagelist"
|
__tablename__ = "inventoryitemcategories"
|
||||||
id = db.Column(db.String(36), primary_key=True)
|
id = db.Column(db.String(36), primary_key=True)
|
||||||
name = db.Column(db.Text, unique=True, nullable=False)
|
name = db.Column(db.Text, unique=True, nullable=False)
|
||||||
description = db.Column(db.Text)
|
description = db.Column(db.Text)
|
||||||
# items = db.relationship("PackageListItem", backref="packagelist", lazy=True)
|
items = db.relationship("InventoryItem", backref="category", lazy=True)
|
||||||
|
|
||||||
edit = False
|
active = False
|
||||||
error = False
|
|
||||||
errormsg = None
|
|
||||||
|
|
||||||
|
|
||||||
class PackageListItemCategory(db.Model):
|
class InventoryItem(db.Model):
|
||||||
__tablename__ = "packagelistitemcategory"
|
__tablename__ = "inventoryitems"
|
||||||
id = db.Column(db.String(36), primary_key=True)
|
id = db.Column(db.String(36), primary_key=True)
|
||||||
name = db.Column(db.Text, unique=True, nullable=False)
|
name = db.Column(db.Text, unique=True, nullable=False)
|
||||||
description = db.Column(db.Text)
|
description = db.Column(db.Text)
|
||||||
items = db.relationship("PackageListItem", backref="category", lazy=True)
|
weight = db.Column(db.Integer, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
# )
|
|
||||||
category_id = db.Column(
|
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 uuid
|
||||||
import os
|
import os
|
||||||
|
import urllib
|
||||||
|
import datetime
|
||||||
|
|
||||||
import dominate
|
import dominate
|
||||||
import dominate.tags as t
|
import dominate.tags as t
|
||||||
from dominate.util import raw
|
from dominate.util import raw
|
||||||
|
|
||||||
from .components import (
|
from .components import (
|
||||||
PackageListManager,
|
InventoryItemManager,
|
||||||
PackageListItemManager,
|
Base,
|
||||||
NewPackageList,
|
|
||||||
Home,
|
Home,
|
||||||
PackageListTableRowEdit,
|
TripList,
|
||||||
PackageListTableRowNormal,
|
TripManager,
|
||||||
PackageListTableRow,
|
InventoryItemDetails,
|
||||||
)
|
)
|
||||||
|
|
||||||
from flask import request, make_response
|
from flask import request, make_response
|
||||||
|
|
||||||
|
|
||||||
def get_packagelists():
|
|
||||||
return PackageList.query.all()
|
|
||||||
|
|
||||||
|
|
||||||
def get_categories():
|
def get_categories():
|
||||||
return PackageListItemCategory.query.all()
|
return InventoryItemCategory.query.all()
|
||||||
|
|
||||||
|
|
||||||
|
def get_trips():
|
||||||
|
return Trip.query.all()
|
||||||
|
|
||||||
|
|
||||||
def get_all_items():
|
def get_all_items():
|
||||||
return PackageListItem.query.all()
|
return InventoryItem.query.all()
|
||||||
|
|
||||||
|
|
||||||
|
def get_triptypes():
|
||||||
|
return TripType.query.all()
|
||||||
|
|
||||||
|
|
||||||
def get_items(category):
|
def get_items(category):
|
||||||
return PackageListItem.query.filter_by(category_id=str(category.id))
|
return InventoryItem.query.filter_by(category_id=str(category.id))
|
||||||
|
|
||||||
def get_packagelist_by_id(id):
|
|
||||||
return PackageList.query.filter_by(id=str(id)).first()
|
|
||||||
|
|
||||||
|
|
||||||
def add_packagelist(name, description):
|
def get_item(id):
|
||||||
try:
|
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(
|
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()
|
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("/")
|
@app.route("/")
|
||||||
def root():
|
def root():
|
||||||
|
return make_response(Base(Home(), app.root_path).doc.render(), 200)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/inventory/")
|
||||||
|
def inventory():
|
||||||
categories = get_categories()
|
categories = get_categories()
|
||||||
items = get_all_items()
|
items = get_all_items()
|
||||||
|
|
||||||
|
args = request.args.to_dict()
|
||||||
|
|
||||||
error = False
|
error = False
|
||||||
if not is_htmx():
|
if not is_htmx():
|
||||||
edit = request.args.get("edit")
|
edit = request.args.get("edit")
|
||||||
if edit is not None:
|
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:
|
if match:
|
||||||
match[0].edit = True
|
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(
|
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):
|
def category(id):
|
||||||
categories = get_categories()
|
categories = get_categories()
|
||||||
print(id)
|
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 = [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)
|
items = get_items(active_category)
|
||||||
error = False
|
error = False
|
||||||
if not is_htmx():
|
if not is_htmx():
|
||||||
edit = request.args.get("edit")
|
edit = request.args.get("edit")
|
||||||
if edit is not None:
|
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:
|
if match:
|
||||||
match[0].edit = True
|
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(
|
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
|
return request.headers.get("HX-Request") is not None
|
||||||
|
|
||||||
|
|
||||||
@app.route("/list/", methods=["POST"])
|
@app.route("/inventory/item/", methods=["POST"])
|
||||||
def add_new_list():
|
def add_new_item():
|
||||||
name = request.form["name"]
|
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 = 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
|
return r
|
||||||
|
|
||||||
|
|
||||||
@@ -169,93 +483,92 @@ def validate_name(name):
|
|||||||
return error, errormsg
|
return error, errormsg
|
||||||
|
|
||||||
|
|
||||||
@app.route("/list/name/validate", methods=["POST"])
|
@app.route("/inventory/item/<uuid:id>/edit/submit/", methods=["POST"])
|
||||||
def validate_list_name():
|
def edit_item_submit(id):
|
||||||
name = request.form["name"]
|
name = request.form["name"]
|
||||||
|
weight = int(request.form["weight"])
|
||||||
|
|
||||||
error, errormsg = validate_name(name)
|
item = InventoryItem.query.filter_by(id=str(id)).first()
|
||||||
|
if item is None:
|
||||||
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:
|
|
||||||
# todo what to do without js?
|
# todo what to do without js?
|
||||||
return make_response("", 404)
|
return make_response("", 404)
|
||||||
|
|
||||||
pkglist.name = name
|
item.name = name
|
||||||
pkglist.description = description
|
item.weight = weight
|
||||||
|
|
||||||
try:
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except sqlalchemy.exc.IntegrityError:
|
|
||||||
db.session.rollback()
|
r = make_response("", 303)
|
||||||
errormsg = f'Name "{name}" already exists'
|
r.headers["Location"] = f"/inventory/category/{item.category.id}"
|
||||||
if is_htmx():
|
return r
|
||||||
pkglist.error = True
|
|
||||||
pkglist.errormsg = errormsg
|
|
||||||
return make_response(PackageListTableRowEdit(pkglist).doc.render(), 200)
|
@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:
|
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 = 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
|
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:
|
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 = make_response("", 303)
|
||||||
r.headers["Location"] = "/"
|
r.headers["Location"] = f"/inventory/category/{item.category.id}"
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
@app.route("/list/<uuid:id>/edit", methods=["POST"])
|
@app.route("/inventory/item/<uuid:id>", methods=["DELETE"])
|
||||||
def edit_list(id):
|
def delete_item(id):
|
||||||
pkglist = get_packagelist_by_id(id)
|
deletions = InventoryItem.query.filter_by(id=str(id)).delete()
|
||||||
|
if deletions == 0:
|
||||||
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):
|
|
||||||
return make_response("", 404)
|
return make_response("", 404)
|
||||||
|
else:
|
||||||
|
db.session.commit()
|
||||||
return make_response("", 200)
|
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
|
click==8.1.3
|
||||||
dominate==2.6.0
|
dominate==2.6.0
|
||||||
Flask==2.1.2
|
Flask==2.1.2
|
||||||
|
Flask-Migrate==3.1.0
|
||||||
Flask-SQLAlchemy==2.5.1
|
Flask-SQLAlchemy==2.5.1
|
||||||
greenlet==1.1.2
|
greenlet==1.1.2
|
||||||
importlib-metadata==4.12.0
|
importlib-metadata==4.12.0
|
||||||
|
importlib-resources==5.9.0
|
||||||
itsdangerous==2.1.2
|
itsdangerous==2.1.2
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
|
Mako==1.2.2
|
||||||
MarkupSafe==2.1.1
|
MarkupSafe==2.1.1
|
||||||
SQLAlchemy==1.4.39
|
SQLAlchemy==1.4.39
|
||||||
Werkzeug==2.1.2
|
Werkzeug==2.1.2
|
||||||
|
|||||||
@@ -5,4 +5,8 @@ source ./venv/bin/activate
|
|||||||
export FLASK_APP=packager
|
export FLASK_APP=packager
|
||||||
export FLASK_ENV=development
|
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