/* schedule.js — filters, pagination, table render, CSV export */ const PAGE_SIZE = 100; let currentOffset = 0; let currentTotal = 0; // ── init ────────────────────────────────────────────────────────────────────── document.addEventListener("DOMContentLoaded", () => { // Default date range: last 7 days const today = new Date(); const week = new Date(today); week.setDate(today.getDate() - 7); document.getElementById("f-date-from").value = fmtDate(week); document.getElementById("f-date-to").value = fmtDate(today); loadData(); }); // ── filter helpers ──────────────────────────────────────────────────────────── function getFilters() { return { date_from: document.getElementById("f-date-from").value || "", date_to: document.getElementById("f-date-to").value || "", airport: document.getElementById("f-airport").value, direction: document.getElementById("f-direction").value, flight_number: document.getElementById("f-flight").value.trim(), time_from: document.getElementById("f-time-from").value || "", time_to: document.getElementById("f-time-to").value || "", }; } function buildQuery(extra = {}) { const f = { ...getFilters(), limit: PAGE_SIZE, offset: currentOffset, ...extra }; return Object.entries(f) .filter(([, v]) => v !== "" && v !== "all") .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .join("&"); } function applyFilters() { currentOffset = 0; loadData(); } function resetFilters() { document.getElementById("f-date-from").value = ""; document.getElementById("f-date-to").value = ""; document.getElementById("f-airport").value = "all"; document.getElementById("f-direction").value = "all"; document.getElementById("f-flight").value = ""; document.getElementById("f-time-from").value = ""; document.getElementById("f-time-to").value = ""; currentOffset = 0; loadData(); } // ── pagination ──────────────────────────────────────────────────────────────── function prevPage() { if (currentOffset <= 0) return; currentOffset = Math.max(0, currentOffset - PAGE_SIZE); loadData(); } function nextPage() { if (currentOffset + PAGE_SIZE >= currentTotal) return; currentOffset += PAGE_SIZE; loadData(); } function updatePagination() { const page = Math.floor(currentOffset / PAGE_SIZE) + 1; const pages = Math.max(1, Math.ceil(currentTotal / PAGE_SIZE)); document.getElementById("page-info").textContent = `Стр. ${page} / ${pages}`; document.getElementById("total-info").textContent = `Всего: ${currentTotal.toLocaleString("ru")}`; document.getElementById("btn-prev").disabled = currentOffset <= 0; document.getElementById("btn-next").disabled = currentOffset + PAGE_SIZE >= currentTotal; } // ── data load ───────────────────────────────────────────────────────────────── async function loadData() { setLoading(true); try { const resp = await fetch(`/api/schedule/data?${buildQuery()}`); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const data = await resp.json(); currentTotal = data.total || 0; renderTable(data.flights || []); renderCards(data.flights || []); updatePagination(); document.getElementById("last-updated").textContent = "Обновлено: " + new Date().toLocaleTimeString("ru"); } catch (e) { showError(e.message); } finally { setLoading(false); } } // ── render table ────────────────────────────────────────────────────────────── function renderTable(flights) { const tbody = document.getElementById("table-body"); if (!flights.length) { tbody.innerHTML = `Нет данных по выбранным фильтрам`; return; } tbody.innerHTML = flights.map(f => { const dirIcon = f.direction === "departure" ? `` : ``; const route = routeStr(f); const sched = fmtTime(f.scheduled_at); const actual = f.actual_at ? fmtTime(f.actual_at) : "—"; const delay = delayCell(f.delay_min); const badge = statusBadge(f.status); const dateStr = fmtDateShort(f.scheduled_at); return ` ${dateStr} ${esc(f.flight_number)} ${esc(f.airline || "—")} ${esc(f.airport)} ${dirIcon} ${esc(route)} ${sched} ${actual} ${delay} ${badge} `; }).join(""); } // ── render cards (mobile) ───────────────────────────────────────────────────── function renderCards(flights) { const container = document.getElementById("cards-container"); if (!flights.length) { container.innerHTML = `
Нет данных по выбранным фильтрам
`; return; } container.innerHTML = flights.map(f => { const dirLabel = f.direction === "departure" ? "↑ Вылет" : "↓ Прилёт"; const dirClass = f.direction === "departure" ? "dir-dep" : "dir-arr"; const route = routeStr(f); const sched = fmtTime(f.scheduled_at); const actual = f.actual_at ? fmtTime(f.actual_at) : "—"; const delay = f.delay_min != null ? `${f.delay_min > 0 ? "+" : ""}${f.delay_min} мин` : "—"; const badge = statusBadge(f.status); return `
${esc(f.flight_number)}
${esc(f.airline || "—")}
${badge}
Аэропорт${esc(f.airport)}
Направление${dirLabel}
Маршрут${esc(route)}
Запланировано${sched}
Фактически${actual}
Задержка${delay}
`; }).join(""); } // ── CSV export ──────────────────────────────────────────────────────────────── function exportCsv() { const qs = buildQuery({ limit: 100000, offset: 0 }); window.location.href = `/api/schedule/export?${qs}`; } // ── helpers ─────────────────────────────────────────────────────────────────── function routeStr(f) { const o = f.origin || ""; const d = f.destination || ""; if (!o && !d) return "—"; if (!o) return `→ ${d}`; if (!d) return `${o} →`; return `${o} → ${d}`; } function statusBadge(status) { const map = { scheduled: "badge-scheduled", departed: "badge-departed", arrived: "badge-arrived", delayed: "badge-delayed", cancelled: "badge-cancelled", }; const cls = map[status] || "badge-scheduled"; const labels = { scheduled: "По расписанию", departed: "Вылетел", arrived: "Прилетел", delayed: "Задержан", cancelled: "Отменён", }; return `${labels[status] || status || "—"}`; } function delayCell(min) { if (min == null) return "—"; if (min > 0) return `+${min}`; if (min < 0) return `${min}`; return "0"; } function fmtDate(d) { return d.toISOString().slice(0, 10); } function fmtDateShort(iso) { if (!iso) return "—"; return iso.slice(0, 10); } function fmtTime(iso) { if (!iso) return "—"; try { return new Date(iso).toLocaleTimeString("ru", { hour: "2-digit", minute: "2-digit", timeZone: "Europe/Moscow" }); } catch { return iso.slice(11, 16); } } function esc(s) { if (!s) return "—"; return String(s) .replace(/&/g, "&") .replace(//g, ">"); } function setLoading(on) { if (on) { document.getElementById("table-body").innerHTML = `Загрузка…`; document.getElementById("cards-container").innerHTML = `
Загрузка…
`; } } function showError(msg) { document.getElementById("table-body").innerHTML = `Ошибка: ${esc(msg)}`; document.getElementById("cards-container").innerHTML = `
Ошибка: ${esc(msg)}
`; }