Compare commits

...

6 commits

Author SHA1 Message Date
76ac07742e
Allow anonymous order creation 2021-07-08 12:02:50 +02:00
f0561bcd71
Remove pills introduced in previous commit 2021-06-21 02:08:32 +02:00
d7dc926992
Aggregate comments 2021-06-21 02:05:11 +02:00
d20ce9803e
Improve design
Remove main CSS from shop view page. Make it theme independent and
maximize contrast.

Improve spacing in "add item" list.

Refactor old term "showcase" to "shop_view" in code.
2021-06-21 00:46:29 +02:00
d0699b3716
HLDS: change :: to double space and require it
Require double space before tags and price, like in the plain text
accounting format of ledger. This makes it easier to differentiate
between prices mentioned in descriptions and the price for the dish.
2021-06-20 23:57:50 +02:00
a1a68a3fd6
Add space at bottom in show view
On big orders the bottom of the page was awkward.
2021-06-20 21:47:24 +02:00
12 changed files with 184 additions and 126 deletions

View file

@ -37,15 +37,12 @@ class OrderForm(Form):
def populate(self) -> None:
"Fill in the options for courier for an Order"
if current_user.is_admin():
self.courier_id.choices = [(0, None)] + [
(u.id, u.username) for u in User.query.order_by("username")
]
else:
self.courier_id.choices = [
(0, None),
(current_user.id, current_user.username),
]
self.courier_id.choices = [(0, None)] + (
[(u.id, u.username) for u in User.query.order_by("username")] if current_user.is_admin()
else [(current_user.id, current_user.username)] if current_user.is_authenticated()
else []
)
self.location_id.choices = [(l.id, l.name) for l in location_definitions]
if self.stoptime.data is None:
self.stoptime.data = datetime.now() + timedelta(hours=1)

View file

