feat: implement night mode functionality with UI updates and journey tracking
- Added NightScreen component for displaying night mode information and alerts. - Introduced NextStops component to show upcoming stops during a journey. - Implemented logic for determining night mode activation based on schedule and user settings. - Created journey utility functions to manage onward journey data and stop information. - Updated tests to cover new night mode features and journey logic. - Added fixture for testing night mode scenarios with specific timestamps. - Removed QR code from night screen for a cleaner UI experience. - Enhanced admin settings to manage night mode configurations.
This commit is contained in:
@@ -1,6 +1,19 @@
|
||||
import { ApiError } from './board.mjs';
|
||||
import { TIMETABLE_MAX_MS, localDate, shiftDate, validDate } from '../src/lib/timetable.mjs';
|
||||
|
||||
function normalizeStops(raw) {
|
||||
if (!Array.isArray(raw)) return [];
|
||||
const stops = raw.map(stop => {
|
||||
const offset = stop.time_offset_minutes == null || stop.time_offset_minutes === '' ? NaN : Number(stop.time_offset_minutes);
|
||||
return { id: Number(stop.stop_id), name: typeof stop.name === 'string' ? stop.name.trim() : '', sequence: Number(stop.sequence),
|
||||
offsetMinutes: Number.isFinite(offset) && offset >= 0 && offset < 1440 ? offset : null };
|
||||
});
|
||||
// An ambiguous loop or incomplete stop identity cannot produce a reliable diagram.
|
||||
if (stops.some(stop => !Number.isSafeInteger(stop.id) || stop.id < 1 || !stop.name || !Number.isSafeInteger(stop.sequence) || stop.sequence < 0)
|
||||
|| new Set(stops.map(stop => stop.id)).size !== stops.length || new Set(stops.map(stop => stop.sequence)).size !== stops.length) return [];
|
||||
return stops.sort((a, b) => a.sequence - b.sequence);
|
||||
}
|
||||
|
||||
function normalizeSchedules(raw, route, rawStops, stopId) {
|
||||
if (!Array.isArray(raw) || !Array.isArray(rawStops)) throw new ApiError();
|
||||
const stops = new Map(rawStops.map(stop => [Number(stop.stop_id), stop]));
|
||||
@@ -35,7 +48,7 @@ function normalizeSchedules(raw, route, rawStops, stopId) {
|
||||
const terminal = end || [...rawStops].sort((a, b) => Number(b.sequence) - Number(a.sequence))[0];
|
||||
const origin = start || [...rawStops].sort((a, b) => Number(a.sequence) - Number(b.sequence))[0];
|
||||
return [{ arrivalSeconds: Number(time[1]) * 3600 + Number(time[2]) * 60 + Number(time[3] || 0) + offset * 60,
|
||||
originStopId: Number(origin?.stop_id), originTime: `${time[1].padStart(2, '0')}:${time[2]}`,
|
||||
originStopId: Number(origin?.stop_id), originTime: `${time[1].padStart(2, '0')}:${time[2]}`, endStopId: Number(terminal?.stop_id),
|
||||
destination: terminal?.name?.trim() || route.name.split(/\s+[-–—]\s+/).at(-1) || route.name,
|
||||
note: typeof trip.note === 'string' ? trip.note.trim() : '', daysOfWeek: days(trip.daysOfWeek), exceptDaysOfWeek: days(trip.exceptDaysOfWeek) }];
|
||||
});
|
||||
@@ -67,7 +80,7 @@ export async function loadTimetable({ upstream, cityId, cityName, stop, now }) {
|
||||
while (index < routes.length) {
|
||||
const current = index++, route = routes[current];
|
||||
const [schedules, stops] = await Promise.all([upstream(`api/v1/routes/${route.id}/schedules`), upstream(`api/v1/routes/${route.id}/stops`)]);
|
||||
result[current] = { ...route, schedules: normalizeSchedules(schedules, route, stops, Number(stop.id)) };
|
||||
result[current] = { ...route, stops: normalizeStops(stops), schedules: normalizeSchedules(schedules, route, stops, Number(stop.id)) };
|
||||
}
|
||||
}));
|
||||
return { version: 1, cityId, cityName, stop: { id: Number(stop.id), name: stop.name.trim(), direction: stop.description?.trim() || '' },
|
||||
|
||||
Reference in New Issue
Block a user