From 24960707e2ef1c6a6021520f858bb69f1bfee758 Mon Sep 17 00:00:00 2001 From: Fred Boniface Date: Sun, 3 May 2026 09:03:45 +0100 Subject: [PATCH] Add train details to train endpoint. Formatting and styling incomplete --- src/lib/components/ui/Loading.svelte | 101 ++--- src/lib/components/ui/NoResults.svelte | 86 ++-- src/lib/components/ui/TocStyle.svelte | 3 +- src/lib/components/ui/TrainService.svelte | 389 ++++++++++++------ .../ui/cards/HeadcodeSearchCard.svelte | 40 +- src/lib/geohash.svelte.ts | 6 +- src/lib/utils/time.ts | 20 +- src/routes/+error.svelte | 10 +- src/routes/trains/+page.svelte | 43 +- src/routes/trains/+page.ts | 104 +++-- 10 files changed, 473 insertions(+), 329 deletions(-) diff --git a/src/lib/components/ui/Loading.svelte b/src/lib/components/ui/Loading.svelte index a16cb73..858c05c 100644 --- a/src/lib/components/ui/Loading.svelte +++ b/src/lib/components/ui/Loading.svelte @@ -1,59 +1,66 @@
-
-
-
-

{message}

+
+
+
+

{message}

\ No newline at end of file + @keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.4; + } + } + diff --git a/src/lib/components/ui/NoResults.svelte b/src/lib/components/ui/NoResults.svelte index 29621d3..65c2b64 100644 --- a/src/lib/components/ui/NoResults.svelte +++ b/src/lib/components/ui/NoResults.svelte @@ -1,55 +1,55 @@
- -

{title}

-

{message}

-
- -
+ +

{title}

+

{message}

