haldis/app/templates/order.html
Midgard ff0ea068de
Don't crash on orders that don't have a slug
Orders created before we introduced slugs don't have a slug. This commit
introduces code to work with them. Without these changes, the legacy
orders are not reachable any more, and trying to create a link for them,
crashes the page.

I wrote this commit because in my test environment I had a long-lived
order for testing purposes, and the home page crashed because the order
would show up in the list of Open Orders.
2022-05-25 10:55:33 +02:00

591 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "layout.html" %}
{% set active_page = "orders" -%}
{% if current_user.is_anonymous() %}
{% set my_items = order.for_user(anon=session.get("anon_name", "")) %}
{% else %}
{% set my_items = order.for_user(user=current_user) %}
{% endif %}
{% set courier_or_admin = not current_user.is_anonymous() and (current_user.is_admin() or current_user.id == order.courier_id) -%}
{% import "utils.html" as util %}
{% block metas %}
{{ super() }}
<meta name="robots" content="noindex, nofollow">
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/qrcode.min.js') }}"></script>
{% endblock %}
{% block container %}
<header class="row">
<div class="col-md-2" style="padding-top: 2em">
<div id="qrcode"></div>
<script type="text/javascript">
var qrcode = new QRCode(document.getElementById("qrcode"), {
text: "{{ url_for("order_bp.order_from_slug", order_slug=order.slug or order.id, _external=True) }}",
width: 128,
height: 128,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
</script>
</div>
<div class="col-md-10">
<h2 id="order-title">Order {{ order.id }}</h2>
<div class="location">
{% if order.location %}
<a href="{{ url_for('general_bp.location', location_id=order.location_id) }}">{{ order.location_name }}</a>
{% else %}
{{ order.location_name }}
{% endif %}
</div>
<div>
Unique order link: <code>{{ url_for("order_bp.order_from_slug", order_slug=order.slug or order.id, _external=True) }}</code>
</div>
</div>
</header>
<section>
<div class="column">
<div class="box" id="my_items">
<h3>My items</h3>
{% if my_items %}
<ul>
{% for item in my_items %}
<li class="spacecake">
{% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%}
<form action="{{ url_for('order_bp.delete_item', order_slug=order.slug or order.id, item_id=item.id) }}" method="post" style="display:inline">
<button class="btn btn-link btn-sm" type="submit" style="padding: 0 0.5em;"><span class="glyphicon glyphicon-remove"></span></button>
</form>
{%- endif %}
<span>{{ item.dish_name }}{% if item.comment %}; {{ item.comment }}{% endif %}</span><span class="spacer"> </span><span class="price">{{ item.price | euro }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<div>(None)</div>
{% endif %}
</div>
{% if form %}
<!--
<div class="box" id="from_favourites">
<h3>Add from favourites</h3>
<ul>
<li>Todo</li>
</ul>
</div>
-->
<div class="box" id="add_item">
<h3>Add item to order</h3>
{% for dish in order.location.dishes %}
<form method="post" action="{{ url_for('order_bp.order_item_create', order_slug=order.slug or order.id) }}" id="dish_{{ dish.id }}">
{{ form.csrf_token }}
<input type="hidden" name="dish_id" value="{{ dish.id }}" />
{% if form.dish_id.errors %}
<div class="form-group has-errors">
{{ util.render_form_field_errors(form.dish_id) }}
</div>
{% endif %}
<details {% if dish.id == selected_dish.id %}open="open"{% endif %}>
<summary class="spacecake">
<span class="dish_name">{{ dish.name }}</span>
<!--
{% if dish.tags %}<span class="tags"> {{ dish.tags | join(", ") }}</span>{% endif %}
-->
<span class="spacer"></span>
<span class="price">{{ dish.price_range() | price_range }}</span>
</summary>
{% if dish.description %}<div class="description">{{ dish.description }}</div>{% endif %}
{% for (choice_type, choice) in dish.choices %}
<div class="form-group select2-container select2">
<label class="control-label" for="choice_{{ choice.id }}">{{ choice.name }}</label><br/>
<select
{{ "multiple=multiple" if choice_type=="multi_choice" else "required=required" }}
name="choice_{{ choice.id }}"
class="form-control select">
{% for option in choice.options %}
<option value="{{ option.id }}"><!--
-->{{ option.name }}{{ ": " + option.price|euro if option.price else "" }}<!--
-->{{ " (" + option.description + ")" if option.description else "" }}<!--
--></option>
{% endfor %}
</select>
</div>
{% endfor %}
<div class="form-group {{ 'has-errors' if form.dish_id.errors }}">
{{ form.comment.label }}<br>
{{ form.comment(class='form-control', placeholder='Fill in comment, when applicable') }}
{{ util.render_form_field_errors(form.comment) }}
</div>
{% if current_user.is_anonymous() %}
<div class="form-group{{ ' has-error' if form.user_name.errors }}{{ ' required' if form.user_name.flags.required }}">
{{ form.user_name.label(class='control-label') }}
{{ form.user_name(class='form-control', placeholder='Fill in your name...') }}
{{ util.render_form_field_errors(form.user_name) }}
</div>
{% endif %}
<div class="form-group" style="padding-top: 8px;">
{{ form.submit_button(class='btn btn-primary') }}
</div>
</details>
</form>
{% endfor %}
</div>
{% endif %}
{% if form %}
</div>
<div class="column">
{% endif %}
<div class="box" id="order_info">
<h3>Order information</h3>
<dl>
<div>
<dt>Order opens</dt>
<dd>{{ order.starttime.strftime("%Y-%m-%d, %H:%M") }}</dd>
<dt>Order closes</dt>
<dd>
{% if order.stoptime %}
{% set stoptimefmt = (
"%H:%M" if order.stoptime.date() == order.starttime.date()
else "%Y-%m-%d, %H:%M"
) %}
{{ order.stoptime.strftime(stoptimefmt) }} ({{ order.stoptime|countdown }})
{% else %}
Never
{% endif %}
</dd>
</div>
<div>
<dt>Location</dt>
<dd>
{% if order.location %}
<a href="{{ url_for('general_bp.location', location_id=order.location_id) }}">{{ order.location_name }}</a>
{% else %}
{{ order.location_name }}
{% endif %}
</dd>
<dt>Courier</dt>
<dd>
{% if order.courier == None %}
{% if not current_user.is_anonymous() %}
<form action="{{ url_for('order_bp.volunteer', order_slug=order.slug or order.id) }}" method="post" style="display:inline">
<input type="submit" class="btn btn-primary btn-sm" value="Volunteer"></input>
</form>
{% else %}No-one yet{% endif %}
{% else %}
{{ order.courier.username }}
{% endif %}
</dd>
</div>
</dl>
<div>
{% if order.can_close(current_user.id) -%}
<form action="{{ url_for('order_bp.close_order', order_slug=order.slug or order.id) }}" method="post" style="display:inline">
<input type="submit" class="btn btn-danger" value="Close"></input>
</form>
{% endif %}
{% if courier_or_admin %}
<a class="btn" href="{{ url_for('order_bp.order_edit', order_slug=order.slug or order.id) }}">Edit</a>
{%- endif %}
</div>
</div>
<div class="box" id="how_to_order">
<h3>About {{ order.location_name }}</h3>
<dl>
{% if order.location.telephone %}
<div>
<dt>Telephone</dt>
<dd><a href="tel:{{ order.location.telephone }}">{{ order.location.telephone }}</a></dd>
</div>
{% endif %}
{% if order.location.website %}
<div>
<dt>Website</dt>
<dd><a href="{{ order.location.website }}">{{ order.location.website }}</a></dd>
</div>
{% endif %}
{% if order.location.address or order.location.osm %}
<div>
<dt>Location</dt>
<dd>
{% if order.location.osm %}
<a href="{{ order.location.osm }}">{{ order.location.address or "View on OSM" }}</a>
{% else %}
{{ order.location.address }}
{% endif %}
</dd>
</div>
{% endif %}
</dl>
</div>
{% if not form %}
</div>
<div class="column">
{% endif %}
<div class="box" id="per_dish">
<h3>Ordered dishes</h3>
{% 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_quantity }}</span> ×
{{ dish_name }}
</h4>
{% if has_comments -%}
<ul class="comments">
{% 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><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_comment_groups[0][1] | map(attribute="for_name") | join(", ") }}</span>
{%- endif %}
</p>
</div>
{%- endfor %}
<div class="footer">
Total {{ order.items.count() }} items — {{ total_price|euro }}
&nbsp;
<a class="btn btn-sm" href="{{ url_for('order_bp.items_shop_view', order_slug=order.slug or order.id) }}">Shop view</a>
</div>
</div>
</div>
</section>
<section class="single_column">
<div class="box" id="per_person">
<h3>Items per person</h3>
<form action="{{ url_for('order_bp.modify_items', order_slug=order.slug or order.id) }}" method="post">
<table class="table table-condensed">
<thead>
<tr><th>Total</th><th>Name</th><th>Items</th></tr>
</thead>
<tbody>
{% for user_name, order_items in order.group_by_user() -%}
<tr>
<td>
{% set paid = order_items | map(attribute="paid") | all %}
<input type="checkbox" name="user_names" value="{{ user_name }}"
{{ "disabled" if not order.can_modify_payment(current_user.id) }}>
<span class="price" style="{{ 'opacity: 0.5' if paid }}">
{{ order_items | map(attribute="price") | ignore_none | sum | euro }}
</span>
{% if paid %}<span class="glyphicon glyphicon-ok" style="opacity: 0.5"></span>{% endif %}
</td>
<td style="{{ 'opacity: 0.5' if paid }}">{{ user_name }}</td>
<td class="items">
<ul>
{% for item in order_items %}
<li class="{{ 'paid' if item.paid }}">
<div class="actions">
{% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%}
<form action="{{ url_for('order_bp.delete_item', order_slug=order.slug or order.id, item_id=item.id) }}" method="post" style="display:inline">
<button class="btn btn-link btn-sm" type="submit" style="padding: 0 0.5em;"><span class="glyphicon glyphicon-remove"></span></button>
</form>
{% else %}
<span class="glyphicon glyphicon-remove" style="color: var(--gray3); padding: 0 0.5em; cursor: not-allowed"></span>
{%- endif %}
</div>
<div class="price_aligned">
{{ item.price|euro }}
{% if item.price_modified %}
<span class="glyphicon glyphicon-pencil" style="opacity: 0.5" title="Edited"></span>
{% endif %}
</div>
<div class="item_description">{{ item.dish_name }}{{ "; " + item.comment if item.comment }}</div>
</li>
{% endfor %}
</ul>
</td>
</tr>
{%- endfor %}
</tbody>
</table>
<div class="footer">
{% if order.can_modify_payment(current_user.id) %}
On selected:
<button name="action" value="mark_paid" class="btn btn-sm"><span class="glyphicon glyphicon-ok"></span> Mark paid</button>
<button name="action" value="mark_unpaid" class="btn btn-sm">Mark unpaid</button>
{% endif %}
{% if order.can_modify_prices(current_user.id) %}
&nbsp; <span style="border-left: 1px solid var(--gray0); display: inline-block;">&nbsp;</span>&nbsp;
<a href="{{ url_for('order_bp.prices', order_slug=order.slug or order.id) }}" class="btn btn-sm">
<span class="glyphicon glyphicon-pencil"></span> Edit prices
</a>
{% endif %}
</div>
</form>
</div>
</section>
{% endblock %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="{{ url_for('static', filename='css/select2.min.css') }}" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/select2-bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/print.css') }}">
<style>
body, h1, h2, h3, h4 {
color: var(--gray0);
}
table {
overflow-wrap: break-word;
}
@media (min-width: 1200px) {
section {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 0 30px;
align-items: start;
}
}
section.single_column {
grid-template-columns: unset;
}
.description {
margin-bottom: 10px;
}
header {
margin-bottom: 25px;
}
h2 {
margin-bottom: 0;
}
h3 {
margin-top: 0;
font-size: 150%;
font-weight: 500;
}
h4 {
margin-bottom: 0.5em;
font-size: 110%;
font-weight: 500;
}
.location {
font-size: 150%;
font-weight: 500;
margin-left: 3px;
margin-top: -5px;
}
.box {
width: 100%;
border: 2px solid var(--gray0);
padding: 10px;
margin-bottom: 30px;
}
@media (min-width: 500px) {
#order_info dl {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
#from_favourites ul, #my_items ul, #per_person ul {
list-style-type: none;
padding: 0;
}
dl {
margin-bottom: 10px;
}
.box :last-child {
margin-bottom: 0;
}
.spacecake {
display: flex;
align-items: flex-end;
}
.spacecake .tags {
padding-left: 0.5em;
color: var(--gray2);
}
.spacecake .spacer {
content: ' ';
flex-grow: 1;
min-width: 10px;
border-bottom: 1px solid var(--gray3);
margin: 0.3em 0.5em;
}
.main li {
line-height: 1.5;
margin: 0.4em 0;
}
#my_items li form {
align-self: flex-start;
}
#per_dish ul {
margin-bottom: 0;
}
#per_dish h4 {
margin-top: 0;
}
#per_dish .dish.no_comments h4 {
margin-bottom: 0;
line-height: inherit;
}
#per_dish .dish {
margin-bottom: 13px;
}
#per_dish .item_for {
color: var(--gray2);
text-align: right;
}
#per_dish .comments {
padding-left: 1.5em;
list-style-type: none;
}
#per_person li {
white-space: nowrap;
margin-top: 0.15em;
vertical-align: top;
}
#per_person li > * {
display: inline-block;
vertical-align: top;
}
#per_person .item_description {
white-space: normal;
}
.price, .price_aligned {
white-space: nowrap;
}
.price_aligned {
display: inline-block;
width: 4em;
}
.items .paid {
opacity: 0.4;
}
.footer {
margin-top: 1em;
}
summary {
line-height: 1.2;
margin: 0 -10px;
padding: 4px 10px;
}
summary .dish_name, summary:before {
align-self: flex-start;
}
details[open] summary .dish_name {
font-weight: bold;
}
details {
margin: 0 -10px;
padding: 0 10px;
}
details[open] {
background-color: var(--gray5);
padding-bottom: 5px;
}
details:not([open]) summary:hover {
background-color: var(--gray5);
}
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
padding: 0 3px;
}
.select2-container .select2-selection--multiple {
min-height: 20px;
line-height: 1;
}
.select2-container .select2-search--inline .select2-search__field {
margin-top: 3px;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
margin-top: 4px;
}
.select2-container li {
margin: 0;
}
.select2-selection--multiple .select2-search.select2-search--inline .select2-search__field:not(:focus) {
border-color: transparent;
box-shadow: none;
}
</style>
{% endblock %}
{% block scripts %}
{{ super() }}
<script src="{{ url_for('static', filename='js/select2.min.js') }}"></script>
{% if form %}
<script type="text/javascript">
"use strict";
function openDish(detailsEl, open=null) {
let openAttr = detailsEl.getAttribute("open");
let isOpen = openAttr || openAttr == "";
$("details").removeAttr("open");
if (open === true || open === null && !isOpen) {
detailsEl.setAttribute("open", true);
$(detailsEl).find(".select").select2({"sorter": x => x.sort()});
let id = detailsEl.parentNode.getAttribute("id");
window.history.replaceState(null, "", `#${id}`);
} else {
window.history.replaceState(null, "",
`${window.location.origin}${window.location.pathname}`);
}
}
$("summary").on("click", e => { openDish(e.currentTarget.parentNode); return false; });
$(window).on("load", () => {
let m = window.location.hash.match(/^#dish_[a-z0-9_-]*$/);
if (m[0]) {
openDish($(m[0]).find("details")[0], open=true);
}
});
</script>
{% endif %}
{% endblock %}