Sorry - this site requires Javascript in order to function. Please ensure you are using the latest browser and your device is up-to-date.
Departures
Community

Woodhouse

Loading...
Station Code: WDH

Live Departures

Loading departures...

Community Reports

Key

Service Status

On time
Delayed
Cancelled
Arrived/At platform
Departed

Train Capacity

Quiet
Moderate
Busy
Very Busy/Full

Information

1
Platform Number
TOC Train Operator
Advertisement
`; return adContainer; } // Render disruptions function renderDisruptions(data) { const section = document.getElementById('disruptionsSection'); const container = document.getElementById('disruptionsContainer'); const expandBtn = document.getElementById('expandDisruptionsBtn'); if (!data.disruptions || data.disruptions.length === 0) { section.style.display = 'none'; return; } // Store all disruptions allDisruptions = data.disruptions; // Determine how many to show const maxToShow = disruptionsExpanded ? allDisruptions.length : Math.min(2, allDisruptions.length); const disruptionsToShow = allDisruptions.slice(0, maxToShow); container.innerHTML = ''; disruptionsToShow.forEach(disruption => { const disruptionCard = createDisruptionCard(disruption); container.appendChild(disruptionCard); }); // Show/hide expand button if (allDisruptions.length > 2) { expandBtn.style.display = 'block'; updateExpandButton(); } else { expandBtn.style.display = 'none'; } section.style.display = 'block'; } // Create disruption card element function createDisruptionCard(disruption) { const card = document.createElement('div'); // Determine severity styling and labels const severityConfig = { 'normal': { title: 'Information', borderColor: 'border-blue-200 dark:border-blue-700', bgColor: 'bg-blue-100 dark:bg-blue-900/20', iconColor: 'text-blue-600 dark:text-blue-400', icon: 'fa-info-circle', buttonColor: 'bg-blue-500 hover:bg-blue-600 text-white' }, 'minor': { title: 'Minor Disruption', borderColor: 'border-amber-200 dark:border-amber-700', bgColor: 'bg-amber-100 dark:bg-amber-900/20', iconColor: 'text-amber-600 dark:text-amber-400', icon: 'fa-exclamation-circle', buttonColor: 'bg-amber-500 hover:bg-amber-600 text-white' }, 'major': { title: 'Major Disruption', borderColor: 'border-red-200 dark:border-red-700', bgColor: 'bg-red-100 dark:bg-red-900/20', iconColor: 'text-red-600 dark:text-red-400', icon: 'fa-exclamation-triangle', buttonColor: 'bg-red-500 hover:bg-red-600 text-white' }, 'severe': { title: 'Major Disruption', // Severe becomes Major borderColor: 'border-red-200 dark:border-red-700', bgColor: 'bg-red-100 dark:bg-red-900/20', iconColor: 'text-red-600 dark:text-red-400', icon: 'fa-exclamation-triangle', buttonColor: 'bg-red-500 hover:bg-red-600 text-white' } }; const severity = disruption.severity?.toLowerCase() || 'normal'; const config = severityConfig[severity] || severityConfig['normal']; card.className = `bg-white/95 backdrop-blur-sm border-2 ${config.borderColor} rounded-2xl p-6 dark:bg-gray-800/95 hover:shadow-lg transition-all duration-300`; // Check if incidentID exists and create button HTML const hasIncidentId = disruption.incidentID || disruption.incidentId || disruption.incident_id; const moreDetailsButton = hasIncidentId ? `
` : ''; card.innerHTML = `

${config.title}

${escapeHtml(disruption.message)}

