const state = { user: null, devices: [], dashboardElevators: [], countdownIntervalId: null, activeModal: null, selectedLogElevatorId: null }; const elements = { welcomeText: document.getElementById("welcomeText"), routeText: document.getElementById("routeText"), logoutButton: document.getElementById("logoutButton"), dashboardCards: document.getElementById("dashboardCards"), deviceList: document.getElementById("deviceList"), openDeviceModalButton: document.getElementById("openDeviceModalButton"), openLogModalButton: document.getElementById("openLogModalButton"), deviceModal: document.getElementById("deviceModal"), logModal: document.getElementById("logModal"), deviceForm: document.getElementById("deviceForm"), deviceEditId: document.getElementById("deviceEditId"), deviceModalTitle: document.getElementById("deviceModalTitle"), deviceModalSubtitle: document.getElementById("deviceModalSubtitle"), deviceRouteName: document.getElementById("deviceRouteName"), deviceName: document.getElementById("deviceName"), deviceSerialNumber: document.getElementById("deviceSerialNumber"), deviceLocation: document.getElementById("deviceLocation"), deviceStatus: document.getElementById("deviceStatus"), deviceInspectionIntervalDays: document.getElementById("deviceInspectionIntervalDays"), deviceNextInspectionDate: document.getElementById("deviceNextInspectionDate"), deviceMessage: document.getElementById("deviceMessage"), closeDeviceModalButton: document.getElementById("closeDeviceModalButton"), cancelDeviceModalButton: document.getElementById("cancelDeviceModalButton"), logForm: document.getElementById("logForm"), logElevatorId: document.getElementById("logElevatorId"), logElevatorSummary: document.getElementById("logElevatorSummary"), logStatus: document.getElementById("logStatus"), logInspectionDate: document.getElementById("logInspectionDate"), logInspectionIntervalDays: document.getElementById("logInspectionIntervalDays"), logNextInspectionDate: document.getElementById("logNextInspectionDate"), logMessage: document.getElementById("logMessage"), closeLogModalButton: document.getElementById("closeLogModalButton"), cancelLogModalButton: document.getElementById("cancelLogModalButton"), openDeviceFromLogButton: document.getElementById("openDeviceFromLogButton") }; bindEvents(); bootPage(); function bindEvents() { elements.logoutButton.addEventListener("click", logout); elements.openDeviceModalButton.addEventListener("click", () => openDeviceModal()); elements.openLogModalButton.addEventListener("click", () => openLogModal()); elements.deviceForm.addEventListener("submit", handleDeviceSave); elements.logForm.addEventListener("submit", handleLogSave); elements.closeDeviceModalButton.addEventListener("click", closeActiveModal); elements.cancelDeviceModalButton.addEventListener("click", closeActiveModal); elements.closeLogModalButton.addEventListener("click", closeActiveModal); elements.cancelLogModalButton.addEventListener("click", closeActiveModal); elements.openDeviceFromLogButton.addEventListener("click", handleOpenDeviceFromLog); elements.logElevatorId.addEventListener("change", handleLogElevatorChange); elements.logInspectionDate.addEventListener("change", syncLogNextInspectionDate); elements.logInspectionIntervalDays.addEventListener("input", syncLogNextInspectionDate); elements.deviceList.addEventListener("click", handleDeviceListClick); elements.deviceModal.addEventListener("click", handleModalBackdropClick); elements.logModal.addEventListener("click", handleModalBackdropClick); window.addEventListener("keydown", handleWindowKeydown); window.addEventListener("pageshow", handlePageShow); } async function bootPage() { setToday(elements.logInspectionDate); syncLogNextInspectionDate(); renderDevices(); renderDashboard(); try { const response = await apiFetch("/api/me"); state.user = response.user; fillUserHeader(); elements.deviceRouteName.value = state.user?.routeName || ""; await Promise.allSettled([loadDashboard(), loadDevices()]); } catch (error) { if (error.status === 401) { window.location.href = "/bejelentkezes"; return; } elements.welcomeText.textContent = "Betöltési hiba"; elements.routeText.textContent = "A munkamenet ellenőrzése nem sikerült. Frissítsd az oldalt vagy jelentkezz be újra."; setFeedback(elements.logMessage, error.message, "error"); } } function fillUserHeader() { const name = state.user?.fullName || "Felhasználó"; const route = state.user?.routeName || "Nincs megadva"; elements.welcomeText.textContent = "Kezelőfelület"; elements.routeText.textContent = `${name} · Technikusi útvonal: ${route}`; } async function loadDashboard() { try { const response = await apiFetch("/api/dashboard"); state.dashboardElevators = response.elevators || []; renderDashboard(); } catch (error) { state.dashboardElevators = []; renderDashboard(); setFeedback(elements.logMessage, error.message, "error"); } } async function loadDevices() { try { const response = await apiFetch("/api/elevators"); state.devices = response.elevators || []; renderDevices(); renderLogElevatorOptions(state.selectedLogElevatorId); } catch (error) { state.devices = []; renderDevices(); renderLogElevatorOptions(null); setFeedback(elements.deviceMessage, error.message, "error"); } } function renderDashboard() { if (!state.dashboardElevators.length) { elements.dashboardCards.innerHTML = `

Nincs közelgő határidő

A következő 7 napban esedékes ellenőrzések itt jelennek meg.

`; clearCountdownTimer(); return; } elements.dashboardCards.innerHTML = state.dashboardElevators .map((elevator) => { const countdown = calculateCountdown(elevator.nextInspectionDate); const statusClass = countdown.expired ? "danger-chip" : countdown.warning ? "warning-chip" : "success-chip"; return `
${countdown.label} ${escapeHtml(elevator.serialNumber)}

${escapeHtml(elevator.name)}

${escapeHtml(elevator.location)}

Legutóbbi állapot: ${humanizeStatus(elevator.lastStatus)}

${countdown.text}

Következő ellenőrzés: ${formatDate(elevator.nextInspectionDate)}

`; }) .join(""); clearCountdownTimer(); state.countdownIntervalId = window.setInterval(updateCountdownBoxes, 1000); } function renderDevices() { if (!state.devices.length) { elements.deviceList.innerHTML = `

Még nincs rögzített eszköz

Hozz létre egy új eszközt, majd adj hozzá naplóbejegyzéseket az állapot frissítéséhez.

`; elements.openLogModalButton.disabled = true; return; } elements.openLogModalButton.disabled = false; elements.deviceList.innerHTML = state.devices .map((device) => `
${humanizeStatus(device.lastStatus)} ${escapeHtml(device.serialNumber)}

${escapeHtml(device.name)}

Helyszín: ${escapeHtml(device.location)}

Útvonal: ${escapeHtml(device.routeName)}

Utolsó ellenőrzés: ${formatDate(device.lastInspectionDate)}

Következő ellenőrzés: ${formatDate(device.nextInspectionDate)}

Ciklus: ${escapeHtml(device.inspectionIntervalDays)} nap

`) .join(""); } function renderLogElevatorOptions(preferredElevatorId) { if (!state.devices.length) { state.selectedLogElevatorId = null; elements.logElevatorId.innerHTML = ``; elements.logElevatorSummary.textContent = "A naplózáshoz előbb egy eszközt kell létrehozni."; return; } const fallbackId = preferredElevatorId || state.selectedLogElevatorId || state.devices[0].id; const resolvedId = state.devices.some((device) => device.id === Number(fallbackId)) ? Number(fallbackId) : state.devices[0].id; state.selectedLogElevatorId = resolvedId; elements.logElevatorId.innerHTML = state.devices .map( (device) => ` ` ) .join(""); renderSelectedElevatorSummary(resolvedId); } function renderSelectedElevatorSummary(elevatorId) { const device = state.devices.find((entry) => entry.id === Number(elevatorId)); if (!device) { elements.logElevatorSummary.textContent = "Válassz ki egy eszközt a naplóbejegyzéshez."; return; } elements.logElevatorSummary.innerHTML = ` ${escapeHtml(device.name)}
Gyári szám: ${escapeHtml(device.serialNumber)}
Helyszín: ${escapeHtml(device.location)}
Utolsó állapot: ${humanizeStatus(device.lastStatus)}
Következő ellenőrzés: ${formatDate(device.nextInspectionDate)} `; } function updateCountdownBoxes() { document.querySelectorAll(".countdown-box").forEach((box) => { const countdown = calculateCountdown(box.dataset.nextDate); box.textContent = countdown.text; }); } function clearCountdownTimer() { if (state.countdownIntervalId) { window.clearInterval(state.countdownIntervalId); state.countdownIntervalId = null; } } function handlePageShow() { if (state.user) { Promise.allSettled([loadDashboard(), loadDevices()]); } } function handleDeviceListClick(event) { const button = event.target.closest("button[data-action]"); if (!button) { return; } const elevatorId = Number(button.dataset.elevatorId); if (button.dataset.action === "log") { openLogModal(elevatorId); return; } if (button.dataset.action === "edit") { openDeviceModal(elevatorId); } } function handleLogElevatorChange() { const elevatorId = Number(elements.logElevatorId.value); state.selectedLogElevatorId = Number.isInteger(elevatorId) && elevatorId > 0 ? elevatorId : null; renderSelectedElevatorSummary(state.selectedLogElevatorId); const device = getSelectedElevator(); if (device) { elements.logInspectionIntervalDays.value = device.inspectionIntervalDays || 30; syncLogNextInspectionDate(); } } function handleModalBackdropClick(event) { if (event.target === event.currentTarget) { closeActiveModal(); } } function handleWindowKeydown(event) { if (event.key === "Escape" && state.activeModal) { closeActiveModal(); } } function handleOpenDeviceFromLog() { closeActiveModal(); openDeviceModal(null, true); } async function handleDeviceSave(event) { event.preventDefault(); const elevatorId = Number(elements.deviceEditId.value || 0); const payload = { name: elements.deviceName.value, serialNumber: elements.deviceSerialNumber.value, location: elements.deviceLocation.value, routeName: elements.deviceRouteName.value, status: elements.deviceStatus.value, inspectionIntervalDays: elements.deviceInspectionIntervalDays.value, nextInspectionDate: elements.deviceNextInspectionDate.value || "", lastInspectionDate: elements.deviceForm.dataset.lastInspectionDate || "" }; try { const response = await apiFetch(elevatorId ? `/api/elevators/${elevatorId}` : "/api/elevators", { method: elevatorId ? "PUT" : "POST", body: JSON.stringify(payload) }); setFeedback(elements.deviceMessage, response.message, "success"); await Promise.allSettled([loadDevices(), loadDashboard()]); closeActiveModal(); if (elements.deviceForm.dataset.returnToLog === "true" && response.elevator?.id) { openLogModal(response.elevator.id); } } catch (error) { setFeedback(elements.deviceMessage, error.message, "error"); } } async function handleLogSave(event) { event.preventDefault(); const formData = new FormData(elements.logForm); try { const response = await apiFetch("/api/logs", { method: "POST", body: formData, isFormData: true }); setFeedback(elements.logMessage, response.message, "success"); await Promise.allSettled([loadDashboard(), loadDevices()]); closeActiveModal(); } catch (error) { setFeedback(elements.logMessage, error.message, "error"); } } async function logout() { try { const response = await apiFetch("/api/logout", { method: "POST" }); window.location.href = response.redirectPath; } catch (_error) { window.location.href = "/bejelentkezes"; } } function openDeviceModal(elevatorId = null, returnToLog = false) { const device = state.devices.find((entry) => entry.id === Number(elevatorId)); elements.deviceForm.reset(); elements.deviceForm.dataset.returnToLog = returnToLog ? "true" : "false"; elements.deviceForm.dataset.lastInspectionDate = normalizeDateInput(device?.lastInspectionDate); elements.deviceEditId.value = device?.id || ""; elements.deviceRouteName.value = state.user?.routeName || ""; elements.deviceInspectionIntervalDays.value = device?.inspectionIntervalDays || 30; elements.deviceStatus.value = device?.lastStatus || "rendben"; elements.deviceNextInspectionDate.value = normalizeDateInput(device?.nextInspectionDate); elements.deviceName.value = device?.name || ""; elements.deviceSerialNumber.value = device?.serialNumber || ""; elements.deviceLocation.value = device?.location || ""; elements.deviceModalTitle.textContent = device ? "Eszköz szerkesztése" : "Új eszköz"; elements.deviceModalSubtitle.textContent = device ? "Az eszköz alapadatainak frissítése. Az állapotot naplóbejegyzéssel is módosíthatod." : "Új lift vagy berendezés rögzítése a technikusi útvonalhoz."; setFeedback(elements.deviceMessage, "", ""); showModal(elements.deviceModal); } function openLogModal(elevatorId = null) { if (!state.devices.length) { openDeviceModal(); setFeedback(elements.deviceMessage, "A naplózáshoz előbb létre kell hoznod egy eszközt.", "error"); return; } elements.logForm.reset(); setFeedback(elements.logMessage, "", ""); setToday(elements.logInspectionDate); elements.logStatus.value = "rendben"; renderLogElevatorOptions(elevatorId); elements.logElevatorId.value = String(state.selectedLogElevatorId || ""); handleLogElevatorChange(); showModal(elements.logModal); } function showModal(modalElement) { closeActiveModal(); state.activeModal = modalElement; modalElement.classList.remove("hidden"); document.body.classList.add("modal-open"); } function closeActiveModal() { if (!state.activeModal) { return; } state.activeModal.classList.add("hidden"); state.activeModal = null; document.body.classList.remove("modal-open"); } function getSelectedElevator() { return state.devices.find((device) => device.id === Number(state.selectedLogElevatorId)) || null; } async function apiFetch(url, options = {}) { const headers = options.isFormData ? {} : { "Content-Type": "application/json" }; const response = await fetch(url, { method: options.method || "GET", credentials: "include", headers, body: options.body }); const contentType = response.headers.get("content-type") || ""; const data = contentType.includes("application/json") ? await response.json() : { message: "A szerver nem JSON választ adott vissza." }; if (!response.ok) { const error = new Error(data.message || "A kérés nem sikerült."); error.status = response.status; throw error; } return data; } function syncLogNextInspectionDate() { const inspectionDate = elements.logInspectionDate.value; const intervalDays = Number(elements.logInspectionIntervalDays.value || 30); if (!inspectionDate) { return; } const baseDate = new Date(`${inspectionDate}T00:00:00`); baseDate.setDate(baseDate.getDate() + intervalDays); elements.logNextInspectionDate.value = baseDate.toISOString().slice(0, 10); } function setToday(element) { element.value = new Date().toISOString().slice(0, 10); } function calculateCountdown(nextInspectionDate) { const normalizedDate = normalizeDateInput(nextInspectionDate); if (!normalizedDate) { return { expired: false, warning: false, label: "Nincs dátum", text: "Nincs megadott határidő" }; } const targetTime = new Date(`${normalizedDate}T23:59:59`).getTime(); const diff = targetTime - Date.now(); const expired = diff < 0; const absoluteDiff = Math.abs(diff); const totalHours = Math.floor(absoluteDiff / (1000 * 60 * 60)); const days = Math.floor(totalHours / 24); const hours = totalHours % 24; const warning = !expired && days <= 7; const label = expired ? "Lejárt" : warning ? "Hamarosan esedékes" : "Rendben"; const prefix = expired ? "Túllépés" : "Hátralévő idő"; return { expired, warning, label, text: `${prefix}: ${days} nap ${hours} óra` }; } function humanizeStatus(status) { return status === "rendben" ? "Rendben" : status === "figyelmeztetes" ? "Figyelmeztetés" : "Lejárt"; } function statusClassName(status) { return status === "rendben" ? "success-chip" : status === "figyelmeztetes" ? "warning-chip" : "danger-chip"; } function formatDate(value) { const normalizedDate = normalizeDateInput(value); return normalizedDate ? new Date(`${normalizedDate}T00:00:00`).toLocaleDateString("hu-HU") : "Nincs dátum"; } function normalizeDateInput(value) { if (!value) { return ""; } if (typeof value === "string") { return value.includes("T") ? value.slice(0, 10) : value; } const parsedDate = new Date(value); return Number.isNaN(parsedDate.getTime()) ? "" : parsedDate.toISOString().slice(0, 10); } function setFeedback(element, message, type) { element.textContent = message; element.className = `feedback-text ${type ? `feedback-${type}` : ""}`; } function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); }