// MDM SORP — App shell with sidebar + topbar + router. const { useState, useEffect, useMemo, useRef, useCallback, createContext, useContext } = React; function AppShell() { const { t, lang, setLang } = useI18n(); const [route, setRoute] = useState(() => { const h = location.hash.slice(1).split("/"); return { name: h[0] || "home", params: h[1] ? { id: decodeURIComponent(h[1]) } : {} }; }); const [cpOpen, setCpOpen] = useState(false); const [collapsed, setCollapsed] = useState(false); const navigate = useCallback((name, params = {}) => { setRoute({ name, params }); const hash = params.id ? `${name}/${encodeURIComponent(params.id)}` : name; history.replaceState(null, "", "#" + hash); }, []); // listen for external hash changes (e.g. design canvas iframes) useEffect(() => { const h = () => { const seg = location.hash.slice(1).split("/"); setRoute({ name: seg[0] || "home", params: seg[1] ? { id: decodeURIComponent(seg[1]) } : {} }); }; window.addEventListener("hashchange", h); return () => window.removeEventListener("hashchange", h); }, []); // hotkey: cmd-K useEffect(() => { const h = (e) => { if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); setCpOpen(true); } }; window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); }, []); const nav = [ { sect: "nav_work", items: [ { id: "home", icon: "home" }, ]}, { sect: "nav_data", items: [ { id: "deals", icon: "deals", count: 38 }, { id: "parties", icon: "parties", count: 2184 }, { id: "nomenclature", icon: "box", count: 1248 }, { id: "documents", icon: "docs", count: 1248 }, ]}, { sect: "nav_intake", items: [ { id: "import", icon: "upload", count: 2 }, ]}, { sect: "nav_queues", items: [ { id: "enrichment", icon: "queue", count: 13, hot: true }, { id: "dedup", icon: "merge", count: 4 }, { id: "normalize", icon: "list", count: 137 }, ]}, { sect: "nav_enrich", items: [ { id: "sourcing", icon: "lightbulb" }, ]}, { sect: "nav_admin", items: [ { id: "dashboards", icon: "bar" }, { id: "schema", icon: "book" }, { id: "integrations", icon: "plug" }, { id: "audit", icon: "history" }, ]}, ]; const crumbs = computeCrumbs(route, t); const fullBleed = route.name === "form"; if (fullBleed) { return ; } return (
{/* Sidebar */} {/* Topbar */}
{crumbs.map((c, i) => ( {i > 0 && /} {c.onClick ? {c.label} : {c.label}} ))}
{ e.target.blur(); setCpOpen(true); }}/> {t("searchHk")}
{/* Main */}
setCpOpen(false)} navigate={(name, params) => navigate(name, params || {})}/>
); } function RouteView({ route, navigate }) { switch (route.name) { case "home": return ; case "deals": return ; case "deal": return ; case "parties": return ; case "party": return ; case "nomenclature": return ; case "documents": return ; case "sourcing": return ; case "normalize": return ; case "capability": return ; /* legacy hash → sourcing */ case "enrichment": return ; case "dedup": return ; case "import": return ; case "sanctions": return ; case "dashboards": return ; case "schema": return ; case "smart_table": return ; case "relationship_map": return ; case "integrations": return ; case "audit": return ; case "search": return ; default: return ; } } function ComingSoon({ title, desc }) { return (

{title}

{desc}

Прототип покрывает основные операторские сценарии. Этот экран — заглушка для полного списка модулей.
); } function computeCrumbs(route, t) { const home = { label: t("home"), onClick: () => location.hash = "#home" }; switch (route.name) { case "home": return [{ label: t("home") }]; case "deal": return [home, { label: t("deals"), onClick: () => location.hash = "#deals" }, { label: route.params.id || "—" }]; case "party": return [home, { label: t("parties"), onClick: () => location.hash = "#parties" }, { label: route.params.id || "—" }]; default: return [home, { label: t(route.name) || route.name }]; } } window.AppShell = AppShell;