${moreDetailsButton}
`; return card; } // Render community reports function renderCommunityReports(data) { const container = document.getElementById('communityReportsContent'); const noReportsDiv = document.getElementById('noCommunityReports'); // Debug logging console.log('Rendering community reports:', data.communityReports); if (!data.communityReports || !data.communityReports.reports || data.communityReports.reports.length === 0) { container.innerHTML = ''; noReportsDiv.classList.remove('hidden'); updateCommunityStatusBadge([]); return; } const reports = data.communityReports.reports; noReportsDiv.classList.add('hidden'); container.innerHTML = ''; // Limit to first 3 reports for sidebar display const displayReports = reports.slice(0, 3); displayReports.forEach(report => { const reportCard = createCommunityReportCard(report); container.appendChild(reportCard); }); updateCommunityStatusBadge(reports); } // Create community report card element function createCommunityReportCard(report) { const card = document.createElement('div'); // Determine severity styling const severityClass = report.severity === 'high' ? 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300' : (report.severity === 'medium' ? 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300' : 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300'); card.className = 'p-3 bg-gray-50 dark:bg-gray-700 rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors'; card.onclick = () => viewReport(report.id); card.innerHTML = `
${escapeHtml(report.title)} ${report.severity.charAt(0).toUpperCase() + report.severity.slice(1)}
${escapeHtml(report.category)}
${report.time} ${report.upvotes || 0}
`; return card; } // Update community status badge in header function updateCommunityStatusBadge(reports) { const badge = document.getElementById('communityStatusBadge'); if (!badge) return; const highSeverityCount = reports.filter(r => r.severity === 'high').length; const mediumSeverityCount = reports.filter(r => r.severity === 'medium').length; // Remove all existing classes badge.className = 'text-white rounded-full px-3 py-1 text-xs font-medium cursor-pointer hover:opacity-90 transition-opacity'; let statusClass = 'bg-green-500'; let statusIcon = 'fa-check-circle'; if (highSeverityCount > 0) { statusClass = 'bg-red-500'; statusIcon = 'fa-exclamation-triangle'; } else if (mediumSeverityCount > 0) { statusClass = 'bg-amber-500'; statusIcon = 'fa-exclamation-circle'; } else if (reports.length > 0) { statusClass = 'bg-blue-500'; statusIcon = 'fa-info-circle'; } badge.classList.add(statusClass); badge.innerHTML = `Community`; } // Helper functions function getLoadingInfo(loading) { if (loading === null || loading === undefined || loading === '') { return false; } const loadingPercent = parseInt(loading); if (loadingPercent <= 33) { return { text: 'Quiet' , color: 'bg-green-500' , bgColor: 'bg-green-100 dark:bg-green-900/20' , textColor: 'text-green-700 dark:text-green-300' , icon: 'fa-user' }; } else if (loadingPercent <=66) { return { text: 'Moderate' , color: 'bg-yellow-500' , bgColor: 'bg-yellow-100 dark:bg-yellow-900/20' , textColor: 'text-yellow-700 dark:text-yellow-300' , icon: 'fa-user' }; } else if (loadingPercent <=80) { return { text: 'Busy' , color: 'bg-orange-500' , bgColor: 'bg-orange-100 dark:bg-orange-900/20' , textColor: 'text-orange-700 dark:text-orange-300' , icon: 'fa-user-group' }; } else { return { text: 'Very Busy' , color: 'bg-red-500' , bgColor: 'bg-red-100 dark:bg-red-900/20' , textColor: 'text-red-700 dark:text-red-300' , icon: 'fa-users' }; } } function getStatusText(departure) { switch (departure.status) { case 'onTime' : return 'On time' ; case 'delayed' : return departure.estimatedDeparture ? `Exp ${departure.estimatedDeparture}` : 'Delayed' ; case 'cancelled' : return 'Cancelled' ; case 'departed' : return departure.actualDeparture ? `Departed at ${departure.actualDeparture}` : 'Departed' ; case 'arrived' : return departure.actualArrival ? `Arrived at ${departure.actualArrival}` : 'Arrived' ; default: return departure.statusMessage || 'Unknown' ; } } function escapeHtml(text) { const div=document.createElement('div'); div.textContent=text; return div.innerHTML; } function updateLastUpdated() { const now=new Date(); const timeString=now.toLocaleTimeString('en-GB', { hour: '2-digit' , minute: '2-digit' , second: '2-digit' , }); document.getElementById('lastUpdated').textContent=`Updated ${timeString}`; } // Main refresh function async function refreshDepartures() { if (isLoading) return; isLoading=true; const refreshIcon=document.getElementById('refreshIcon'); // Add spin animation if (refreshIcon) { refreshIcon.classList.add('spinner'); } try { const data=await fetchDepartures(); if (data) { renderDepartures(data); renderDisruptions(data); renderCommunityReports(data); updateLastUpdated(); lastApiCall=Date.now(); } } catch (error) { console.error('Error refreshing departures:', error); showError(error.message); } finally { isLoading=false; if (refreshIcon) { refreshIcon.classList.remove('spinner'); } } } // Filter form functionality function toggleFilterForm() { const formContent=document.getElementById('filterFormContent'); const toggleIcon=document.getElementById('filterToggleIcon'); const container=document.getElementById('filterFormContainer'); if (formContent.style.display==='none' ) { formContent.style.display='block' ; toggleIcon.style.transform='rotate(180deg)' ; container.classList.remove('filter-form-collapsed'); container.classList.add('filter-form-expanded'); } else { formContent.style.display='none' ; toggleIcon.style.transform='rotate(0deg)' ; container.classList.remove('filter-form-expanded'); container.classList.add('filter-form-collapsed'); } } // Modal functions function openFilterModal() { document.getElementById('filterModal').classList.remove('hidden'); document.body.style.overflow='hidden' ; } function closeFilterModal() { document.getElementById('filterModal').classList.add('hidden'); document.body.style.overflow='auto' ; } function clearFilters() { document.getElementById('timeFilter').value='' ; document.getElementById('hoursFilter').value='4' ; document.getElementById('destinationFilter').value='' ; document.getElementById('destinationFilterCode').value='' ; hideStationResults(); } function applyFilters() { closeFilterModal(); refreshDepartures(); } // Close modal on escape key document.addEventListener('keydown', function(e) { if (e.key==='Escape' ) { closeFilterModal(); } }); // Other existing functions function scrollToCommunityReports() { const communitySection=document.querySelector('h3:has(i.fa-users)').closest('.bg-white\\/90'); if (communitySection) { communitySection.scrollIntoView({ behavior: 'smooth' , block: 'center' }); communitySection.classList.add('ring-4', 'ring-purple-300' , 'ring-opacity-50' ); setTimeout(()=> { communitySection.classList.remove('ring-4', 'ring-purple-300', 'ring-opacity-50'); }, 2000); } } function viewReport(reportId) { window.location.href = `/community-reports/report?id=${reportId}`; } function createReport() { window.location.href = `/community-reports/?station=WDH`; } function viewDisruptionDetails(incidentId) { // Open disruption details in new tab/window window.open(`/service-status/incident/${incidentId}`, '_blank'); } function switchToPage(page) { if (page === 'arrivals') { window.location.href = `/arrivals/WDH`; } } // Toggle disruptions display function toggleDisruptions() { disruptionsExpanded = !disruptionsExpanded; const container = document.getElementById('disruptionsContainer'); const maxToShow = disruptionsExpanded ? allDisruptions.length : Math.min(2, allDisruptions.length); const disruptionsToShow = allDisruptions.slice(0, maxToShow); container.innerHTML = ''; disruptionsToShow.forEach(disruption => { const disruptionCard = createDisruptionCard(disruption); container.appendChild(disruptionCard); }); updateExpandButton(); } function updateExpandButton() { const expandText = document.getElementById('expandDisruptionsText'); const expandIcon = document.getElementById('expandDisruptionsIcon'); if (disruptionsExpanded) { expandText.textContent = 'Show Less'; expandIcon.className = 'fa-solid fa-fw fa-chevron-up ml-1'; } else { const hiddenCount = allDisruptions.length - 2; expandText.textContent = `Show All (${hiddenCount} more)`; expandIcon.className = 'fa-solid fa-fw fa-chevron-down ml-1'; } } function openDepartureDetails(rid) { location.href = `/service/${rid}`; } // Initialize filters from URL parameters (only on initial load) function initializeFiltersFromUrl() { const urlParams = new URLSearchParams(window.location.search); // Set destination filter const destParam = urlParams.get('filterCrs'); if (destParam) { // Find the station by code in the stations data const stationData = window.stations || stations; if (stationData) { const station = stationData.find(s => s.code.toLowerCase() === destParam.toLowerCase()); if (station) { document.getElementById('destinationFilter').value = station.name; document.getElementById('destinationFilterCode').value = station.code; } else { // If station not found, just set the code document.getElementById('destinationFilterCode').value = destParam; } } } // Set time filter const timeParam = urlParams.get('time'); if (timeParam) { document.getElementById('timeFilter').value = timeParam; } } // Initialize on page load document.addEventListener('DOMContentLoaded', function() { // Initialize filters from URL parameters (only once on page load) initializeFiltersFromUrl(); // Initialize station search initializeStationSearch(); // Add event listeners to filter inputs ['timeFilter', 'hoursFilter'].forEach(id => { const element = document.getElementById(id); if (element) { element.addEventListener('change', () => { // Auto-apply filters when changed clearTimeout(refreshInterval); setTimeout(refreshDepartures, 500); }); } }); // Add event listener for destination filter code (when station is selected) const destinationFilterCode = document.getElementById('destinationFilterCode'); if (destinationFilterCode) { const observer = new MutationObserver(() => { clearTimeout(refreshInterval); setTimeout(refreshDepartures, 500); }); observer.observe(destinationFilterCode, { attributes: true, attributeFilter: ['value'] }); } // Initial load refreshDepartures(); // Set up auto-refresh every 10 seconds refreshInterval = setInterval(refreshDepartures, 10000); }); // Clean up interval on page unload window.addEventListener('beforeunload', function() { if (refreshInterval) { clearInterval(refreshInterval); } });