262 lines
7.1 KiB
JavaScript
262 lines
7.1 KiB
JavaScript
const state = {
|
|
user: null,
|
|
logs: [],
|
|
logsLoaded: false,
|
|
initialLogRetryCount: 0,
|
|
searchActivated: false
|
|
};
|
|
|
|
const elements = {
|
|
welcomeText: document.getElementById("welcomeText"),
|
|
routeText: document.getElementById("routeText"),
|
|
logoutButton: document.getElementById("logoutButton"),
|
|
entriesList: document.getElementById("entriesList"),
|
|
searchInput: document.getElementById("searchInput"),
|
|
sortSelect: document.getElementById("sortSelect")
|
|
};
|
|
|
|
bindEvents();
|
|
bootPage();
|
|
|
|
function bindEvents() {
|
|
elements.logoutButton.addEventListener("click", logout);
|
|
elements.searchInput.addEventListener("input", handleSearchInput);
|
|
elements.sortSelect.addEventListener("change", loadLogs);
|
|
window.addEventListener("pageshow", handlePageShow);
|
|
}
|
|
|
|
async function bootPage() {
|
|
state.logsLoaded = false;
|
|
state.initialLogRetryCount = 0;
|
|
state.searchActivated = false;
|
|
elements.searchInput.value = "";
|
|
renderLogs();
|
|
|
|
try {
|
|
const response = await apiFetch("/api/me");
|
|
state.user = response.user;
|
|
fillUserHeader();
|
|
await loadLogs(true);
|
|
} catch (error) {
|
|
if (error.status === 401) {
|
|
window.location.href = "/bejelentkezes";
|
|
return;
|
|
}
|
|
|
|
elements.welcomeText.textContent = "Betöltési hiba";
|
|
elements.routeText.textContent = "A napló nem tölthető be. Frissítsd az oldalt vagy jelentkezz be újra.";
|
|
renderError(error.message);
|
|
}
|
|
}
|
|
|
|
function fillUserHeader() {
|
|
const name = state.user?.fullName || "Felhasználó";
|
|
const route = state.user?.routeName || "Nincs megadva";
|
|
elements.welcomeText.textContent = "Napló";
|
|
elements.routeText.textContent = `${name} · Technikusi útvonal: ${route}`;
|
|
}
|
|
|
|
async function loadLogs(isInitialLoad = false) {
|
|
try {
|
|
const sortBy = elements.sortSelect.value || "inspectionDateDesc";
|
|
const response = await apiFetch(`/api/logs?sortBy=${encodeURIComponent(sortBy)}`);
|
|
|
|
state.logs = Array.isArray(response.logs)
|
|
? response.logs.map((log) => ({
|
|
...log,
|
|
files: Array.isArray(log.files) ? log.files : []
|
|
}))
|
|
: [];
|
|
|
|
state.logsLoaded = true;
|
|
renderLogs();
|
|
|
|
if (isInitialLoad && !state.logs.length) {
|
|
scheduleInitialLogRetry();
|
|
}
|
|
} catch (error) {
|
|
state.logs = [];
|
|
state.logsLoaded = true;
|
|
renderError(error.message);
|
|
|
|
if (isInitialLoad) {
|
|
scheduleInitialLogRetry();
|
|
}
|
|
}
|
|
}
|
|
|
|
function renderLogs() {
|
|
if (!state.logsLoaded) {
|
|
elements.entriesList.innerHTML = `
|
|
<article class="entry-card empty-card">
|
|
<h3>Betöltés</h3>
|
|
<p>A bejegyzések betöltése folyamatban van.</p>
|
|
</article>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
const query = state.searchActivated ? elements.searchInput.value.trim().toLowerCase() : "";
|
|
const visibleLogs = state.logs.filter((log) => searchableLog(log).includes(query));
|
|
|
|
if (!visibleLogs.length) {
|
|
elements.entriesList.innerHTML = `
|
|
<article class="entry-card empty-card">
|
|
<h3>Nincs találat</h3>
|
|
<p>Módosítsd a keresést, vagy hozz létre új ellenőrzési bejegyzést az alkalmazás oldalon.</p>
|
|
</article>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
elements.entriesList.innerHTML = visibleLogs
|
|
.map((log) => {
|
|
const fileMarkup = log.files.length
|
|
? log.files
|
|
.map(
|
|
(file) => `
|
|
<a class="file-link" href="${file.downloadPath}">
|
|
${escapeHtml(file.originalName)}
|
|
</a>
|
|
`
|
|
)
|
|
.join("")
|
|
: `<span class="muted-text">Nincs PDF csatolmány</span>`;
|
|
|
|
return `
|
|
<article class="entry-card">
|
|
<div class="card-topline">
|
|
<span class="status-chip ${statusClassName(log.status)}">${humanizeStatus(log.status)}</span>
|
|
<span class="muted-text">${formatDate(log.inspectionDate)}</span>
|
|
</div>
|
|
<h3>${escapeHtml(log.elevatorName)}</h3>
|
|
<p><strong>Gyári szám:</strong> ${escapeHtml(log.serialNumber)}</p>
|
|
<p><strong>Helyszín:</strong> ${escapeHtml(log.location)}</p>
|
|
<p><strong>Útvonal:</strong> ${escapeHtml(log.routeName)}</p>
|
|
<p><strong>Technikus:</strong> ${escapeHtml(log.technicianName)}</p>
|
|
<p><strong>Következő ellenőrzés:</strong> ${formatDate(log.nextInspectionDate)}</p>
|
|
<p><strong>Megjegyzés:</strong> ${escapeHtml(log.notes || "Nincs megjegyzés.")}</p>
|
|
<div class="file-list">${fileMarkup}</div>
|
|
</article>
|
|
`;
|
|
})
|
|
.join("");
|
|
}
|
|
|
|
function renderError(message) {
|
|
elements.entriesList.innerHTML = `
|
|
<article class="entry-card empty-card">
|
|
<h3>Hiba</h3>
|
|
<p>${escapeHtml(message)}</p>
|
|
</article>
|
|
`;
|
|
}
|
|
|
|
function scheduleInitialLogRetry() {
|
|
if (state.initialLogRetryCount >= 3 || state.logs.length) {
|
|
return;
|
|
}
|
|
|
|
state.initialLogRetryCount += 1;
|
|
|
|
window.setTimeout(() => {
|
|
state.logsLoaded = false;
|
|
renderLogs();
|
|
loadLogs(false);
|
|
}, 500 * state.initialLogRetryCount);
|
|
}
|
|
|
|
function handleSearchInput() {
|
|
state.searchActivated = true;
|
|
renderLogs();
|
|
}
|
|
|
|
function handlePageShow() {
|
|
if (state.user) {
|
|
state.logsLoaded = false;
|
|
renderLogs();
|
|
loadLogs();
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
try {
|
|
const response = await apiFetch("/api/logout", { method: "POST" });
|
|
window.location.href = response.redirectPath;
|
|
} catch (_error) {
|
|
window.location.href = "/bejelentkezes";
|
|
}
|
|
}
|
|
|
|
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 searchableLog(log) {
|
|
return [
|
|
log.elevatorName,
|
|
log.serialNumber,
|
|
log.location,
|
|
log.routeName,
|
|
log.technicianName,
|
|
log.status,
|
|
log.notes
|
|
]
|
|
.join(" ")
|
|
.toLowerCase();
|
|
}
|
|
|
|
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 escapeHtml(value) {
|
|
return String(value)
|
|
.replaceAll("&", "&")
|
|
.replaceAll("<", "<")
|
|
.replaceAll(">", ">")
|
|
.replaceAll('"', """)
|
|
.replaceAll("'", "'");
|
|
}
|