+
+ +
\ No newline at end of file + .btn-container { + margin-top: 20px; + } + diff --git a/src/lib/components/ui/TocStyle.svelte b/src/lib/components/ui/TocStyle.svelte index c390afc..2cbcc80 100644 --- a/src/lib/components/ui/TocStyle.svelte +++ b/src/lib/components/ui/TocStyle.svelte @@ -8,7 +8,6 @@ let code = $derived(toc.toUpperCase()); -
{code} @@ -25,7 +24,7 @@ font-size: 1.1rem; background-color: #333; color: #fff; - font-family:'Courier New', Courier, monospace; + font-family: 'Courier New', Courier, monospace; } .AW { diff --git a/src/lib/components/ui/TrainService.svelte b/src/lib/components/ui/TrainService.svelte index 6f1de25..cfff6c3 100644 --- a/src/lib/components/ui/TrainService.svelte +++ b/src/lib/components/ui/TrainService.svelte @@ -1,140 +1,293 @@
- - {#if isExpanded && details} -
- - {JSON.stringify(details)} -
- {/if} + + {#if isExpanded && details} +
+ +
+ + {#if details.header.cr} + + {details.header.cr} + {#if details.header.cl} + {details.header.cn ? ' near ' : ' at '} + {details.header.cl} + {/if} + + {/if} + + + {#if details.header.dr} + + {details.header.dr} + {#if details.header.dl} + {details.header.dn ? ' near ' : ' at '} + {details.header.dl} + {/if} + + {/if} +
+ +
+
+
+ + + + + + + + + + + + + + + + + + {#each details.locations as loc} + + + + {#if loc.r == "PASS"} + + + + {:else} + + + + + {/if} + + {/each} + +
ArrDep
LocationPlatSchActSchAct
{loc.t}{loc.p}Pass{formatUkTime(loc.wtp)}{formatUkTime(loc.atp || loc.etp || "--")}{formatUkTime(loc.pta || loc.wta || "--")}{formatUkTime(loc.ata || loc.eta || "--")}{formatUkTime(loc.ptd || loc.wtd || "--")}{formatUkTime(loc.atd || loc.etd || "--")}
+
+
+ {/if}
\ No newline at end of file + .to-summary { + font-size: 0.8rem; + font-style: oblique; + text-transform: lowercase; + } + + .box-ext { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + } + + .detail-head { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + text-align: center; + width: 100%; + } + + .cancel-reason, .delay-reason { + display: block; + padding: 4px 8px; + width: 95%; + } + + .cancel-reason { + color:rgb(255, 0, 0); + font-weight: 500; + animation: cancel-pulse 2s ease-in-out infinite; + } + + .delay-reason { + color:rgb(255, 119, 0); + font-weight: 500; + animation: cancel-pulse 2s ease-in-out infinite; + } + + @keyframes cancel-pulse { + 0% { + opacity: 1; + text-shadow: 0 0 0px rgb(255, 0, 0); + } + 50% { + opacity: 0.8; + text-shadow: 0 0 5px rgba(255,0,0,0.2) + } + 100% { + opacity: 1; + text-shadow: 0 0 0px rgb(255, 0, 0); + } + } + + .schedule-table-container { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + } + + .schedule-table { + width: 95%; + max-width: 375px; + padding-bottom: 1rem; + } + + th, td { + text-align: center; + } + + .tpl-cell { + color: yellow; + } + + .pass-loc { + color: var(--color-title); + opacity: 0.75; + } + + .can-loc { + text-decoration: line-through; + } + + .est { + font-style: oblique; + opacity: 0.5; + } + + .act { + color: yellow; + } + diff --git a/src/lib/components/ui/cards/HeadcodeSearchCard.svelte b/src/lib/components/ui/cards/HeadcodeSearchCard.svelte index 657f27e..3bda760 100644 --- a/src/lib/components/ui/cards/HeadcodeSearchCard.svelte +++ b/src/lib/components/ui/cards/HeadcodeSearchCard.svelte @@ -1,35 +1,27 @@ - -
- - - +
+ + +
\ No newline at end of file + diff --git a/src/lib/geohash.svelte.ts b/src/lib/geohash.svelte.ts index 60f956b..9f852f1 100644 --- a/src/lib/geohash.svelte.ts +++ b/src/lib/geohash.svelte.ts @@ -10,13 +10,13 @@ class NearestStationsState { private initGeoConfig: PositionOptions = { enableHighAccuracy: false, timeout: 500, - maximumAge: Infinity, - } + maximumAge: Infinity + }; private geoConfig: PositionOptions = { enableHighAccuracy: false, timeout: 20000, - maximumAge: 30000, + maximumAge: 30000 }; constructor() { diff --git a/src/lib/utils/time.ts b/src/lib/utils/time.ts index becb137..e6ac239 100644 --- a/src/lib/utils/time.ts +++ b/src/lib/utils/time.ts @@ -3,15 +3,15 @@ * Ensures Europe/London timezone irrespective of browser timezone. */ export function formatUkTime(dateStr: string | Date | undefined): string { - if (!dateStr) return '--:--'; - const date = typeof dateStr === 'string' ? new Date(dateStr): dateStr; + if (!dateStr) return '--:--'; + const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; - if (isNaN(date.getTime())) return '--:--'; + if (isNaN(date.getTime())) return '--:--'; - return date.toLocaleTimeString('en-GB', { - hour: '2-digit', - minute: '2-digit', - hour12: false, - timeZone: 'Europe/London', - }); -} \ No newline at end of file + return date.toLocaleTimeString('en-GB', { + hour: '2-digit', + minute: '2-digit', + hour12: false, + timeZone: 'Europe/London' + }); +} diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte index 23cea5b..f98a45a 100644 --- a/src/routes/+error.svelte +++ b/src/routes/+error.svelte @@ -8,12 +8,12 @@
{#if page.status == 404} - - + + {:else} - - -{/if} + + + {/if}

{page.status}

diff --git a/src/routes/trains/+page.svelte b/src/routes/trains/+page.svelte index 0937d62..9b56947 100644 --- a/src/routes/trains/+page.svelte +++ b/src/routes/trains/+page.svelte @@ -1,34 +1,29 @@ - - {#if data.results.length === 0} - + {:else} -

- {#each data.results as service (service.r)} - - {/each} -
+
+ {#each data.results as service (service.r)} + + {/each} +
{/if} diff --git a/src/routes/trains/+page.ts b/src/routes/trains/+page.ts index ff3a735..a46c66c 100644 --- a/src/routes/trains/+page.ts +++ b/src/routes/trains/+page.ts @@ -4,61 +4,59 @@ import type { PageLoad } from './$types'; import { error } from '@sveltejs/kit'; export const load: PageLoad = async ({ url }) => { - const headcode = url.searchParams.get('h'); - let dateParam = url.searchParams.get('d'); - const toc = url.searchParams.get('t') || ""; + const headcode = url.searchParams.get('h'); + let dateParam = url.searchParams.get('d'); + const toc = url.searchParams.get('t') || ''; - const date: string | Date = (dateParam === "" || dateParam === null) - ? new Date() - : dateParam; + const date: string | Date = dateParam === '' || dateParam === null ? new Date() : dateParam; - if (!headcode) { - throw error(400, { - message: 'Headcode not provided', - owlCode: 'INVALID_DATA' - }); - } + if (!headcode) { + throw error(400, { + message: 'Headcode not provided', + owlCode: 'INVALID_DATA' + }); + } - // Declared outside of the try so that it can be used in both the try and catch blocks - let results: ApiTrainsTrainByHeadcode.TrainByHeadcodeResponse[] + // Declared outside of the try so that it can be used in both the try and catch blocks + let results: ApiTrainsTrainByHeadcode.TrainByHeadcodeResponse[]; - try { - const response = await OwlClient.trains.getByHeadcode(headcode, date, toc); + try { + const response = await OwlClient.trains.getByHeadcode(headcode, date, toc); - results = (response.data); - return { - title: headcode.toUpperCase(), - results: results, - } - } catch (e: unknown) { - if (e instanceof ValidationError) { - throw error(400, { - message: e.message, - owlCode: 'VALIDATION_ERROR', - }); - } else if (e instanceof ApiError) { - // Check if NO_RESULTS error, and return empty array if that is the case - if (e.code === "NOT_FOUND") { - return { - title: headcode.toUpperCase(), - results: [] - } - } else { - throw error(e.status, { - message: e.message, - owlCode: 'API_ERROR', - }); - } - } else if (e instanceof Error) { - throw error(500, { - message: e.message, - owlCode: 'GEN_ERROR', - }) - } else { - throw error(500, { - message: "Unexpected error", - owlCode: "UNKNOWN_ERR", - }) - } - } -} + results = response.data; + return { + title: headcode.toUpperCase(), + results: results + }; + } catch (e: unknown) { + if (e instanceof ValidationError) { + throw error(400, { + message: e.message, + owlCode: 'VALIDATION_ERROR' + }); + } else if (e instanceof ApiError) { + // Check if NO_RESULTS error, and return empty array if that is the case + if (e.code === 'NOT_FOUND') { + return { + title: headcode.toUpperCase(), + results: [] + }; + } else { + throw error(e.status, { + message: e.message, + owlCode: 'API_ERROR' + }); + } + } else if (e instanceof Error) { + throw error(500, { + message: e.message, + owlCode: 'GEN_ERROR' + }); + } else { + throw error(500, { + message: 'Unexpected error', + owlCode: 'UNKNOWN_ERR' + }); + } + } +};