fix(ui): keep the table columns readable on a phone
The tables were squeezed to fit the screen, and with seven to nine
columns on a 390px phone 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, and the beer name was painted over the gravity
reading because the cell was allowed to overflow.
The tables keep their natural width and scroll sideways inside their
card, which is what a table does. The advice is a small button so the
column that matters is narrow, and the headings have a short form for
a phone ("Brewing advice" -> "Advice", "Attenuation" -> "Att.").
The secondary columns - the drop, the legacy ABV and the days - go away
on a phone; the details are still on the run's own page.
A name and a comment get a sensible minimum width and wrap on a word
boundary rather than one word per line.
The desktop is untouched: the same full headings, the same 806px table,
and the pop-out still opens on hover.
This commit is contained in:
+101
-83
@@ -451,7 +451,10 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }
|
||||
|
||||
/* ---------------------------------------------------------------- tables */
|
||||
|
||||
.table-wrap { overflow-x: auto; }
|
||||
.table-wrap {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
table.data {
|
||||
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 .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
|
||||
sideways inside its wrapper, which meant the columns on the right were
|
||||
off the screen and the brewer had to pan the table to see them.
|
||||
/* On a narrow screen the header wraps onto two rows: the brand and the
|
||||
sign-out on the first, the nav tabs on the second where they get the
|
||||
full width.
|
||||
|
||||
Each row becomes a card: the header cell becomes a label above its
|
||||
value, and the cells stack. */
|
||||
They used to sit on one row with the brand and the sign-out, and the
|
||||
last tab was cut off mid-word. */
|
||||
@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 {
|
||||
flex-wrap: wrap;
|
||||
row-gap: .5rem;
|
||||
@@ -537,79 +537,97 @@ table.data .comment { color: var(--text-muted); font-size: .84rem; }
|
||||
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 {
|
||||
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 */
|
||||
|
||||
@@ -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() {
|
||||
focusFirstEmpty();
|
||||
defaultDateFields();
|
||||
wireToggles();
|
||||
wireConfirms();
|
||||
wireAdvicePopouts();
|
||||
labelTableCells();
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
|
||||
@@ -99,43 +99,43 @@
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Run</th>
|
||||
<th>Name</th>
|
||||
<th class="num">OG</th>
|
||||
<th class="num">FG</th>
|
||||
<th class="num">ABV</th>
|
||||
<th class="num">Attenuation</th>
|
||||
<th class="num">Days</th>
|
||||
<th class="num">To 1.010</th>
|
||||
<th>Finished</th>
|
||||
<th class="col-run">Run</th>
|
||||
<th class="col-name">Name</th>
|
||||
<th class="num col-gravity">OG</th>
|
||||
<th class="num col-gravity">FG</th>
|
||||
<th class="num col-abv">ABV</th>
|
||||
<th class="num col-abv" title="Attenuation"><span class="long">Attenuation</span><span class="short">Att.</span></th>
|
||||
<th class="num col-abv col-days">Days</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 class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in rows %}
|
||||
<tr{% if row.suspect %} class="is-suspect"{% endif %}>
|
||||
<td class="mono">{{ row.beer_run_id }}</td>
|
||||
<td>{{ row.name }}{% if row.suspect %} <span class="chip chip--warn">not counted</span>{% endif %}</td>
|
||||
<td class="num">
|
||||
<td class="col-run mono">{{ row.beer_run_id }}</td>
|
||||
<td class="col-name">{{ row.name }}{% if row.suspect %} <span class="chip chip--warn">not counted</span>{% endif %}</td>
|
||||
<td class="col-gravity num">
|
||||
{% if row.og_points is not none %}
|
||||
{{ '%.3f' % (row.og_points / 1000) }}
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="num">
|
||||
<td class="col-gravity num">
|
||||
{% if row.fg_points is not none %}
|
||||
{{ '%.3f' % (row.fg_points / 1000) }}
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="num">
|
||||
<td class="col-abv num">
|
||||
{% if row.abv is not none %}{{ '%.2f' % row.abv }}%{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="num">
|
||||
<td class="col-abv num">
|
||||
{% if row.attenuation is not none %}
|
||||
<strong>{{ row.attenuation }}%</strong>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="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="mono nowrap">{{ row.final_date or '—' }}</td>
|
||||
<td class="col-abv col-days num">{{ row.days if row.days 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="col-finished mono nowrap">{{ row.final_date or '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -155,21 +155,21 @@
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Run</th>
|
||||
<th class="num">OG</th>
|
||||
<th class="num">FG</th>
|
||||
<th>Problem</th>
|
||||
<th class="col-run">Run</th>
|
||||
<th class="num col-gravity">OG</th>
|
||||
<th class="num col-gravity">FG</th>
|
||||
<th class="col-comment">Problem</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in suspect %}
|
||||
<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>
|
||||
</td>
|
||||
<td class="num">{{ '%.3f' % (row.og_points / 1000) }}</td>
|
||||
<td class="num"><span class="chip chip--warn">{{ '%.3f' % (row.fg_points / 1000) }}</span></td>
|
||||
<td class="comment">{{ row.problem }}</td>
|
||||
<td class="col-gravity num">{{ '%.3f' % (row.og_points / 1000) }}</td>
|
||||
<td class="col-gravity num"><span class="chip chip--warn">{{ '%.3f' % (row.fg_points / 1000) }}</span></td>
|
||||
<td class="col-comment comment">{{ row.problem }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -78,48 +78,52 @@
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Run</th>
|
||||
<th>Name</th>
|
||||
<th class="num">Gravity</th>
|
||||
<th class="num">Est. ABV</th>
|
||||
<th>Comment</th>
|
||||
<th>Brewing advice</th>
|
||||
<th></th>
|
||||
<th class="col-run">Run</th>
|
||||
<th class="col-name">Name</th>
|
||||
<th class="num col-gravity">Gravity</th>
|
||||
<th class="num col-abv" title="Estimated alcohol by volume"><span class="long">Est. ABV</span><span class="short">ABV</span></th>
|
||||
<th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
|
||||
<th class="col-advice" title="Brewing advice"><span class="long">Brewing advice</span><span class="short">Advice</span></th>
|
||||
<th class="col-action"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for run in active_runs %}
|
||||
<tr>
|
||||
<td class="mono">{{ run.beer_run_id }}</td>
|
||||
<td>{{ run.canonical_name }}</td>
|
||||
<td class="num">
|
||||
<td class="mono col-run">{{ run.beer_run_id }}</td>
|
||||
<td class="col-name">{{ run.canonical_name }}</td>
|
||||
<td class="num col-gravity">
|
||||
{% if run.min_sg is not none %}
|
||||
<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 %}
|
||||
</td>
|
||||
<td class="num">
|
||||
<td class="num col-abv">
|
||||
{% if run.projected_abv is not none %}
|
||||
{{ '%.2f' % run.projected_abv }}%
|
||||
{% else %}
|
||||
<span class="faint" title="Record another reading, or set the original gravity, to get an estimate">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="comment">
|
||||
<td class="comment col-comment">
|
||||
{% if run.comments %}
|
||||
{{ run.comments[-1] }}
|
||||
{% else %}
|
||||
<span class="faint">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="advice-cell">
|
||||
<td class="advice-cell col-advice">
|
||||
{% if run.advice %}
|
||||
<button type="button"
|
||||
class="advice-trigger{% if not run.advice_ok %} advice-trigger--failed{% endif %}"
|
||||
aria-expanded="false">
|
||||
<span class="advice-trigger__icon" aria-hidden="true">✎</span>
|
||||
<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>
|
||||
</button>
|
||||
<div class="advice-pop" role="tooltip">
|
||||
@@ -134,7 +138,7 @@
|
||||
<span class="faint">Thinking…</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="right">
|
||||
<td class="right col-action">
|
||||
<a class="btn btn--ghost btn--sm"
|
||||
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
||||
</td>
|
||||
@@ -163,41 +167,41 @@
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Run</th>
|
||||
<th>Name</th>
|
||||
<th class="num">OG</th>
|
||||
<th class="num">FG</th>
|
||||
<th class="num">ABV</th>
|
||||
<th>Finished</th>
|
||||
<th>Comment</th>
|
||||
<th></th>
|
||||
<th class="col-run">Run</th>
|
||||
<th class="col-name">Name</th>
|
||||
<th class="num col-gravity">OG</th>
|
||||
<th class="num col-gravity">FG</th>
|
||||
<th class="num col-abv">ABV</th>
|
||||
<th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
|
||||
<th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
|
||||
<th class="col-action"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for run in completed_runs %}
|
||||
<tr>
|
||||
<td class="mono">{{ run.beer_run_id }}</td>
|
||||
<td>{{ run.canonical_name }}</td>
|
||||
<td class="num">
|
||||
<td class="mono col-run">{{ run.beer_run_id }}</td>
|
||||
<td class="col-name">{{ run.canonical_name }}</td>
|
||||
<td class="num col-gravity">
|
||||
{% if run.og_points is not none %}{{ run.og_points }}{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="num">
|
||||
<td class="num col-gravity">
|
||||
{% if run.fg_points is not none %}{{ run.fg_points }}{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="num">
|
||||
<td class="num col-abv">
|
||||
{% if run.abv is not none %}
|
||||
<strong>{{ '%.2f' % run.abv }}%</strong>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="mono nowrap">{{ run.final_date or '—' }}</td>
|
||||
<td class="comment">
|
||||
<td class="mono nowrap col-finished">{{ run.final_date or '—' }}</td>
|
||||
<td class="comment col-comment">
|
||||
{% if run.comments %}
|
||||
{{ run.comments[-1] }}
|
||||
{% else %}
|
||||
<span class="faint">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="right">
|
||||
<td class="right col-action">
|
||||
<a class="btn btn--ghost btn--sm"
|
||||
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
||||
</td>
|
||||
|
||||
@@ -84,21 +84,21 @@
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Run</th>
|
||||
<th>Name</th>
|
||||
<th>Date</th>
|
||||
<th class="num">Reading</th>
|
||||
<th>Comment</th>
|
||||
<th class="col-run">Run</th>
|
||||
<th class="col-name">Name</th>
|
||||
<th class="col-finished">Date</th>
|
||||
<th class="num col-gravity">Reading</th>
|
||||
<th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in outliers %}
|
||||
<tr>
|
||||
<td class="mono">{{ row.beer_run_id }}</td>
|
||||
<td>{{ row.name }}</td>
|
||||
<td class="mono nowrap">{{ row.date or '—' }}</td>
|
||||
<td class="num"><span class="chip chip--warn">{{ row.sg }}</span></td>
|
||||
<td class="comment">{{ row.comment or '—' }}</td>
|
||||
<td class="col-run mono">{{ row.beer_run_id }}</td>
|
||||
<td class="col-name">{{ row.name }}</td>
|
||||
<td class="col-finished mono nowrap">{{ row.date or '—' }}</td>
|
||||
<td class="col-gravity num"><span class="chip chip--warn">{{ row.sg }}</span></td>
|
||||
<td class="col-comment comment">{{ row.comment or '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -118,45 +118,45 @@
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Run</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th class="num">OG</th>
|
||||
<th class="num">FG</th>
|
||||
<th class="num">Drop</th>
|
||||
<th class="num">ABV</th>
|
||||
<th class="num">Legacy</th>
|
||||
<th>Finished</th>
|
||||
<th>Comment</th>
|
||||
<th></th>
|
||||
<th class="col-run">Run</th>
|
||||
<th class="col-name">Name</th>
|
||||
<th class="col-finished" title="Status"><span class="long">Status</span><span class="short">Stat</span></th>
|
||||
<th class="num col-gravity">OG</th>
|
||||
<th class="num col-gravity">FG</th>
|
||||
<th class="num col-drop">Drop</th>
|
||||
<th class="num col-abv">ABV</th>
|
||||
<th class="num col-legacy">Legacy</th>
|
||||
<th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
|
||||
<th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
|
||||
<th class="col-action"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for run in summaries %}
|
||||
<tr>
|
||||
<td class="mono">{{ run.beer_run_id }}</td>
|
||||
<td>{{ run.canonical_name }}</td>
|
||||
<td>
|
||||
<td class="col-run mono">{{ run.beer_run_id }}</td>
|
||||
<td class="col-name">{{ run.canonical_name }}</td>
|
||||
<td class="col-finished">
|
||||
{% if run.active %}
|
||||
<span class="chip chip--active">Active</span>
|
||||
{% else %}
|
||||
<span class="chip chip--done">Finished</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="num">{{ run.og_points or '—' }}</td>
|
||||
<td class="num">{{ run.fg_points or '—' }}</td>
|
||||
<td class="num">{{ run.drop_points if run.drop_points is not none else '—' }}</td>
|
||||
<td class="num">
|
||||
<td class="col-drop col-gravity num">{{ run.og_points or '—' }}</td>
|
||||
<td class="col-gravity num">{{ run.fg_points or '—' }}</td>
|
||||
<td class="col-gravity num">{{ run.drop_points if run.drop_points is not none else '—' }}</td>
|
||||
<td class="col-abv num">
|
||||
{% if run.abv is not none %}<strong>{{ '%.2f' % run.abv }}%</strong>
|
||||
{% elif run.projected_abv is not none %}{{ '%.2f' % run.projected_abv }}%*
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="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="comment">
|
||||
<td class="col-legacy col-abv num muted">{{ run.legacy_abv if run.legacy_abv is not none else '—' }}</td>
|
||||
<td class="col-finished mono nowrap">{{ run.final_date or '—' }}</td>
|
||||
<td class="col-comment comment">
|
||||
{% if run.comments %}{{ run.comments[-1] }}{% else %}<span class="faint">—</span>{% endif %}
|
||||
</td>
|
||||
<td class="right">
|
||||
<td class="right col-action">
|
||||
<a class="btn btn--ghost btn--sm"
|
||||
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
||||
</td>
|
||||
@@ -178,29 +178,29 @@
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Run</th>
|
||||
<th>Name</th>
|
||||
<th class="num">Standard ABV</th>
|
||||
<th class="num">Legacy ABV</th>
|
||||
<th class="num">Overstated by</th>
|
||||
<th>Finished</th>
|
||||
<th class="col-run">Run</th>
|
||||
<th class="col-name">Name</th>
|
||||
<th class="num col-abv" title="Standard ABV"><span class="long">Standard ABV</span><span class="short">Std</span></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 col-abv" title="Overstated by"><span class="long">Overstated by</span><span class="short">Over</span></th>
|
||||
<th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in abv_rows %}
|
||||
<tr>
|
||||
<td class="mono">{{ row.beer_run_id }}</td>
|
||||
<td>{{ row.name }}</td>
|
||||
<td class="num">{{ '%.2f' % row.abv }}%</td>
|
||||
<td class="num muted">{{ '%.2f' % row.legacy_abv }}%</td>
|
||||
<td class="num">
|
||||
<td class="col-run mono">{{ row.beer_run_id }}</td>
|
||||
<td class="col-name">{{ row.name }}</td>
|
||||
<td class="col-abv num">{{ '%.2f' % row.abv }}%</td>
|
||||
<td class="col-abv num muted">{{ '%.2f' % row.legacy_abv }}%</td>
|
||||
<td class="col-abv num">
|
||||
{% if row.delta > 0 %}
|
||||
<span class="chip chip--warn">+{{ '%.2f' % row.delta }}</span>
|
||||
{% else %}
|
||||
<span class="faint">{{ '%.2f' % row.delta }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="mono nowrap">{{ row.final_date or '—' }}</td>
|
||||
<td class="col-finished mono nowrap">{{ row.final_date or '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</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
|
||||
|
||||
|
||||
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):
|
||||
"""A generation failure reads differently from real advice."""
|
||||
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
|
||||
|
||||
Reference in New Issue
Block a user