// ============================================================================ // Простая администраторская панель // — Добавление товаров и правка цен. Изменения хранятся в браузере // (localStorage) и сразу попадают в конструктор, пакеты, КП и заявки. // — Доступ по коду (по умолчанию ниже). Код можно сменить в ADMIN_PASS. // ============================================================================ const ADMIN_PASS = "razum2025"; // ← код доступа в админ-панель // Доступ к админке только по секретной ссылке /changes (замаскировано — не /admin). // Работает как https://razumdom38.ru/changes (на боевом домене), так и через // #changes / ?changes в превью и на статике. function rdAdminAllowed() { try { const path = location.pathname.replace(/\/+$/, "").toLowerCase(); const hash = (location.hash || "").toLowerCase(); const search = (location.search || "").toLowerCase(); return path.endsWith("/changes") || path.endsWith("/changes.html") || hash.includes("changes") || search.includes("changes"); } catch (e) { return false; } } const AdminPanel = () => { const RD = window.RD; const allowed = rdAdminAllowed(); const [open, setOpen] = React.useState(allowed); // по ссылке /changes — открыта сразу const [unlocked, setUnlocked] = React.useState(() => { try { return sessionStorage.getItem("rd_admin_ok") === "1"; } catch (e) { return false; } }); const [pass, setPass] = React.useState(""); const [passErr, setPassErr] = React.useState(false); const [, force] = React.useReducer((x) => x + 1, 0); // живой список при правках React.useEffect(() => RD.onCatalogChange(force), []); const tryUnlock = (e) => { e.preventDefault(); if (pass.trim() === ADMIN_PASS) { setUnlocked(true); setPassErr(false); try { sessionStorage.setItem("rd_admin_ok", "1"); } catch (e2) {} } else { setPassErr(true); } }; const groups = RD.groupOrder(); const sensors = RD.SENSORS; // ——— форма добавления ——— const [f, setF] = React.useState({ name: "", desc: "", group: groups[0] || "", newGroup: "", price: "", icon: "bulb" }); const upd = (k, v) => setF((s) => ({ ...s, [k]: v })); const addNow = () => { const grp = f.group === "__new__" ? f.newGroup.trim() : f.group; if (!f.name.trim() || !grp || !(Number(f.price) > 0)) return; RD.addProduct({ name: f.name, desc: f.desc, group: grp, price: f.price, icon: f.icon }); setF({ name: "", desc: "", group: grp, newGroup: "", price: "", icon: "bulb" }); }; // Для обычных посетителей админка полностью скрыта (нет даже кнопки). // Доступ — только по секретной ссылке /changes. if (!allowed) return null; return ( <> {/* Триггер */} {!open ? null : (
setOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 9001, background: "rgba(12,18,33,0.55)", backdropFilter: "blur(4px)", display: "grid", placeItems: "center", padding: 20, }}>
e.stopPropagation()} style={{ width: "min(840px, 96vw)", maxHeight: "90vh", display: "flex", flexDirection: "column", background: "var(--surface)", border: "1px solid var(--line-2)", borderRadius: 22, overflow: "hidden", boxShadow: "var(--shadow-lg)", }}> {/* header */}
Админ-панель · каталог
Товары и цены · сохраняется в этом браузере
{!unlocked ? (
Введите код доступа
{ setPass(e.target.value); setPassErr(false); }} placeholder="Код" style={{ ...adminInput, width: 220, textAlign: "center" }} /> {passErr &&
Неверный код
}
Код можно сменить в коде (ADMIN_PASS)
) : (
{/* Добавление товара */}
Добавить товар
upd("name", e.target.value)} style={adminInput} /> upd("desc", e.target.value)} style={adminInput} /> upd("price", e.target.value)} style={adminInput} />
{f.group === "__new__" && ( upd("newGroup", e.target.value)} style={adminInput} /> )}
Новые группы появляются в конструкторе свёрнутыми.
{/* Список товаров */}
{groups.map((g) => { const rows = sensors.filter((s) => s.group === g); if (rows.length === 0) return null; return (
{g} {!RD.MAIN_GROUPS.includes(g) && доп.}
{rows.map((s) => (
{ const v = e.target.value.trim(); if (v && v !== s.name) RD.updateProduct(s.id, { name: v }); }} style={{ ...adminInput, padding: "9px 11px" }} />
{ const v = Math.max(0, Math.round(Number(e.target.value) || 0)); if (v !== s.price) RD.updateProduct(s.id, { price: v }); }} style={{ ...adminInput, padding: "9px 11px", textAlign: "right" }} />
))}
); })}
{/* Footer actions */}
Изменения сохраняются автоматически
)}
)} ); }; const adminInput = { width: "100%", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 10, padding: "11px 13px", color: "var(--ink-2)", fontSize: 14, fontFamily: "var(--font)", outline: "none", }; window.AdminPanel = AdminPanel;