Five UI and data features for return journeys and results page
- Replace native date inputs with always-open custom calendar; return journeys show two months side-by-side with Airbnb-style range selection - Add min-connection filter (30/40/50/60 min) for the inbound leg of return journeys, separate from the outbound connection filter - Fix total journey time: naive datetime subtraction across CET/BST was 1 h too long outbound and 1 h too short inbound - Filter inbound circle line suggestions when connection ≥ 40 min: only show services arriving ≥ 5 min before GWR departure at Paddington - Add Std / SP labels to Eurostar fare lines so users can distinguish Standard from Standard Premier - Row selection with a fixed summary bar showing NR + Eurostar + circle totals; selection is preserved in the URL - Load walk-on fares sequentially, outbound section first - Mobile: card-grid table layout, hide headcode/platform on small screens Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a88b19fa4c
commit
1bc7631863
5 changed files with 695 additions and 122 deletions
|
|
@ -79,6 +79,16 @@
|
|||
{% for section in sections %}
|
||||
<div class="filter-row" style="margin-top:0.5rem">
|
||||
<span class="filter-label" style="min-width:5.5rem">{{ 'Outbound' if section.direction == 'outbound' else 'Return' }}:</span>
|
||||
{% if section.direction == 'inbound' %}
|
||||
<div>
|
||||
<label for="min_conn_in_select" class="filter-label">Min connection:</label>
|
||||
<select id="min_conn_in_select" onchange="applyConnectionFilter()" class="select-inline">
|
||||
{% for mins in valid_inbound_return_min_connections %}
|
||||
<option value="{{ mins }}" {% if mins == inbound_min_connection %}selected{% endif %}>{{ mins }} min</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<span class="filter-label">NR:</span>
|
||||
<div class="btn-group">
|
||||
|
|
@ -129,6 +139,7 @@
|
|||
const RESULTS_BASE = '{{ results_base_url }}';
|
||||
const DEFAULT_MIN_CONN = {{ default_min_connection }};
|
||||
const DEFAULT_MAX_CONN = {{ default_max_connection }};
|
||||
const DEFAULT_MIN_CONN_IN = {{ default_inbound_min_connection }};
|
||||
let TRIP_FARES = {{ trip_fares_json | safe }};
|
||||
let ADVANCE_FARES = {{ advance_fares_json | safe }};
|
||||
let WALKON_API_URLS = {{ walkon_api_urls_json | safe }};
|
||||
|
|
@ -141,6 +152,8 @@
|
|||
let currentEsClasses = {{ es_classes_json | safe }};
|
||||
const SECTION_DIRECTIONS = {{ section_directions_json | safe }};
|
||||
let advanceLoadingSections = {};
|
||||
let walkonLoadingSections = {};
|
||||
let selectedRowKeys = {};
|
||||
|
||||
function updateAdvanceLoadingStatus() {
|
||||
var loading = Object.keys(advanceLoadingSections).some(function(sectionId) {
|
||||
|
|
@ -156,6 +169,11 @@
|
|||
var params = [];
|
||||
if (min !== DEFAULT_MIN_CONN) params.push('min_connection=' + min);
|
||||
if (max !== DEFAULT_MAX_CONN) params.push('max_connection=' + max);
|
||||
var minInEl = document.getElementById('min_conn_in_select');
|
||||
if (minInEl) {
|
||||
var minIn = parseInt(minInEl.value);
|
||||
if (minIn !== DEFAULT_MIN_CONN_IN) params.push('min_connection_in=' + minIn);
|
||||
}
|
||||
var sectionIds = Object.keys(currentNrClasses);
|
||||
if (sectionIds.length === 1) {
|
||||
var nrCls = currentNrClasses[sectionIds[0]];
|
||||
|
|
@ -172,6 +190,12 @@
|
|||
if (esC !== 'standard') params.push('es_class' + suffix + '=' + esC);
|
||||
});
|
||||
}
|
||||
for (var _sid in selectedRowKeys) {
|
||||
var _sel = selectedRowKeys[_sid];
|
||||
if (!_sel || !SECTION_DIRECTIONS[_sid]) continue;
|
||||
var _pname = SECTION_DIRECTIONS[_sid] === 'outbound' ? 'out' : 'ret';
|
||||
params.push(_pname + '=' + encodeURIComponent(_sel.slice(_sid.length + 1)));
|
||||
}
|
||||
return params.length ? RESULTS_BASE + '?' + params.join('&') : RESULTS_BASE;
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +229,7 @@
|
|||
function fareHtml(fare) {
|
||||
return '<span class="text-sm font-bold">' + fmtPrice(fare.price) + '</span>'
|
||||
+ (fare.ticket ? ' <span class="text-xs text-muted">' + fare.ticket + '</span>' : '')
|
||||
+ (fare.seats != null ? ' <span class="text-xs text-muted">' + fare.seats + ' at this price</span>' : '');
|
||||
+ (fare.seats != null ? ' <span class="text-xs text-muted fare-seats">' + fare.seats + ' at this price</span>' : '');
|
||||
}
|
||||
|
||||
function mergeAdvanceFares(sectionId, fares) {
|
||||
|
|
@ -376,17 +400,19 @@
|
|||
var esStdEl = tr.querySelector('.es-standard');
|
||||
var esPlusEl = tr.querySelector('.es-plus');
|
||||
if (esStdEl) {
|
||||
esStdEl.innerHTML = row.es_standard ? fareHtml(row.es_standard) : '<span class="text-sm text-muted">\u2013</span>';
|
||||
esStdEl.innerHTML = '<span class="text-xs text-muted">Std</span> ' + (row.es_standard ? fareHtml(row.es_standard) : '<span class="text-sm text-muted">\u2013</span>');
|
||||
esStdEl.classList.toggle('fare-inactive', esClass !== 'standard');
|
||||
}
|
||||
if (esPlusEl) {
|
||||
esPlusEl.innerHTML = row.es_plus ? fareHtml(row.es_plus) : '<span class="text-sm text-muted">\u2013</span>';
|
||||
esPlusEl.innerHTML = '<span class="text-xs text-muted">SP</span> ' + (row.es_plus ? fareHtml(row.es_plus) : '<span class="text-sm text-muted">\u2013</span>');
|
||||
esPlusEl.classList.toggle('fare-inactive', esClass !== 'plus');
|
||||
}
|
||||
|
||||
var totalSpan = tr.querySelector('.total-price');
|
||||
if (totalSpan) {
|
||||
if (key in totals) {
|
||||
if (key.indexOf(':unreachable:') !== -1) {
|
||||
totalSpan.innerHTML = '<span class="text-xs text-muted" title="No National Rail service from {{ departure_station_name }} arrives at Paddington in time for this Eurostar">No rail connection</span>';
|
||||
} else if (key in totals) {
|
||||
var total = totals[key];
|
||||
var html = '<span class="text-sm text-green" style="font-weight:700">' + fmtPrice(total);
|
||||
if (minTotal !== null && maxTotal !== null) {
|
||||
|
|
@ -395,42 +421,153 @@
|
|||
}
|
||||
html += '</span>';
|
||||
totalSpan.innerHTML = html;
|
||||
} else if (!nrFare && walkonLoadingSections[row.section]) {
|
||||
totalSpan.innerHTML = '';
|
||||
} else if (!nrFare) {
|
||||
totalSpan.innerHTML = '<span class="text-xs text-muted" title="National Rail fare data is not available for this service">NR fare not available</span>';
|
||||
} else {
|
||||
var missing = !nrFare ? 'No NR fare' : 'No Eurostar fare';
|
||||
totalSpan.innerHTML = '<span class="text-xs text-muted">' + missing + '</span>';
|
||||
totalSpan.innerHTML = '<span class="text-xs text-muted" title="No Eurostar price found for this service — it may be sold out in the selected class. Check eurostar.com.">No Eurostar price</span>';
|
||||
}
|
||||
}
|
||||
});
|
||||
updateSelectionBar();
|
||||
}
|
||||
|
||||
function loadWalkonFares() {
|
||||
var urls = WALKON_API_URLS;
|
||||
var pending = Object.keys(urls).length;
|
||||
if (!pending) return;
|
||||
function done() {
|
||||
var ids = Object.keys(urls);
|
||||
if (!ids.length) return;
|
||||
/* outbound first, then inbound */
|
||||
ids.sort(function(a, b) {
|
||||
return (SECTION_DIRECTIONS[a] === 'outbound' ? 0 : 1) -
|
||||
(SECTION_DIRECTIONS[b] === 'outbound' ? 0 : 1);
|
||||
});
|
||||
ids.forEach(function(sid) { walkonLoadingSections[sid] = true; });
|
||||
var pending = ids.length;
|
||||
function done(id) {
|
||||
walkonLoadingSections[id] = false;
|
||||
if (--pending === 0) {
|
||||
var el = document.getElementById('walkon-loading');
|
||||
if (el) el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
for (var sectionId in urls) {
|
||||
(function(id, url) {
|
||||
fetch(url).then(function(r) { return r.json(); }).then(function(fares) {
|
||||
mergeWalkonFares(id, fares);
|
||||
updateDisplay();
|
||||
done();
|
||||
}).catch(done);
|
||||
})(sectionId, urls[sectionId]);
|
||||
/* sequential: each fetch starts only after the previous one finishes */
|
||||
ids.reduce(function(chain, id) {
|
||||
return chain.then(function() {
|
||||
return fetch(urls[id])
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(fares) { mergeWalkonFares(id, fares); done(id); updateDisplay(); })
|
||||
.catch(function() { done(id); updateDisplay(); });
|
||||
});
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
function initSelectionFromUrl() {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
for (var sid in SECTION_DIRECTIONS) {
|
||||
var dir = SECTION_DIRECTIONS[sid];
|
||||
var val = params.get(dir === 'outbound' ? 'out' : 'ret');
|
||||
if (val) {
|
||||
var rowKey = sid + ':' + val;
|
||||
if (TRIP_FARES[rowKey]) selectedRowKeys[sid] = rowKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectRow(tr) {
|
||||
var key = tr.getAttribute('data-row-key');
|
||||
if (!key || key.indexOf(':unreachable:') !== -1) return;
|
||||
var row = TRIP_FARES[key];
|
||||
if (!row) return;
|
||||
selectedRowKeys[row.section] = (selectedRowKeys[row.section] === key) ? null : key;
|
||||
updateRowHighlights();
|
||||
updateSelectionBar();
|
||||
history.replaceState(null, '', buildUrl());
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
selectedRowKeys = {};
|
||||
updateRowHighlights();
|
||||
updateSelectionBar();
|
||||
history.replaceState(null, '', buildUrl());
|
||||
}
|
||||
|
||||
function updateRowHighlights() {
|
||||
document.querySelectorAll('tr[data-row-key]').forEach(function(tr) {
|
||||
var key = tr.getAttribute('data-row-key');
|
||||
var row = TRIP_FARES[key];
|
||||
if (!row) return;
|
||||
tr.classList.toggle('row-selected', selectedRowKeys[row.section] === key);
|
||||
});
|
||||
}
|
||||
|
||||
function updateSelectionBar() {
|
||||
var bar = document.getElementById('selection-bar');
|
||||
if (!bar) return;
|
||||
var allSids = Object.keys(SECTION_DIRECTIONS);
|
||||
var activeSids = allSids.filter(function(sid) { return selectedRowKeys[sid]; });
|
||||
if (activeSids.length === 0) { bar.style.display = 'none'; return; }
|
||||
bar.style.display = 'block';
|
||||
|
||||
var totalNr = 0, totalEs = 0, totalCircle = 0, allPrices = true;
|
||||
var parts = [];
|
||||
activeSids.forEach(function(sid) {
|
||||
var rowKey = selectedRowKeys[sid];
|
||||
var row = TRIP_FARES[rowKey];
|
||||
if (!row) return;
|
||||
var nrFare = currentNrFare(row);
|
||||
var esClass = currentEsClasses[sid] || 'standard';
|
||||
var esFare = esClass === 'standard' ? row.es_standard : row.es_plus;
|
||||
if (nrFare) totalNr += nrFare.price; else allPrices = false;
|
||||
if (esFare) totalEs += esFare.price; else allPrices = false;
|
||||
totalCircle += row.circle_fare || 0;
|
||||
var kp = rowKey.split(':');
|
||||
// outbound key: section:NRdep:ESdep — show NR dep (Bristol)
|
||||
// inbound key: section:NRdep:ESdep — show ES dep (destination, CET)
|
||||
var depTime = SECTION_DIRECTIONS[sid] === 'outbound'
|
||||
? kp[1] + ':' + kp[2]
|
||||
: kp[3] + ':' + kp[4];
|
||||
parts.push((SECTION_DIRECTIONS[sid] === 'outbound' ? 'Out ' : 'Ret ') + depTime);
|
||||
});
|
||||
|
||||
var descEl = document.getElementById('sel-desc');
|
||||
if (descEl) descEl.textContent = parts.join(' · ');
|
||||
|
||||
var hintEl = document.getElementById('sel-hint');
|
||||
if (hintEl) {
|
||||
if (allSids.length > 1 && activeSids.length < allSids.length) {
|
||||
var missingDir = SECTION_DIRECTIONS[allSids.filter(function(s) { return !selectedRowKeys[s]; })[0]];
|
||||
hintEl.textContent = 'Select a ' + (missingDir === 'outbound' ? 'outbound' : 'return') + ' journey to see combined total';
|
||||
hintEl.style.display = '';
|
||||
} else {
|
||||
hintEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
var grandTotal = totalNr + totalEs + totalCircle;
|
||||
var nrEl = document.getElementById('sel-nr');
|
||||
var esEl = document.getElementById('sel-es');
|
||||
var grandEl = document.getElementById('sel-grand');
|
||||
if (nrEl) nrEl.innerHTML = 'NR <strong>' + (allPrices ? fmtPrice(totalNr) : '–') + '</strong>';
|
||||
if (esEl) esEl.innerHTML = 'Eurostar <strong>' + (allPrices ? fmtPrice(totalEs) : '–') + '</strong>';
|
||||
if (grandEl) {
|
||||
var label = (activeSids.length === allSids.length && allSids.length > 1) ? 'Grand total' : 'Total';
|
||||
var priceHtml = allPrices
|
||||
? '<strong style="font-size:1.05rem;color:#276749">' + fmtPrice(grandTotal) + '</strong>'
|
||||
: '<strong>–</strong>';
|
||||
grandEl.innerHTML = label + ' ' + priceHtml;
|
||||
}
|
||||
}
|
||||
|
||||
function initialiseResultsPage() {
|
||||
initSelectionFromUrl();
|
||||
var needsAdvance = Object.keys(currentNrClasses).some(function(sid) {
|
||||
var c = currentNrClasses[sid];
|
||||
return c === 'advance_std' || c === 'advance_1st';
|
||||
});
|
||||
if (needsAdvance) loadMissingAdvanceFares();
|
||||
updateDisplay();
|
||||
updateRowHighlights();
|
||||
loadWalkonFares();
|
||||
startTimetableRefresh();
|
||||
}
|
||||
|
|
@ -548,15 +685,15 @@
|
|||
<thead>
|
||||
<tr>
|
||||
{% if section.direction == 'inbound' %}
|
||||
<th>Eurostar<br><span class="text-xs font-normal text-muted">{{ destination }} → St Pancras</span></th>
|
||||
<th class="col-transfer">Transfer<br><span class="text-xs font-normal text-muted">St Pancras → Paddington</span></th>
|
||||
<th class="flow-step">Eurostar<br><span class="text-xs font-normal text-muted">{{ destination }} → St Pancras</span></th>
|
||||
<th class="col-transfer flow-step">Transfer<br><span class="text-xs font-normal text-muted">St Pancras → Paddington</span></th>
|
||||
<th>National Rail<br><span class="text-xs font-normal text-muted">Paddington → {{ departure_station_name }}</span></th>
|
||||
{% else %}
|
||||
<th>National Rail<br><span class="text-xs font-normal text-muted">{{ departure_station_name }} → Paddington</span></th>
|
||||
<th class="col-transfer">Transfer<br><span class="text-xs font-normal text-muted">Paddington → St Pancras</span></th>
|
||||
<th class="flow-step">National Rail<br><span class="text-xs font-normal text-muted">{{ departure_station_name }} → Paddington</span></th>
|
||||
<th class="col-transfer flow-step">Transfer<br><span class="text-xs font-normal text-muted">Paddington → St Pancras</span></th>
|
||||
<th>Eurostar<br><span class="text-xs font-normal text-muted">St Pancras → {{ destination }}</span></th>
|
||||
{% endif %}
|
||||
<th class="nowrap">Total</th>
|
||||
<th class="nowrap">Total<br><span class="text-xs font-normal text-muted">click row to select</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -577,8 +714,10 @@
|
|||
{% else %}
|
||||
{% set row_class = '' %}
|
||||
{% endif %}
|
||||
<tr class="{{ row_class }}" data-row-key="{{ row.row_key }}"
|
||||
{% if row.eurostar_price is not none %}data-es-std="{{ row.eurostar_price }}"{% endif %}>
|
||||
<tr class="{{ row_class }}{% if row.row_type == 'trip' %} row-selectable{% endif %}"
|
||||
data-row-key="{{ row.row_key }}"
|
||||
{% if row.eurostar_price is not none %}data-es-std="{{ row.eurostar_price }}"{% endif %}
|
||||
{% if row.row_type == 'trip' %}onclick="selectRow(this)"{% endif %}>
|
||||
{% if row.row_type == 'trip' %}
|
||||
{% if section.direction == 'inbound' %}
|
||||
<td>
|
||||
|
|
@ -591,8 +730,9 @@
|
|||
{%- if row.train_number %}{{ row.train_number }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="fare-line es-standard">{% if row.eurostar_price is not none %}<span class="text-sm font-bold">£{{ "%.2f"|format(row.eurostar_price) }}</span>{% endif %}</span>
|
||||
<span class="fare-line es-plus">{% if row.eurostar_plus_price is not none %}<span class="text-sm font-bold">£{{ "%.2f"|format(row.eurostar_plus_price) }}</span>{% endif %}</span>
|
||||
<span class="fare-line es-standard"><span class="text-xs text-muted">Std</span>{% if row.eurostar_price is not none %} <span class="text-sm font-bold">£{{ "%.2f"|format(row.eurostar_price) }}</span>{% endif %}</span>
|
||||
<span class="fare-line es-plus"><span class="text-xs text-muted">SP</span>{% if row.eurostar_plus_price is not none %} <span class="text-sm font-bold">£{{ "%.2f"|format(row.eurostar_plus_price) }}</span>{% endif %}</span>
|
||||
<span class="text-xs text-muted mobile-conn">then {{ row.connection_duration }} to station{% if row.connection_minutes < 45 %} ⚠️{% endif %}</span>
|
||||
</td>
|
||||
<td class="col-transfer" style="color:#4a5568">
|
||||
<span class="nowrap">{{ row.connection_duration }}{% if row.connection_minutes < 45 %} <span title="Tight connection">⚠️</span>{% endif %}</span>
|
||||
|
|
@ -612,7 +752,7 @@
|
|||
<span class="font-bold nowrap">{{ row.depart_paddington }} → {{ row.arrive_uk_station }}</span>
|
||||
<span class="text-sm text-muted nowrap">({{ row.gwr_duration }})</span>
|
||||
{% if row.headcode or row.arrive_platform %}
|
||||
<br><span class="text-xs text-muted">{{ row.headcode }}{% if row.headcode and row.arrive_platform %} · {% endif %}{% if row.arrive_platform %}Plat {{ row.arrive_platform }}{% endif %}</span>
|
||||
<br><span class="text-xs text-muted mobile-hide">{{ row.headcode }}{% if row.headcode and row.arrive_platform %} · {% endif %}{% if row.arrive_platform %}Plat {{ row.arrive_platform }}{% endif %}</span>
|
||||
{% endif %}
|
||||
<span class="fare-line nr-walkon">{% if row.ticket_price is not none %}<span class="text-sm font-bold">£{{ "%.2f"|format(row.ticket_price) }}</span>{% endif %}</span>
|
||||
<span class="fare-line nr-advance-std"></span>
|
||||
|
|
@ -623,11 +763,12 @@
|
|||
<span class="font-bold nowrap">{{ row.depart_bristol }} → {{ row.arrive_paddington }}</span>
|
||||
<span class="text-sm text-muted nowrap">({{ row.gwr_duration }})</span>
|
||||
{% if row.headcode or row.arrive_platform %}
|
||||
<br><span class="text-xs text-muted">{{ row.headcode }}{% if row.headcode and row.arrive_platform %} · {% endif %}{% if row.arrive_platform %}Plat {{ row.arrive_platform }}{% endif %}</span>
|
||||
<br><span class="text-xs text-muted mobile-hide">{{ row.headcode }}{% if row.headcode and row.arrive_platform %} · {% endif %}{% if row.arrive_platform %}Plat {{ row.arrive_platform }}{% endif %}</span>
|
||||
{% endif %}
|
||||
<span class="fare-line nr-walkon">{% if row.ticket_price is not none %}<span class="text-sm font-bold">£{{ "%.2f"|format(row.ticket_price) }}</span>{% endif %}</span>
|
||||
<span class="fare-line nr-advance-std"></span>
|
||||
<span class="fare-line nr-advance-1st"></span>
|
||||
<span class="text-xs text-muted mobile-conn">then {{ row.connection_duration }} to Eurostar{% if row.connection_minutes < 80 %} ⚠️{% endif %}</span>
|
||||
</td>
|
||||
<td class="col-transfer" style="color:#4a5568">
|
||||
<span class="nowrap">{{ row.connection_duration }}{% if row.connection_minutes < 80 %} <span title="Tight connection">⚠️</span>{% endif %}</span>
|
||||
|
|
@ -649,8 +790,8 @@
|
|||
{%- if row.train_number %}{{ row.train_number }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="fare-line es-standard">{% if row.eurostar_price is not none %}<span class="text-sm font-bold">£{{ "%.2f"|format(row.eurostar_price) }}</span>{% endif %}</span>
|
||||
<span class="fare-line es-plus">{% if row.eurostar_plus_price is not none %}<span class="text-sm font-bold">£{{ "%.2f"|format(row.eurostar_plus_price) }}</span>{% endif %}</span>
|
||||
<span class="fare-line es-standard"><span class="text-xs text-muted">Std</span>{% if row.eurostar_price is not none %} <span class="text-sm font-bold">£{{ "%.2f"|format(row.eurostar_price) }}</span>{% endif %}</span>
|
||||
<span class="fare-line es-plus"><span class="text-xs text-muted">SP</span>{% if row.eurostar_plus_price is not none %} <span class="text-sm font-bold">£{{ "%.2f"|format(row.eurostar_plus_price) }}</span>{% endif %}</span>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="font-bold nowrap">
|
||||
|
|
@ -665,7 +806,7 @@
|
|||
</td>
|
||||
{% else %}
|
||||
<td>
|
||||
<span class="text-dimmed text-sm">Too early</span>
|
||||
<span class="text-dimmed text-sm" title="No National Rail service from {{ departure_station_name }} arrives at Paddington in time for this Eurostar">Too early</span>
|
||||
</td>
|
||||
<td class="col-transfer text-dimmed">—</td>
|
||||
<td>
|
||||
|
|
@ -676,8 +817,10 @@
|
|||
<span class="font-bold nowrap text-dimmed">{{ row.depart_st_pancras }} → {{ row.arrive_destination }}</span>
|
||||
{% if row.train_number %}<br><span class="text-xs text-dimmed">{{ row.train_number }}</span>{% endif %}
|
||||
{% endif %}
|
||||
<span class="fare-line es-standard"></span>
|
||||
<span class="fare-line es-plus"></span>
|
||||
</td>
|
||||
<td class="text-dimmed nowrap">No connection</td>
|
||||
<td class="text-dimmed nowrap"><span class="total-price"></span></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -718,4 +861,19 @@
|
|||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div id="selection-bar">
|
||||
<div class="sel-bar-inner">
|
||||
<div>
|
||||
<span id="sel-desc" style="color:#2d3748"></span>
|
||||
<span id="sel-hint" style="display:none; margin-left:1rem; color:#a0aec0; font-size:0.8rem"></span>
|
||||
</div>
|
||||
<div class="sel-totals">
|
||||
<span id="sel-nr" class="text-muted"></span>
|
||||
<span id="sel-es" class="text-muted"></span>
|
||||
<span id="sel-grand"></span>
|
||||
<button class="sel-clear" onclick="clearSelection()">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue