Initial commit: site monorepo with API, web, and infra.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Общий навбар операторских страниц.
|
||||
* <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;
|
||||
|
||||
const open = () => {
|
||||
mobileMenuOverlay.classList.add("active");
|
||||
hamburgerMenu.classList.add("active");
|
||||
};
|
||||
const close = () => {
|
||||
mobileMenuOverlay.classList.remove("active");
|
||||
hamburgerMenu.classList.remove("active");
|
||||
};
|
||||
|
||||
hamburgerMenu.addEventListener("click", open);
|
||||
closeMenu?.addEventListener("click", close);
|
||||
mobileMenuOverlay.addEventListener("click", (e) => {
|
||||
if (e.target === mobileMenuOverlay) 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();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user