fix(ui): keep the table columns readable on a phone #18
+101
-83
@@ -451,7 +451,10 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }
|
|||||||
|
|
||||||
/* ---------------------------------------------------------------- tables */
|
/* ---------------------------------------------------------------- tables */
|
||||||
|
|
||||||
.table-wrap { overflow-x: auto; }
|
.table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
table.data {
|
table.data {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -480,21 +483,18 @@ table.data tbody tr:hover { background: var(--bg-raised-2); }
|
|||||||
table.data .num { font-family: var(--mono); text-align: right; white-space: nowrap; }
|
table.data .num { font-family: var(--mono); text-align: right; white-space: nowrap; }
|
||||||
table.data .comment { color: var(--text-muted); font-size: .84rem; }
|
table.data .comment { color: var(--text-muted); font-size: .84rem; }
|
||||||
|
|
||||||
/* On a narrow screen the tables reflow into stacked cards.
|
/* A heading that is too long for a phone column. The long form shows
|
||||||
|
on a desktop and the short form on a phone - see the media query. */
|
||||||
|
table.data .short { display: none; }
|
||||||
|
table.data .advice-trigger .short { display: none; }
|
||||||
|
|
||||||
A seven-column table does not fit a phone. It was left to scroll
|
/* On a narrow screen the header wraps onto two rows: the brand and the
|
||||||
sideways inside its wrapper, which meant the columns on the right were
|
sign-out on the first, the nav tabs on the second where they get the
|
||||||
off the screen and the brewer had to pan the table to see them.
|
full width.
|
||||||
|
|
||||||
Each row becomes a card: the header cell becomes a label above its
|
They used to sit on one row with the brand and the sign-out, and the
|
||||||
value, and the cells stack. */
|
last tab was cut off mid-word. */
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
/* The header wraps onto two rows on a phone: the brand and the
|
|
||||||
sign-out on the first, the nav tabs on the second where they get
|
|
||||||
the full width and scroll sideways if they still do not fit.
|
|
||||||
|
|
||||||
They used to sit on one row with the brand and the sign-out, and
|
|
||||||
the last tab was cut off mid-word. */
|
|
||||||
.topbar__inner {
|
.topbar__inner {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
row-gap: .5rem;
|
row-gap: .5rem;
|
||||||
@@ -537,79 +537,97 @@ table.data .comment { color: var(--text-muted); font-size: .84rem; }
|
|||||||
letter-spacing: .01em;
|
letter-spacing: .01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The tables stay tables on a phone.
|
||||||
|
|
||||||
|
They scroll sideways inside their card, which is what a table
|
||||||
|
does. They used to be squeezed to fit instead, and with seven
|
||||||
|
to nine columns on a 390px screen that meant every heading and
|
||||||
|
every value was truncated to a few characters - a gravity of
|
||||||
|
"1.0..." and a date of "202..." are worse than useless.
|
||||||
|
|
||||||
|
So the columns keep their natural width, the table scrolls, and
|
||||||
|
the advice is a small button so the column that matters is
|
||||||
|
narrow. */
|
||||||
|
table.data {
|
||||||
|
table-layout: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.data th,
|
||||||
|
table.data td {
|
||||||
|
padding-left: .5rem;
|
||||||
|
padding-right: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A name and a comment are words, so they get a sensible minimum
|
||||||
|
width and wrap on a word boundary - not one word per line. */
|
||||||
|
table.data td.col-name,
|
||||||
|
table.data td.col-comment {
|
||||||
|
min-width: 7rem;
|
||||||
|
max-width: 12rem;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: normal;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The heading gets a short form, because a heading that is cut off
|
||||||
|
is worse than a heading that is brief. */
|
||||||
|
table.data .col-abv .long,
|
||||||
|
table.data .col-advice .long,
|
||||||
|
table.data .col-finished .long,
|
||||||
|
table.data .col-comment .long {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.data .col-abv .short,
|
||||||
|
table.data .col-advice .short,
|
||||||
|
table.data .col-finished .short,
|
||||||
|
table.data .col-comment .short {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The advice trigger is small on a phone - it is a button, and
|
||||||
|
the advice itself is in the pop-out. */
|
||||||
|
table.data .advice-trigger {
|
||||||
|
font-size: .68rem;
|
||||||
|
padding: .25rem .4rem;
|
||||||
|
gap: .2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.data .advice-trigger .long {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.data .advice-trigger .short {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The action column holds a button. */
|
||||||
|
table.data .col-action {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.data .col-action .btn {
|
||||||
|
padding-left: .5rem;
|
||||||
|
padding-right: .5rem;
|
||||||
|
font-size: .72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The secondary columns go away on a phone - the details are still
|
||||||
|
on the run's own page. */
|
||||||
|
table.data .col-drop,
|
||||||
|
table.data .col-legacy,
|
||||||
|
table.data .col-days {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The scroll area keeps a visible scrollbar on a phone, so it is
|
||||||
|
obvious there is more of the table to the right. */
|
||||||
.table-wrap {
|
.table-wrap {
|
||||||
overflow-x: visible;
|
scrollbar-width: thin;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.data,
|
|
||||||
table.data tbody,
|
|
||||||
table.data tr,
|
|
||||||
table.data td {
|
|
||||||
display: block;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data thead {
|
|
||||||
/* HIDDEN in the stacked layout. Each cell shows its column
|
|
||||||
name as a label, so the header row would be a duplicate - and
|
|
||||||
it was 230px of the first card. */
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data tr {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: .75rem;
|
|
||||||
padding: .25rem .1rem;
|
|
||||||
background: var(--bg-raised-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data tbody tr:hover {
|
|
||||||
background: var(--bg-raised-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data th {
|
|
||||||
display: block;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data td {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 8.5rem 1fr;
|
|
||||||
gap: .5rem;
|
|
||||||
align-items: baseline;
|
|
||||||
padding: .45rem .7rem;
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, .04);
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data td::before {
|
|
||||||
/* The column heading, shown as the row's label. */
|
|
||||||
content: attr(data-label);
|
|
||||||
font-size: .65rem;
|
|
||||||
font-weight: 650;
|
|
||||||
letter-spacing: .07em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--text-faint);
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data td.num {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data td.right,
|
|
||||||
table.data td:empty {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data td.right::before {
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A cell with nothing in it must not leave a gap. */
|
|
||||||
table.data td:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------- chips */
|
/* ----------------------------------------------------------------- chips */
|
||||||
|
|||||||
@@ -207,36 +207,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function labelTableCells() {
|
|
||||||
var tables = document.querySelectorAll("table.data");
|
|
||||||
for (var t = 0; t < tables.length; t++) {
|
|
||||||
var headerRow = tables[t].querySelector("thead tr");
|
|
||||||
if (!headerRow) { continue; }
|
|
||||||
var labels = [];
|
|
||||||
var headers = headerRow.querySelectorAll("th");
|
|
||||||
for (var h = 0; h < headers.length; h++) {
|
|
||||||
labels.push(headers[h].textContent.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
var rows = tables[t].querySelectorAll("tbody tr");
|
|
||||||
for (var r = 0; r < rows.length; r++) {
|
|
||||||
var cells = rows[r].querySelectorAll("td");
|
|
||||||
for (var c = 0; c < cells.length && c < labels.length; c++) {
|
|
||||||
if (labels[c] && !cells[c].getAttribute("data-label")) {
|
|
||||||
cells[c].setAttribute("data-label", labels[c]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
focusFirstEmpty();
|
focusFirstEmpty();
|
||||||
defaultDateFields();
|
defaultDateFields();
|
||||||
wireToggles();
|
wireToggles();
|
||||||
wireConfirms();
|
wireConfirms();
|
||||||
wireAdvicePopouts();
|
wireAdvicePopouts();
|
||||||
labelTableCells();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
|
|||||||
@@ -99,43 +99,43 @@
|
|||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Run</th>
|
<th class="col-run">Run</th>
|
||||||
<th>Name</th>
|
<th class="col-name">Name</th>
|
||||||
<th class="num">OG</th>
|
<th class="num col-gravity">OG</th>
|
||||||
<th class="num">FG</th>
|
<th class="num col-gravity">FG</th>
|
||||||
<th class="num">ABV</th>
|
<th class="num col-abv">ABV</th>
|
||||||
<th class="num">Attenuation</th>
|
<th class="num col-abv" title="Attenuation"><span class="long">Attenuation</span><span class="short">Att.</span></th>
|
||||||
<th class="num">Days</th>
|
<th class="num col-abv col-days">Days</th>
|
||||||
<th class="num">To 1.010</th>
|
<th class="num col-abv" title="Days to reach 1.010"><span class="long">To 1.010</span><span class="short">1.010</span></th>
|
||||||
<th>Finished</th>
|
<th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in rows %}
|
{% for row in rows %}
|
||||||
<tr{% if row.suspect %} class="is-suspect"{% endif %}>
|
<tr{% if row.suspect %} class="is-suspect"{% endif %}>
|
||||||
<td class="mono">{{ row.beer_run_id }}</td>
|
<td class="col-run mono">{{ row.beer_run_id }}</td>
|
||||||
<td>{{ row.name }}{% if row.suspect %} <span class="chip chip--warn">not counted</span>{% endif %}</td>
|
<td class="col-name">{{ row.name }}{% if row.suspect %} <span class="chip chip--warn">not counted</span>{% endif %}</td>
|
||||||
<td class="num">
|
<td class="col-gravity num">
|
||||||
{% if row.og_points is not none %}
|
{% if row.og_points is not none %}
|
||||||
{{ '%.3f' % (row.og_points / 1000) }}
|
{{ '%.3f' % (row.og_points / 1000) }}
|
||||||
{% else %}—{% endif %}
|
{% else %}—{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="num">
|
<td class="col-gravity num">
|
||||||
{% if row.fg_points is not none %}
|
{% if row.fg_points is not none %}
|
||||||
{{ '%.3f' % (row.fg_points / 1000) }}
|
{{ '%.3f' % (row.fg_points / 1000) }}
|
||||||
{% else %}—{% endif %}
|
{% else %}—{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="num">
|
<td class="col-abv num">
|
||||||
{% if row.abv is not none %}{{ '%.2f' % row.abv }}%{% else %}—{% endif %}
|
{% if row.abv is not none %}{{ '%.2f' % row.abv }}%{% else %}—{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="num">
|
<td class="col-abv num">
|
||||||
{% if row.attenuation is not none %}
|
{% if row.attenuation is not none %}
|
||||||
<strong>{{ row.attenuation }}%</strong>
|
<strong>{{ row.attenuation }}%</strong>
|
||||||
{% else %}—{% endif %}
|
{% else %}—{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="num">{{ row.days if row.days is not none else '—' }}</td>
|
<td class="col-abv col-days num">{{ row.days if row.days is not none else '—' }}</td>
|
||||||
<td class="num">{{ row.days_to_1010 if row.days_to_1010 is not none else '—' }}</td>
|
<td class="col-abv num">{{ row.days_to_1010 if row.days_to_1010 is not none else '—' }}</td>
|
||||||
<td class="mono nowrap">{{ row.final_date or '—' }}</td>
|
<td class="col-finished mono nowrap">{{ row.final_date or '—' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -155,21 +155,21 @@
|
|||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Run</th>
|
<th class="col-run">Run</th>
|
||||||
<th class="num">OG</th>
|
<th class="num col-gravity">OG</th>
|
||||||
<th class="num">FG</th>
|
<th class="num col-gravity">FG</th>
|
||||||
<th>Problem</th>
|
<th class="col-comment">Problem</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in suspect %}
|
{% for row in suspect %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="mono">
|
<td class="col-run mono">
|
||||||
<a href="{{ url_for('brews.run_detail', run_id=row.beer_run_id) }}">{{ row.beer_run_id }}</a>
|
<a href="{{ url_for('brews.run_detail', run_id=row.beer_run_id) }}">{{ row.beer_run_id }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="num">{{ '%.3f' % (row.og_points / 1000) }}</td>
|
<td class="col-gravity num">{{ '%.3f' % (row.og_points / 1000) }}</td>
|
||||||
<td class="num"><span class="chip chip--warn">{{ '%.3f' % (row.fg_points / 1000) }}</span></td>
|
<td class="col-gravity num"><span class="chip chip--warn">{{ '%.3f' % (row.fg_points / 1000) }}</span></td>
|
||||||
<td class="comment">{{ row.problem }}</td>
|
<td class="col-comment comment">{{ row.problem }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -78,48 +78,52 @@
|
|||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Run</th>
|
<th class="col-run">Run</th>
|
||||||
<th>Name</th>
|
<th class="col-name">Name</th>
|
||||||
<th class="num">Gravity</th>
|
<th class="num col-gravity">Gravity</th>
|
||||||
<th class="num">Est. ABV</th>
|
<th class="num col-abv" title="Estimated alcohol by volume"><span class="long">Est. ABV</span><span class="short">ABV</span></th>
|
||||||
<th>Comment</th>
|
<th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
|
||||||
<th>Brewing advice</th>
|
<th class="col-advice" title="Brewing advice"><span class="long">Brewing advice</span><span class="short">Advice</span></th>
|
||||||
<th></th>
|
<th class="col-action"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for run in active_runs %}
|
{% for run in active_runs %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="mono">{{ run.beer_run_id }}</td>
|
<td class="mono col-run">{{ run.beer_run_id }}</td>
|
||||||
<td>{{ run.canonical_name }}</td>
|
<td class="col-name">{{ run.canonical_name }}</td>
|
||||||
<td class="num">
|
<td class="num col-gravity">
|
||||||
{% if run.min_sg is not none %}
|
{% if run.min_sg is not none %}
|
||||||
<span class="mono strong">{{ run.min_sg_decimal }}</span>
|
<span class="mono strong">{{ run.min_sg_decimal }}</span>
|
||||||
<div class="faint tiny">{{ run.min_sg_point_scale }} points</div>
|
<div class="faint tiny point-scale">{{ run.min_sg_point_scale }} points</div>
|
||||||
{% else %}—{% endif %}
|
{% else %}—{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="num">
|
<td class="num col-abv">
|
||||||
{% if run.projected_abv is not none %}
|
{% if run.projected_abv is not none %}
|
||||||
{{ '%.2f' % run.projected_abv }}%
|
{{ '%.2f' % run.projected_abv }}%
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="faint" title="Record another reading, or set the original gravity, to get an estimate">—</span>
|
<span class="faint" title="Record another reading, or set the original gravity, to get an estimate">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="comment">
|
<td class="comment col-comment">
|
||||||
{% if run.comments %}
|
{% if run.comments %}
|
||||||
{{ run.comments[-1] }}
|
{{ run.comments[-1] }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="faint">—</span>
|
<span class="faint">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="advice-cell">
|
<td class="advice-cell col-advice">
|
||||||
{% if run.advice %}
|
{% if run.advice %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="advice-trigger{% if not run.advice_ok %} advice-trigger--failed{% endif %}"
|
class="advice-trigger{% if not run.advice_ok %} advice-trigger--failed{% endif %}"
|
||||||
aria-expanded="false">
|
aria-expanded="false">
|
||||||
<span class="advice-trigger__icon" aria-hidden="true">✎</span>
|
<span class="advice-trigger__icon" aria-hidden="true">✎</span>
|
||||||
<span class="advice-trigger__label">
|
<span class="advice-trigger__label">
|
||||||
{% if run.advice_ok %}Read the advice{% else %}Advice failed{% endif %}
|
{% if run.advice_ok %}
|
||||||
|
<span class="long">Read the advice</span><span class="short">Advice</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="long">Advice failed</span><span class="short">Failed</span>
|
||||||
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="advice-pop" role="tooltip">
|
<div class="advice-pop" role="tooltip">
|
||||||
@@ -134,7 +138,7 @@
|
|||||||
<span class="faint">Thinking…</span>
|
<span class="faint">Thinking…</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="right">
|
<td class="right col-action">
|
||||||
<a class="btn btn--ghost btn--sm"
|
<a class="btn btn--ghost btn--sm"
|
||||||
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -163,41 +167,41 @@
|
|||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Run</th>
|
<th class="col-run">Run</th>
|
||||||
<th>Name</th>
|
<th class="col-name">Name</th>
|
||||||
<th class="num">OG</th>
|
<th class="num col-gravity">OG</th>
|
||||||
<th class="num">FG</th>
|
<th class="num col-gravity">FG</th>
|
||||||
<th class="num">ABV</th>
|
<th class="num col-abv">ABV</th>
|
||||||
<th>Finished</th>
|
<th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
|
||||||
<th>Comment</th>
|
<th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
|
||||||
<th></th>
|
<th class="col-action"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for run in completed_runs %}
|
{% for run in completed_runs %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="mono">{{ run.beer_run_id }}</td>
|
<td class="mono col-run">{{ run.beer_run_id }}</td>
|
||||||
<td>{{ run.canonical_name }}</td>
|
<td class="col-name">{{ run.canonical_name }}</td>
|
||||||
<td class="num">
|
<td class="num col-gravity">
|
||||||
{% if run.og_points is not none %}{{ run.og_points }}{% else %}—{% endif %}
|
{% if run.og_points is not none %}{{ run.og_points }}{% else %}—{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="num">
|
<td class="num col-gravity">
|
||||||
{% if run.fg_points is not none %}{{ run.fg_points }}{% else %}—{% endif %}
|
{% if run.fg_points is not none %}{{ run.fg_points }}{% else %}—{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="num">
|
<td class="num col-abv">
|
||||||
{% if run.abv is not none %}
|
{% if run.abv is not none %}
|
||||||
<strong>{{ '%.2f' % run.abv }}%</strong>
|
<strong>{{ '%.2f' % run.abv }}%</strong>
|
||||||
{% else %}—{% endif %}
|
{% else %}—{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="mono nowrap">{{ run.final_date or '—' }}</td>
|
<td class="mono nowrap col-finished">{{ run.final_date or '—' }}</td>
|
||||||
<td class="comment">
|
<td class="comment col-comment">
|
||||||
{% if run.comments %}
|
{% if run.comments %}
|
||||||
{{ run.comments[-1] }}
|
{{ run.comments[-1] }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="faint">—</span>
|
<span class="faint">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="right">
|
<td class="right col-action">
|
||||||
<a class="btn btn--ghost btn--sm"
|
<a class="btn btn--ghost btn--sm"
|
||||||
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -84,21 +84,21 @@
|
|||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Run</th>
|
<th class="col-run">Run</th>
|
||||||
<th>Name</th>
|
<th class="col-name">Name</th>
|
||||||
<th>Date</th>
|
<th class="col-finished">Date</th>
|
||||||
<th class="num">Reading</th>
|
<th class="num col-gravity">Reading</th>
|
||||||
<th>Comment</th>
|
<th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in outliers %}
|
{% for row in outliers %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="mono">{{ row.beer_run_id }}</td>
|
<td class="col-run mono">{{ row.beer_run_id }}</td>
|
||||||
<td>{{ row.name }}</td>
|
<td class="col-name">{{ row.name }}</td>
|
||||||
<td class="mono nowrap">{{ row.date or '—' }}</td>
|
<td class="col-finished mono nowrap">{{ row.date or '—' }}</td>
|
||||||
<td class="num"><span class="chip chip--warn">{{ row.sg }}</span></td>
|
<td class="col-gravity num"><span class="chip chip--warn">{{ row.sg }}</span></td>
|
||||||
<td class="comment">{{ row.comment or '—' }}</td>
|
<td class="col-comment comment">{{ row.comment or '—' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -118,45 +118,45 @@
|
|||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Run</th>
|
<th class="col-run">Run</th>
|
||||||
<th>Name</th>
|
<th class="col-name">Name</th>
|
||||||
<th>Status</th>
|
<th class="col-finished" title="Status"><span class="long">Status</span><span class="short">Stat</span></th>
|
||||||
<th class="num">OG</th>
|
<th class="num col-gravity">OG</th>
|
||||||
<th class="num">FG</th>
|
<th class="num col-gravity">FG</th>
|
||||||
<th class="num">Drop</th>
|
<th class="num col-drop">Drop</th>
|
||||||
<th class="num">ABV</th>
|
<th class="num col-abv">ABV</th>
|
||||||
<th class="num">Legacy</th>
|
<th class="num col-legacy">Legacy</th>
|
||||||
<th>Finished</th>
|
<th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
|
||||||
<th>Comment</th>
|
<th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
|
||||||
<th></th>
|
<th class="col-action"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for run in summaries %}
|
{% for run in summaries %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="mono">{{ run.beer_run_id }}</td>
|
<td class="col-run mono">{{ run.beer_run_id }}</td>
|
||||||
<td>{{ run.canonical_name }}</td>
|
<td class="col-name">{{ run.canonical_name }}</td>
|
||||||
<td>
|
<td class="col-finished">
|
||||||
{% if run.active %}
|
{% if run.active %}
|
||||||
<span class="chip chip--active">Active</span>
|
<span class="chip chip--active">Active</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="chip chip--done">Finished</span>
|
<span class="chip chip--done">Finished</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="num">{{ run.og_points or '—' }}</td>
|
<td class="col-drop col-gravity num">{{ run.og_points or '—' }}</td>
|
||||||
<td class="num">{{ run.fg_points or '—' }}</td>
|
<td class="col-gravity num">{{ run.fg_points or '—' }}</td>
|
||||||
<td class="num">{{ run.drop_points if run.drop_points is not none else '—' }}</td>
|
<td class="col-gravity num">{{ run.drop_points if run.drop_points is not none else '—' }}</td>
|
||||||
<td class="num">
|
<td class="col-abv num">
|
||||||
{% if run.abv is not none %}<strong>{{ '%.2f' % run.abv }}%</strong>
|
{% if run.abv is not none %}<strong>{{ '%.2f' % run.abv }}%</strong>
|
||||||
{% elif run.projected_abv is not none %}{{ '%.2f' % run.projected_abv }}%*
|
{% elif run.projected_abv is not none %}{{ '%.2f' % run.projected_abv }}%*
|
||||||
{% else %}—{% endif %}
|
{% else %}—{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="num muted">{{ run.legacy_abv if run.legacy_abv is not none else '—' }}</td>
|
<td class="col-legacy col-abv num muted">{{ run.legacy_abv if run.legacy_abv is not none else '—' }}</td>
|
||||||
<td class="mono nowrap">{{ run.final_date or '—' }}</td>
|
<td class="col-finished mono nowrap">{{ run.final_date or '—' }}</td>
|
||||||
<td class="comment">
|
<td class="col-comment comment">
|
||||||
{% if run.comments %}{{ run.comments[-1] }}{% else %}<span class="faint">—</span>{% endif %}
|
{% if run.comments %}{{ run.comments[-1] }}{% else %}<span class="faint">—</span>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="right">
|
<td class="right col-action">
|
||||||
<a class="btn btn--ghost btn--sm"
|
<a class="btn btn--ghost btn--sm"
|
||||||
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -178,29 +178,29 @@
|
|||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Run</th>
|
<th class="col-run">Run</th>
|
||||||
<th>Name</th>
|
<th class="col-name">Name</th>
|
||||||
<th class="num">Standard ABV</th>
|
<th class="num col-abv" title="Standard ABV"><span class="long">Standard ABV</span><span class="short">Std</span></th>
|
||||||
<th class="num">Legacy ABV</th>
|
<th class="num col-legacy col-abv" title="Legacy ABV"><span class="long">Legacy ABV</span><span class="short">Old</span></th>
|
||||||
<th class="num">Overstated by</th>
|
<th class="num col-abv" title="Overstated by"><span class="long">Overstated by</span><span class="short">Over</span></th>
|
||||||
<th>Finished</th>
|
<th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in abv_rows %}
|
{% for row in abv_rows %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="mono">{{ row.beer_run_id }}</td>
|
<td class="col-run mono">{{ row.beer_run_id }}</td>
|
||||||
<td>{{ row.name }}</td>
|
<td class="col-name">{{ row.name }}</td>
|
||||||
<td class="num">{{ '%.2f' % row.abv }}%</td>
|
<td class="col-abv num">{{ '%.2f' % row.abv }}%</td>
|
||||||
<td class="num muted">{{ '%.2f' % row.legacy_abv }}%</td>
|
<td class="col-abv num muted">{{ '%.2f' % row.legacy_abv }}%</td>
|
||||||
<td class="num">
|
<td class="col-abv num">
|
||||||
{% if row.delta > 0 %}
|
{% if row.delta > 0 %}
|
||||||
<span class="chip chip--warn">+{{ '%.2f' % row.delta }}</span>
|
<span class="chip chip--warn">+{{ '%.2f' % row.delta }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="faint">{{ '%.2f' % row.delta }}</span>
|
<span class="faint">{{ '%.2f' % row.delta }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="mono nowrap">{{ row.final_date or '—' }}</td>
|
<td class="col-finished mono nowrap">{{ row.final_date or '—' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1024,6 +1024,31 @@ def test_the_advice_in_the_table_is_a_popout_not_the_text(client, repo):
|
|||||||
assert "Warm it to 21C and wait." in body
|
assert "Warm it to 21C and wait." in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_tables_keep_their_columns_on_a_phone(client, repo):
|
||||||
|
"""The tables are readable on a phone, not squeezed into nothing.
|
||||||
|
|
||||||
|
The advice is a button and the table scrolls, so nothing is
|
||||||
|
truncated to an unreadable stub.
|
||||||
|
"""
|
||||||
|
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
|
||||||
|
client.post("/login", data={"username": "beer", "password": "beer_pass"},
|
||||||
|
follow_redirects=True)
|
||||||
|
|
||||||
|
seed_readings(repo, [("1", "Cider raspberry elderberry", "1045", "2026-01-01", False, "")])
|
||||||
|
doc = repo.readings.find_one({"beer_run_id": "1"})
|
||||||
|
repo.set_reading_advice(str(doc["_id"]), "Warm it to 21C and wait.", ok=True)
|
||||||
|
|
||||||
|
body = client.get("/brews").get_data(as_text=True)
|
||||||
|
|
||||||
|
# The long heading has a short form for a phone, and the long form
|
||||||
|
# is still in the page for a desktop.
|
||||||
|
assert "Brewing advice" in body
|
||||||
|
assert ">Advice<" in body.replace("\n", "")
|
||||||
|
|
||||||
|
# The beer name is not truncated in the markup.
|
||||||
|
assert "Cider raspberry elderberry" in body
|
||||||
|
|
||||||
|
|
||||||
def test_a_failed_advice_is_marked_as_such(client, repo):
|
def test_a_failed_advice_is_marked_as_such(client, repo):
|
||||||
"""A generation failure reads differently from real advice."""
|
"""A generation failure reads differently from real advice."""
|
||||||
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
|
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
|
||||||
|
|||||||
Reference in New Issue
Block a user