/* Shell: сайдбар (сворачиваемый) + топбар + контент активного модуля. Реестр экранов RD.modules — добавляешь модуль → появляется пункт меню. */ window.RD = window.RD || { core: {}, modules: {} }; window.RD.modules.Shell = function Shell() { var store = RD.core.useStore(); var h = React.createElement; var openRef = React.useState(false); var sbOpen = openRef[0], setSbOpen = openRef[1]; // C1: экран входа — права назначаются по выбранному пользователю (из config.users) if (!store.state.user) { var users = (store.config && store.config.users) || [ { name: 'Анна Котова', role: 'Сметчик', av: 'АК', c: '#3357e6' }, { name: 'Дмитрий Морозов', role: 'Прораб', av: 'ДМ', c: '#c2790a' }, { name: 'Игорь Лебедев', role: 'Снабженец', av: 'ИЛ', c: '#0a7ea4' }, { name: 'Ольга Зайцева', role: 'Администратор', av: 'ОЗ', c: '#2f8f5b' }, { name: 'Гость (заказчик)', role: 'Наблюдатель', av: 'ГЗ', c: '#7a5af0' } ]; var br = store.raw.brand || {}, ob = store.raw.object || {}; return h('div', { style: { position: 'fixed', inset: 0, background: 'linear-gradient(135deg,var(--rd-surface-2),var(--rd-surface-3))', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 90 } }, h('div', { style: { width: 380, maxWidth: '92vw', background: 'var(--rd-surface)', borderRadius: 16, boxShadow: '0 24px 70px rgba(0,0,0,.3)', overflow: 'hidden', border: '1px solid var(--rd-border)' } }, h('div', { style: { padding: '20px 22px 16px', borderBottom: '1px solid var(--rd-border)', display: 'flex', alignItems: 'center', gap: 11 } }, h('div', { style: { width: 34, height: 34, borderRadius: 9, background: 'linear-gradient(135deg,#3357e6,#6a78f0)', color: '#fff', fontWeight: 700, fontSize: 16, display: 'flex', alignItems: 'center', justifyContent: 'center' } }, br.logoLetter || 'R'), h('div', null, h('div', { style: { fontSize: 15, fontWeight: 600, color: 'var(--rd-text)' } }, br.appName || 'Renovation Director'), h('div', { style: { fontSize: 11, color: 'var(--rd-text-muted)' } }, (br.module || 'AntHill') + ' · вход в проект ' + (ob.complex || '')))), h('div', { style: { padding: 18 } }, h('div', { style: { fontSize: 11, fontWeight: 600, letterSpacing: .4, color: 'var(--rd-text-muted)', marginBottom: 11 } }, 'ВОЙТИ КАК · права определяются логином'), users.map(function (u, i) { return h('button', { key: i, onClick: function () { store.login(u); }, style: { display: 'flex', alignItems: 'center', gap: 11, width: '100%', padding: '10px 12px', border: '1px solid var(--rd-border)', borderRadius: 10, background: 'var(--rd-surface)', cursor: 'pointer', marginBottom: 8, fontFamily: 'inherit', textAlign: 'left' } }, h('span', { style: { width: 34, height: 34, borderRadius: '50%', background: u.c || 'var(--rd-accent)', color: '#fff', fontSize: 12, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 'none' } }, u.av || u.name.slice(0, 2)), h('span', { style: { flex: 1 } }, h('span', { style: { display: 'block', fontSize: 13, fontWeight: 600, color: 'var(--rd-text)' } }, u.name), h('span', { style: { display: 'block', fontSize: 11, color: 'var(--rd-text-muted)' } }, 'роль: ' + u.role)), h('span', { style: { fontSize: 14, color: 'var(--rd-text-faint)' } }, '→')); }), h('div', { style: { fontSize: 10.5, color: 'var(--rd-text-faint)', textAlign: 'center', marginTop: 4 } }, 'демо-вход · в проде — SSO / корпоративный логин')))); } var NAV = [ { id: 'overview', label: 'Обзор', mod: 'Overview' }, { id: 'alerts', label: 'Алерты', mod: 'Alerts' }, { id: 'kotlovans', label: 'Котлованы', mod: 'Kotlovans' }, { id: 'estimate', label: 'Смета', mod: 'Estimate' }, { id: 'schedule', label: 'График работ', mod: 'Schedule' }, { id: 'payments-tab', label: 'Платежи', mod: 'PaymentsTab' }, { id: 'payments', label: 'Оплаты и поставки', mod: 'Payments' }, { id: 'materials', label: 'Материалы', mod: 'Materials' }, { id: 'pricing', label: 'Прайсы и тендер', mod: 'Pricing' }, { id: 'budget', label: 'Бюджет / ПНЛ', mod: 'Budget' }, { id: 'team', label: 'Команда', mod: 'TeamFull' }, { id: 'assistant', label: 'Прораб-ИИ', mod: 'Assistant' }, { id: 'history', label: 'История изменений', mod: 'ChangeHistory' } ].filter(function (n) { return store.perms.navHidden.indexOf(n.id) < 0; }); var brand = store.raw.brand || {}, obj = store.raw.object || {}; var ActiveMod = RD.modules[(NAV.find(function (n) { return n.id === store.state.view; }) || {}).mod] || RD.modules.Placeholder; function navItem(n) { return h('button', { key: n.id, className: 'rd-nav-btn' + (store.state.view === n.id ? ' active' : ''), onClick: function () { store.setView(n.id); } }, n.label); } return h('div', { className: 'rd-app' }, // сайдбар sbOpen ? h('aside', { className: 'rd-sidebar-full' }, h('div', { style: { padding: '15px 15px 13px', borderBottom: '1px solid var(--rd-border)', display: 'flex', alignItems: 'center', gap: 10 } }, h('div', { style: { width: 30, height: 30, borderRadius: 8, background: 'linear-gradient(135deg,#3357e6,#6a78f0)', color: '#fff', fontWeight: 700, fontSize: 15, display: 'flex', alignItems: 'center', justifyContent: 'center' } }, brand.logoLetter || 'R'), h('div', { style: { lineHeight: 1.1 } }, h('div', { style: { fontWeight: 600, fontSize: 13.5 } }, brand.appName || 'Renovation Director'), h('div', { style: { fontSize: 11, color: 'var(--rd-text-muted)' } }, brand.module || 'AntHill')), h('div', { style: { flex: 1 } }), h('button', { onClick: function () { setSbOpen(false); }, style: { border: 'none', background: 'none', cursor: 'pointer', color: 'var(--rd-text-muted)', fontSize: 18 } }, '‹') ), h('div', { style: { padding: '6px 9px', fontSize: 10, fontWeight: 600, letterSpacing: .6, color: 'var(--rd-text-muted)' } }, 'ПРОЕКТ · ' + (obj.complexUpper || obj.complex || '')), h('nav', { style: { padding: '4px 8px', flex: 1 } }, NAV.map(navItem)) ) : h('aside', { className: 'rd-sidebar-mini' }, h('div', { style: { width: 30, height: 30, borderRadius: 8, background: 'linear-gradient(135deg,#3357e6,#6a78f0)', color: '#fff', fontWeight: 700, fontSize: 15, display: 'flex', alignItems: 'center', justifyContent: 'center' } }, brand.logoLetter || 'R'), h('button', { onClick: function () { setSbOpen(true); }, style: { marginTop: 8, border: '1px solid var(--rd-border)', background: 'var(--rd-surface)', cursor: 'pointer', color: 'var(--rd-text-2)', fontSize: 15, width: 32, height: 28, borderRadius: 7 } }, '›'), NAV.map(function (n) { return h('button', { key: n.id, title: n.label, onClick: function () { store.setView(n.id); }, style: { width: 36, height: 36, border: 'none', borderRadius: 8, cursor: 'pointer', background: store.state.view === n.id ? 'var(--rd-border)' : 'transparent', color: store.state.view === n.id ? 'var(--rd-text)' : 'var(--rd-text-2)', fontSize: 13, fontWeight: 700 } }, n.label[0]); }) ), // main: топбар + активный модуль h('div', { style: { flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 } }, h('header', { className: 'rd-topbar' }, h('div', { style: { display: 'flex', alignItems: 'center', gap: 7, fontSize: 12.5, color: 'var(--rd-text-muted)' } }, h('span', null, obj.complex || ''), h('span', { style: { color: 'var(--rd-text-faint)' } }, '/'), h('span', null, obj.unit || ''), h('span', { style: { color: 'var(--rd-text-faint)' } }, '/'), h('span', { style: { color: 'var(--rd-text)', fontWeight: 600 } }, (NAV.find(function (n) { return n.id === store.state.view; }) || {}).label || '')), h('div', { style: { flex: 1 } }), h('span', { style: { fontSize: 11.5, color: 'var(--rd-text-2)' } }, (store.state.user || '') + ' · ' + store.state.role), h('button', { onClick: function () { store.logout(); }, title: 'Сменить пользователя', style: { height: 30, padding: '0 10px', border: '1px solid var(--rd-border)', borderRadius: 8, background: 'var(--rd-surface)', color: 'var(--rd-text-2)', fontSize: 11.5, cursor: 'pointer' } }, 'Выйти'), // тема h('select', { value: store.state.theme, onChange: function (e) { store.setTheme(e.target.value); }, style: { height: 30, border: '1px solid var(--rd-border)', borderRadius: 8, background: 'var(--rd-surface)', color: 'var(--rd-text)', fontSize: 12, padding: '0 8px' } }, [['light', 'Светлая'], ['dark', 'Тёмная'], ['amber', 'Янтарная']].map(function (t) { return h('option', { key: t[0], value: t[0] }, t[1]); })) ), // Wrap module in ErrorBoundary to prevent white screen on crashes h(RD.core.ErrorBoundary, { moduleName: (NAV.find(function (n) { return n.id === store.state.view; }) || {}).label || 'Unknown' }, h(ActiveMod, null) ) ), store.toast ? h('div', { style: { position: 'fixed', bottom: 22, left: '50%', transform: 'translateX(-50%)', display: 'flex', alignItems: 'center', gap: 10, background: 'var(--rd-text)', color: 'var(--rd-surface)', fontSize: 12.5, fontWeight: 500, padding: '11px 15px', borderRadius: 11, boxShadow: '0 8px 28px rgba(0,0,0,.3)', zIndex: 80, maxWidth: 460 } }, h('span', { style: { width: 18, height: 18, borderRadius: '50%', flex: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 700, color: '#fff', background: store.toast.kind === 'ok' ? 'var(--rd-good)' : '#c2790a' } }, store.toast.kind === 'ok' ? '✓' : '⚠'), h('span', null, store.toast.text)) : null ); }; window.RD.modules.Placeholder = function () { return React.createElement('div', { style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--rd-text-muted)', fontSize: 14 } }, 'Модуль ещё не перенесён в React. Добавьте RD.modules.<Имя> по образцу Estimate.jsx'); };