The loading page now opens an EventSource to a new ?render=stream endpoint. The server immediately sends a shell event (full page chrome: nav, filters, JS — no external fetches needed), then a section event per direction as each one's NR + Eurostar data arrives, and finally a done event with the summary and timetable-refresh URL. The client slots each section card into a placeholder and calls initialiseResultsPage() only after done, so fares and advance-fare streaming start at the right moment. Adds results_shell.html (shell template with empty JS data globals and mergeSectionData/finaliseResults hooks), results_section.html (extracted section card partial used by both the full and stream render paths), and helper functions _section_trip_fares() and _build_summary_html() to avoid duplicating fare-dict assembly between the two paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
172 lines
12 KiB
HTML
172 lines
12 KiB
HTML
<div class="card" style="margin-bottom:1.5rem">
|
||
<h2>
|
||
{% if section.direction == 'inbound' %}
|
||
Return: {{ destination }} → {{ departure_station_name }}
|
||
{% else %}
|
||
Outbound: {{ departure_station_name }} → {{ destination }}
|
||
{% endif %}
|
||
</h2>
|
||
<p class="card-meta">{{ section.date_display }}</p>
|
||
{% if section.rows %}
|
||
<table class="results-table">
|
||
<thead>
|
||
<tr>
|
||
{% if section.direction == 'inbound' %}
|
||
<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 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<br><span class="text-xs font-normal text-muted">click row to select</span></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% set trip_rows = section.rows | selectattr('row_type', 'equalto', 'trip') | list %}
|
||
{% if trip_rows %}
|
||
{% set best_mins = trip_rows | map(attribute='total_minutes') | min %}
|
||
{% set worst_mins = trip_rows | map(attribute='total_minutes') | max %}
|
||
{% endif %}
|
||
{% for row in section.rows %}
|
||
{% if row.row_type == 'trip' and row.total_minutes <= best_mins + 5 and trip_rows | length > 1 %}
|
||
{% set row_class = 'row-fast' %}
|
||
{% elif row.row_type == 'trip' and row.total_minutes >= worst_mins - 5 and trip_rows | length > 1 %}
|
||
{% set row_class = 'row-slow' %}
|
||
{% elif row.row_type == 'unreachable' %}
|
||
{% set row_class = 'row-unreachable' %}
|
||
{% elif loop.index is odd %}
|
||
{% set row_class = 'row-alt' %}
|
||
{% else %}
|
||
{% set row_class = '' %}
|
||
{% 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>
|
||
<span class="font-bold nowrap"><span class="font-normal text-muted" style="font-size:0.85em">(CET)</span> {{ row.depart_destination }} → {{ row.arrive_st_pancras }} <span class="font-normal text-muted" style="font-size:0.85em">(UK)</span></span>
|
||
<br><span class="text-xs text-muted">check in by {{ row.check_in_by }}</span>
|
||
{% if row.eurostar_duration or row.train_number %}
|
||
<br><span class="text-xs text-muted">
|
||
{%- if row.eurostar_duration %}<span class="nowrap">({{ row.eurostar_duration }})</span>{% endif %}
|
||
{%- if row.eurostar_duration and row.train_number %} · {% endif %}
|
||
{%- if row.train_number %}{{ row.train_number }}{% endif %}
|
||
</span>
|
||
{% endif %}
|
||
<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>
|
||
{% if row.circle_services %}
|
||
{% if row.circle_services | length > 1 %}
|
||
{% set c_early = row.circle_services[0] %}
|
||
{% set c = row.circle_services[1] %}
|
||
<br><span class="text-xs text-muted nowrap">Circle {{ c_early.depart }} → PAD {{ c_early.arrive_pad }} · £{{ "%.2f"|format(c_early.fare) }}</span>
|
||
<br><span class="text-xs text-muted nowrap" style="opacity:0.7">next {{ c.depart }} → PAD {{ c.arrive_pad }}</span>
|
||
{% else %}
|
||
{% set c = row.circle_services[0] %}
|
||
<br><span class="text-xs text-muted nowrap">Circle {{ c.depart }} → PAD {{ c.arrive_pad }} · £{{ "%.2f"|format(c.fare) }}</span>
|
||
{% endif %}
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<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 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>
|
||
</td>
|
||
{% else %}
|
||
<td>
|
||
<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 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>
|
||
{% if row.circle_services %}
|
||
{% set c = row.circle_services[0] %}
|
||
<br><span class="text-xs text-muted nowrap">Circle {{ c.depart }} → KX {{ c.arrive_kx }} · £{{ "%.2f"|format(c.fare) }}</span>
|
||
{% if row.circle_services | length > 1 %}
|
||
{% set c2 = row.circle_services[1] %}
|
||
<br><span class="text-xs text-muted nowrap" style="opacity:0.7">next {{ c2.depart }} → KX {{ c2.arrive_kx }} · £{{ "%.2f"|format(c2.fare) }}</span>
|
||
{% endif %}
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<span class="font-bold nowrap">{{ row.depart_st_pancras }} → {{ row.arrive_destination }} <span class="font-normal text-muted" style="font-size:0.85em">(CET)</span></span>
|
||
{% if row.eurostar_duration or row.train_number %}
|
||
<br><span class="text-xs text-muted">
|
||
{%- if row.eurostar_duration %}<span class="nowrap">({{ row.eurostar_duration }})</span>{% endif %}
|
||
{%- if row.eurostar_duration and row.train_number %} · {% endif %}
|
||
{%- if row.train_number %}{{ row.train_number }}{% endif %}
|
||
</span>
|
||
{% endif %}
|
||
<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">
|
||
{% if row.total_minutes <= best_mins + 5 and trip_rows | length > 1 %}
|
||
<span class="text-green" title="Fastest journey">{{ row.total_duration }} ⚡</span>
|
||
{% elif row.total_minutes >= worst_mins - 5 and trip_rows | length > 1 %}
|
||
<span class="text-red" title="Slowest journey">{{ row.total_duration }} 🐢</span>
|
||
{% else %}
|
||
<span class="text-blue">{{ row.total_duration }}</span>
|
||
{% endif %}
|
||
<br><span class="total-price"></span>
|
||
</td>
|
||
{% else %}
|
||
<td>
|
||
<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>
|
||
{% if section.direction == 'inbound' %}
|
||
<span class="font-bold nowrap text-dimmed">{{ row.depart_destination }} → {{ row.arrive_st_pancras }}</span>
|
||
{% if row.train_number %}<br><span class="text-xs text-dimmed">{{ row.train_number }}</span>{% endif %}
|
||
{% else %}
|
||
<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"><span class="total-price"></span></td>
|
||
{% endif %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<div class="empty-state">
|
||
<p>No valid journeys found.</p>
|
||
<p>
|
||
{% if section.gwr_count == 0 and section.eurostar_count == 0 %}
|
||
Could not retrieve train data. Check your network connection or try again.
|
||
{% elif section.gwr_count == 0 %}
|
||
No National Rail trains found for this date.
|
||
{% elif section.eurostar_count == 0 %}
|
||
No Eurostar services found for {{ destination }} on this date.
|
||
{% else %}
|
||
No National Rail + Eurostar combination has a {{ section.min_connection }}-{{ section.max_connection }} minute connection.
|
||
{% endif %}
|
||
</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|