{% extends "base.html" %} {% block title %}Inventory · foilstack{% endblock %} {% block status %}{{ totals.count }} in stock · {{ totals.market|money }} at market{% if totals.sold_rows %} · {{ totals.sold_rows }} sold, {{ totals.realised|money }} realised{% endif %}{% endblock %} {% block screen %} {# Nowrap for the reason the listing run is: at 1000px, the width the README animation is captured at, the status chips pushed the primary button onto a row of its own and the facet row below it then read as four rows of chrome above the table. The give is the search box, which shrinks. #}

Inventory

{# The filters and the sort, carried through, so searching narrows what is on screen rather than throwing the rest of the controls away. #} {% for name, value in carry %}{% endfor %}
{% for c in status_chips %} {{ c.label }} {{ c.count }} {% endfor %}
{{ rows|length }}{% if narrowed %} of {{ total_lines }}{% endif %} lines
{# Disabled rather than hidden, unlike the Delete button beside it. That one is absent at rest because it is destructive and there is nothing to mis-click; this one is the only thing on the screen that says selecting rows leads anywhere, so it has to stay visible to be the instruction. It starts disabled in the markup rather than waiting for `sync()`, because a live primary button for one parse is one a fast hand can press — and until this it submitted no ids at all, which `/listings` reads as the whole inventory and prices a run over everything the account owns. The filter the seller was looking at is not on that form, so the screen they left said 75 unlisted and the one they arrived at answered for all of it. #}
{% if facets %}
{% for f in facets %}
{{ f.label }} {% for o in f.options %} {{ o.label }} {{ o.count }} {% endfor %} {% if f.hidden %}+{{ f.hidden }}{% endif %}
{% endfor %} {% if filtered %} clear {{ filtered }} filter{{ '' if filtered == 1 else 's' }} {% endif %}
{% endif %}
{# A sortable heading is a link, not a script: the order is in the URL, so a sorted view is one a seller can bookmark and send. #} {% for r in rows %} {% endfor %}
{{ sorts.name|sortlink('CARD') }} {{ sorts.set|sortlink('GAME / SET') }} {{ sorts.quantity|sortlink('QTY') }} CONDITION PRINTING {{ sorts.cost|sortlink('COST') }} {{ sorts.market|sortlink('MARKET') }} {{ sorts.margin|sortlink('MARGIN') }} {{ sorts.listed|sortlink('LISTED') }}
{# One hidden input per copy, so the server contract stays "a list of inventory ids" no matter how the UI groups them. #} {% for i in r.ids %}{% endfor %} {# The catalogue image, not a scan: with several copies behind this line, picking one of their scans to represent it is arbitrary. #} {{ r.name }} {% if r.sold %}sold {% elif r.sold_quantity %}{{ r.sold_quantity }} sold{% endif %} {{ r.game }}{% if r.set_name %} / {{ r.set_name }}{% endif %} {{ r.quantity }} {{ r.conditions }} {{ r.printing_label }} {# Ahead of the "guessed" mark: a finish with no printing behind it is the worse of the two and stays quiet on a single-printing card, which is most of the cards it happens to. #} {% if r.finish_unpriced %}! {% elif r.guessed %}?{% endif %} {{ r.cost|money if r.cost is not none else '—' }} {{ r.market|money if r.market is not none else '—' }} {{ r.margin_pct ~ '%' if r.margin_pct is not none else '—' }} {{ r.listed_label }}
{% if not rows %}
{% if show == 'printing' %}every card is priced on a printing you chose {% elif filtered and total_lines %}no card matches every filter · clear them {% elif q %}nothing matches “{{ q }}” {% else %}nothing confirmed yet · import some scans to get started{% endif %}
{% endif %}
{% endblock %} {% block scripts %} {% endblock %}