@ -29,10 +29,12 @@ location = >location_header items:{ block } ;
attributes =
name:/[^\n#]*?(?= +-- | +:: | +€ | *\n| *#)/
[ s '--' ~ s description:/[^\n#]*?(?= +:: | +€ | *\n| *#)/ ]
[ s '::' {s ('{' tags+:identifier '}')} ]
[ [ s '::' ~ ] s price:price ]
name:/[^\n#]*?(?= +-- | | *\n| *#)/
[ s '--' ~ s description:/[^\n#]*?(?= | *\n| *#)/ ]
[ / {2,}/ ~
[ {[ s ] ('{' tags+:identifier '}')} / +|$/ ]
[ price:price ]
]
;
@ -73,7 +75,7 @@ indent_choice_block =
;
s = / +/ ;
s = / +/ ;
n = '\n' {{'\t'} '\n'} ;
@name

View file

@ -1,6 +1,7 @@
"Script for everything Order related in the database"
import typing
from datetime import datetime
from collections import defaultdict
from utils import first
from hlds.definitions import location_definitions
@ -69,23 +70,27 @@ class Order(db.Model):
return list(sorted(group.items(), key=lambda t: (t[0] or "", t[1] or "")))
def group_by_dish(self) -> typing.List[typing.Tuple[str, typing.List]]:
def group_by_dish(self) \
-> typing.List[typing.Tuple[str, int, typing.List[typing.Tuple[str, typing.List]]]]:
"Group items of an Order by dish"
group: typing.Dict[str, typing.List] = dict()
group: typing.Dict[str, typing.Dict[str, typing.List]] = \
defaultdict(lambda: defaultdict(list))
for item in self.items:
if item.dish_name not in group:
group[item.dish_name] = []
group[item.dish_name][item.comment].append(item)
group[item.dish_name].append(item)
for _dish_name, order_items in group.items():
order_items.sort(key=lambda order_item: (
(order_item.comment or " No comment") +
(order_item.for_name or "")
))
return list(sorted(group.items()))
return sorted(
(
dish_name,
# Amount of items of this dish
sum(map(len, comment_group.values())),
sorted(
(comment, sorted(items, key=lambda x: (x.for_name or "")))
for comment, items in comment_group.items()
)
)
for dish_name, comment_group in group.items()
)
def is_closed(self) -> bool:
return self.stoptime and datetime.now() > self.stoptime

View file

@ -60,64 +60,6 @@ body {
width: 100%;
}
.showcase {
font-size: 16px;
line-height: 1.2;
}
.showcase {
padding: 0;
max-width: 500px;
margin: 0 auto;
}
.showcase h1 {
font-size: 200%;
margin: 0 ;
padding: 0.4em 0 0.2em;
border-bottom: 1px dashed var(--gray1);
text-align: center;
}
.showcase h2 {
font-size: 110%;
font-weight: bold;
margin-bottom: 0.6em;
}
.showcase .open-order-warning {
font-size: 150%;
text-align: center;
padding: 1em 0.5em;
background-color: rgba(255, 0, 0, 0.1);
}
.showcase .dish {
padding: 0 0.5em;
}
@media (min-width: 500px) {
.showcase .dish {
padding: 0 1em;
}
}
.showcase .quantity {
font-size: 110%;
}
.showcase .comments {
padding-left: 2em;
}
.showcase .comments li {
margin: 0.5em 0 0;
}
.showcase .total {
border-top: 1px dashed var(--gray1);
text-align: center;
padding: 0.5em 0;
margin-top: 1.3em;
}
.time_data{
display: flex;
justify-content: space-between;

View file

@ -0,0 +1,109 @@
:root {
--bg: #eee;
--ticketBg: #fff;
--fg: #000;
--dashes: 1px dashed #444;
--fontFamily: "Roboto","Helvetica Neue",Helvetica,Arial,sans-serif;
--fontSize: 16px;
}
html, body {
margin: 0;
padding: 0;
}
body {
background: var(--bg);
color: var(--fg);
font-family: var(--fontFamily);
font-size: var(--fontSize);
line-height: 1.2;
}
.ticket {
background: var(--ticketBg);
font-size: 16px;
padding: 0;
max-width: 500px;
margin: 25px auto;
margin: 7vh auto;
box-shadow: 0 0.15em 0.3em rgba(0, 0, 0, 0.2);
}
@media (max-width: 500px) {
body {
background: var(--ticketBg);
}
.ticket {
margin: 0;
box-shadow: none;
}
}
h1 {
font-size: 200%;
margin: 0 ;
padding: 0.3em 0 0.3em;
border-bottom: var(--dashes);
text-align: center;
}
h2 {
font-size: 110%;
font-weight: bold;
margin-bottom: 0.6em;
}
.open-order-warning {
font-size: 150%;
text-align: center;
padding: 1em 0.5em;
background-color: rgba(255, 0, 0, 0.1);
}
.dish {
padding: 0 0.5em;
}
@media (min-width: 500px) {
.dish {
padding: 0 1em;
}
}
.quantity {
font-size: 110%;
}
.comments {
padding-left: 1.5em;
list-style-type: none;
}
.comments li {
margin: 0.5em 0 0;
}
.comment_part {
background-color: #f7f7f7;
border: 1px solid #ddd;
padding: 0.1em 0.5em;
margin-top: 2px;
margin-right: 0.4em;
border-radius: 1em;
display: inline-block;
}
.comment_part_separator {
display: inline-block;
width: 0;
overflow: hidden;
}
.total {
border-top: var(--dashes);
text-align: center;
padding: 0.5em 0;
margin-top: 1.3em;
}
.time_data{
display: flex;
justify-content: space-between;
}

View file

@ -230,24 +230,26 @@
<div class="box" id="per_dish">
<h3>Ordered dishes</h3>
{% for dish_name, dish_order_items in order.group_by_dish() -%}
{% set has_comments = dish_order_items | map(attribute="comment") | any -%}
{% for dish_name, dish_quantity, dish_comment_groups in order.group_by_dish() -%}
{% set has_comments = dish_comment_groups | length > 1 or (dish_comment_groups | map("first") | any) -%}
<div class="dish {{ 'spacecake no_comments' if not has_comments }}">
<h4>
<span class="quantity">{{ dish_order_items | length }}</span> ×
<span class="quantity">{{ dish_quantity }}</span> ×
{{ dish_name }}
</h4>
{% if has_comments -%}
<ul class="comments">
{% for item in dish_order_items -%}
<li class="spacecake">{% if item["comment"] %}<span>{{ item["comment"] }}</span>
{% for comment, items in dish_comment_groups -%}
<li class="spacecake"><span class="comment">
<span class="quantity">{{ items | length }}</span> ×
{% if comment %}{{ comment }}
{% else %}<i>No comment</i>
{% endif %}<span class="spacer"> </span><span class="item_for">for {{ item.for_name }}</span></li>
{% endif %}</span><span class="spacer"> </span><span class="item_for">for {{ items | map(attribute="for_name") | join(", ") }}</span></li>
{% endfor %}
</ul>
{% else %}
<span class="spacer"> </span><span class="item_for">for {{ dish_order_items | map(attribute="for_name") | join(", ") }}</span>
<span class="spacer"> </span><span class="item_for">for {{ dish_comment_groups[0][1] | map(attribute="for_name") | join(", ") }}</span>
{%- endif %}
</p>
@ -256,7 +258,7 @@
<div class="footer">
Total {{ order.items.count() }} items — {{ total_price|euro }}
&nbsp;
<a class="btn btn-sm" href="{{ url_for('order_bp.items_showcase', order_id=order.id) }}">Shop view</a>
<a class="btn btn-sm" href="{{ url_for('order_bp.items_shop_view', order_id=order.id) }}">Shop view</a>
</div>
</div>
</div>
@ -313,9 +315,7 @@
<div class="footer">
On selected:
<button class="btn btn-sm">Mark paid (TODO)</button>
Request payment for selected:
<button class="btn btn-sm"><span class="glyphicon glyphicon-ok"></span> Mark paid (TODO)</button>
<button class="btn btn-sm"><span class="glyphicon glyphicon-piggy-bank"></span> Tab (TODO)</button>
<button class="btn btn-sm"><span class="glyphicon glyphicon-qrcode"></span> QR code (TODO)</button>
</div>
@ -439,6 +439,10 @@ li {
color: var(--gray2);
text-align: right;
}
#per_dish .comments {
padding-left: 1.5em;
list-style-type: none;
}
#per_person li {
@ -472,7 +476,8 @@ li {
summary {
line-height: 1.2;
margin: 0.6em 0;
margin: 0 -10px;
padding: 4px 10px;
}
summary .dish_name, summary:before {
align-self: flex-start;
@ -480,14 +485,16 @@ summary .dish_name, summary:before {
details[open] summary .dish_name {
font-weight: bold;
}
details {
margin: 0 -10px;
padding: 0 10px;
}
details[open] {
background-color: var(--gray5);
margin-left: -10px;
margin-right: -10px;
margin-bottom: 5px;
padding-left: 10px;
padding-right: 10px;
padding-bottom: 10px;
padding-bottom: 5px;
}
details:not([open]) summary:hover {
background-color: var(--gray5);
}
.select2-container--default .select2-selection--multiple .select2-selection__rendered {

View file

@ -7,9 +7,7 @@ Haldis - Order {{ order.id }}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}" media="screen">
<link rel="stylesheet" href="{{ url_for('general_bp.theme_css') }}" media="screen">
<link rel="stylesheet" href="{{ url_for('static', filename='css/print.css') }}" media="print">
<link rel="stylesheet" href="{{ url_for('static', filename='css/shop_view.css') }}" media="screen">
{% endblock %}
{% block scripts %}
@ -23,7 +21,7 @@ Haldis - Order {{ order.id }}
{% block content -%}
{{ utils.flashed_messages(container=True) }}
<div class="darker showcase" id="items-ordered">
<div class="ticket" id="items-ordered">
<h1>Haldis order {{ order.id }}</h1>
{% if not order.is_closed() %}
@ -33,13 +31,14 @@ Haldis - Order {{ order.id }}
</div>
{% endif %}
{% for dish_name, dish_order_items in order.group_by_dish() -%}
{% for dish_name, dish_quantity, dish_comment_groups in order.group_by_dish() -%}
<div class="dish">
<h2><span class="quantity">{{ dish_order_items | length }}</span> × {{ dish_name }}</h2>
{% if dish_order_items | map(attribute="comment") | any -%}
<h2><span class="quantity">{{ dish_quantity }}</span> × {{ dish_name }}</h2>
{% if dish_comment_groups | map("first") | any -%}
<ul class="comments">
{% for item in dish_order_items -%}
<li>{% if item["comment"] %}{{ item["comment"] }}
{% for comment, items in dish_comment_groups -%}
<li><span class="quantity">{{ items | length }}</span> ×
{% if comment %}{{ comment }}
{% else %}<i>No comment</i>
{% endif %}</li>
{% endfor %}

View file

@ -14,14 +14,14 @@
{% endfor %}
{% else %}
<h4>No orders available.</h4>
{% if not current_user.is_anonymous() %}
{% if form %}
To create an order, fill in the form on the right.
{% else %}
Login to create an order, or ask someone else.
{% endif %}
{%- endif %}
</div>
{% if not current_user.is_anonymous() %}
{% if form %}
<div class="col-md-push-1 col-md-6">
<h3>Create new order</h3>
<div class="row darker">

View file

@ -31,7 +31,7 @@ order_bp = Blueprint("order_bp", "order")
@order_bp.route("/")
def orders(form: OrderForm = None) -> str:
"Generate general order view"
if form is None and not current_user.is_anonymous():
if form is None:
form = OrderForm()
location_id = request.args.get("location_id")
form.location_id.default = location_id
@ -88,7 +88,7 @@ def order_from_id(order_id: int, form: OrderForm = None, dish_id=None) -> str:
@order_bp.route("/<order_id>/items")
def items_showcase(order_id: int) -> str:
def items_shop_view(order_id: int) -> str:
"Generate order items view from id"
order = Order.query.filter(Order.id == order_id).first()
if order is None:

View file

@ -1 +1,2 @@
setlocal noexpandtab
setlocal softtabstop=0

View file

@ -25,8 +25,9 @@ syn keyword hldsChoiceType single_choice multi_choice nextgroup=hldsBlockIdAf
syn match hldsBlockId "^[a-z0-9_-]\+: "
syn match hldsBlockIdAftrKywrd "[a-z0-9_-]\+: " contained
syn match hldsTag " {[a-z0-9_-]\+}"
syn match hldsPrice "€ *[0-9]\+\(\.[0-9]\+\|\)"
syn match _doubleSpace " \+" nextgroup=hldsTag,hldsPrice
syn match hldsTag "{[a-z0-9_-]\+}\( \|$\)" contained nextgroup=hldsTag,hldsPrice
syn match hldsPrice "€ *[0-9]\+\(\.[0-9]\+\|\)\( \|$\)" contained
syn match hldsComment "#.*$" contains=hldsTodo,@Spell
syn keyword hldsTodo FIXME NOTE NOTES TODO XXX contained

View file

@ -33,13 +33,8 @@
"tags": {
"patterns": [
{
"match": "(::)\\s*({[a-zA-Z-_]*}\\s*)*",
"name": "markup.italic",
"captures": {
"1": {
"name": "markup.bold"
}
}
"match": " +( +{[a-zA-Z-_]*})*",
"name": "markup.italic"
}
]
},
@ -94,4 +89,4 @@
}
},
"scopeName": "source.hlds"
}
}