174 lines
6.9 KiB
JavaScript
174 lines
6.9 KiB
JavaScript
/**
|
|
* Общий навбар операторских страниц.
|
|
* <div id="wespZootechNavMount" data-active-page="recipes"></div>
|
|
*/
|
|
(function () {
|
|
/* Один файл — одинаковый слот в светлой и тёмной теме (logo.png другой aspect ratio). */
|
|
const LOGO_LIGHT = "/static/logo2.png";
|
|
const LOGO_DARK = "/static/logo2.png";
|
|
|
|
function brandLogoHtml() {
|
|
return `<img src="${LOGO_LIGHT}" alt="Комптон" class="logo logo--light-theme">
|
|
<img src="${LOGO_DARK}" alt="" class="logo logo--dark-theme" aria-hidden="true">`;
|
|
}
|
|
|
|
const PAGES = [
|
|
{ id: "recipes", href: "/recipes", icon: "fa-utensils", label: "Рецепты" },
|
|
{ id: "lab", href: "/lab", icon: "fa-flask", label: "Lab" },
|
|
{ id: "feed_dispensers", href: "/feed_dispensers", icon: "fa-server", label: "Оборудование" },
|
|
{ id: "components", href: "/components", icon: "fa-box", label: "Компоненты" },
|
|
{ id: "reports", href: "/reports", icon: "fa-chart-bar", label: "Отчеты" },
|
|
{ id: "feed_consumption", href: "/feed_consumption", icon: "fa-chart-line", label: "Учёт кормов" },
|
|
];
|
|
|
|
function navItem(page, active, mobile) {
|
|
const cls = mobile
|
|
? `mobile-menu-item${active ? " active" : ""}`
|
|
: `menu-btn${active ? " active" : ""}`;
|
|
const tag = mobile ? "a" : "a";
|
|
if (page.id === "settings") {
|
|
return `<${tag} href="javascript:void(0)" data-action="open-settings" onclick="typeof openSettings==='function'&&openSettings(event)" class="${cls}">
|
|
<i class="fas fa-cog"></i><span>Настройки</span></${tag}>`;
|
|
}
|
|
return `<${tag} href="${page.href}" class="${cls}">
|
|
<i class="fas ${page.icon}"></i><span>${page.label}</span></${tag}>`;
|
|
}
|
|
|
|
function pagesForNav(canLab) {
|
|
if (canLab) return PAGES;
|
|
return PAGES.filter((p) => p.id !== "lab");
|
|
}
|
|
|
|
function renderNav(activeId, pages) {
|
|
const desktop = pages.map((p) => navItem(p, p.id === activeId, false)).join("");
|
|
const mobile = pages.map((p) => navItem(p, p.id === activeId, true)).join("");
|
|
const settingsDesktop = navItem({ id: "settings" }, false, false);
|
|
const settingsMobile = navItem({ id: "settings" }, false, true);
|
|
|
|
return `<nav class="mobile-navbar">
|
|
<div class="container">
|
|
<div class="nav-content">
|
|
<div class="nav-brand">
|
|
<a href="/recipes" class="nav-logo-link" aria-label="Рецепты">
|
|
${brandLogoHtml()}
|
|
</a>
|
|
</div>
|
|
<div class="hamburger-menu" id="hamburgerMenu" role="button" aria-label="Меню" tabindex="0">
|
|
<div class="hamburger-line"></div>
|
|
<div class="hamburger-line"></div>
|
|
<div class="hamburger-line"></div>
|
|
</div>
|
|
<div class="desktop-menu">${desktop}${settingsDesktop}</div>
|
|
<div class="user-section">
|
|
<span id="userInfo" class="user-info"><i class="fas fa-user"></i> <span id="userLogin"></span></span>
|
|
<a href="#" data-action="logout" onclick="typeof logout==='function'&&logout();return false;" class="logout-btn">
|
|
<i class="fas fa-sign-out-alt"></i><span>Выход</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mobile-menu-overlay" id="mobileMenuOverlay">
|
|
<div class="mobile-menu">
|
|
<div class="mobile-menu-header">
|
|
<div class="mobile-brand">
|
|
<a href="/recipes" class="nav-logo-link" aria-label="Рецепты">
|
|
${brandLogoHtml()}
|
|
</a>
|
|
</div>
|
|
<div class="close-menu" id="closeMenu" role="button" aria-label="Закрыть" tabindex="0">
|
|
<i class="fas fa-times"></i>
|
|
</div>
|
|
</div>
|
|
<div class="mobile-menu-items">${mobile}${settingsMobile}</div>
|
|
<div class="mobile-menu-footer">
|
|
<div class="mobile-user-info" id="mobileUserInfo"></div>
|
|
<a href="#" data-action="logout" onclick="typeof logout==='function'&&logout();return false;" class="mobile-logout-btn">
|
|
<i class="fas fa-sign-out-alt"></i><span>Выход</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>`;
|
|
}
|
|
|
|
function wireMobileMenu() {
|
|
const hamburgerMenu = document.getElementById("hamburgerMenu");
|
|
const mobileMenuOverlay = document.getElementById("mobileMenuOverlay");
|
|
const closeMenu = document.getElementById("closeMenu");
|
|
if (!hamburgerMenu || !mobileMenuOverlay) return;
|
|
if (mobileMenuOverlay.dataset.menuWired === "1") return;
|
|
mobileMenuOverlay.dataset.menuWired = "1";
|
|
|
|
if (mobileMenuOverlay.parentElement !== document.body) {
|
|
document.body.appendChild(mobileMenuOverlay);
|
|
}
|
|
|
|
const syncMobileUserInfo = () => {
|
|
const userInfo = document.getElementById("userInfo");
|
|
const mobileUserInfo = document.getElementById("mobileUserInfo");
|
|
if (userInfo && mobileUserInfo) {
|
|
mobileUserInfo.textContent = userInfo.textContent?.trim() || "";
|
|
}
|
|
};
|
|
|
|
const open = () => {
|
|
syncMobileUserInfo();
|
|
mobileMenuOverlay.classList.add("active");
|
|
hamburgerMenu.classList.add("active");
|
|
document.body.classList.add("mobile-menu-open");
|
|
};
|
|
const close = () => {
|
|
mobileMenuOverlay.classList.remove("active");
|
|
hamburgerMenu.classList.remove("active");
|
|
document.body.classList.remove("mobile-menu-open");
|
|
};
|
|
|
|
hamburgerMenu.addEventListener("click", open);
|
|
closeMenu?.addEventListener("click", close);
|
|
mobileMenuOverlay.addEventListener("click", (e) => {
|
|
if (e.target === mobileMenuOverlay) close();
|
|
});
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key === "Escape" && mobileMenuOverlay.classList.contains("active")) close();
|
|
});
|
|
document.querySelectorAll(".mobile-menu-item").forEach((item) => {
|
|
item.addEventListener("click", () => setTimeout(close, 300));
|
|
});
|
|
|
|
window.WespZootechNavCloseMobileMenu = close;
|
|
}
|
|
|
|
async function fetchCanLab() {
|
|
try {
|
|
const response = await fetch("/api/auth/check");
|
|
const data = await response.json();
|
|
if (data.status === "success" && data.authenticated) {
|
|
return Boolean(data.can_lab);
|
|
}
|
|
} catch (error) {
|
|
console.error("Ошибка проверки доступа к Lab:", error);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async function mount() {
|
|
const mountEl = document.getElementById("wespZootechNavMount");
|
|
if (!mountEl) return;
|
|
const active = mountEl.getAttribute("data-active-page") || "";
|
|
const canLab = await fetchCanLab();
|
|
window.WESP_CAN_LAB = canLab;
|
|
mountEl.outerHTML = renderNav(active, pagesForNav(canLab));
|
|
wireMobileMenu();
|
|
window.WespPageEnter?.init?.();
|
|
window.WespLabAccess?.apply?.(canLab);
|
|
}
|
|
|
|
window.WespZootechNav = { remount: mount };
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", mount);
|
|
} else {
|
|
mount();
|
|
}
|
|
})();
|