(()=>{ 'use strict'; if(window.SunDeveloperUXV1768)return; const VERSION='17.6.8'; const RELEASE='20260908-v17-6-8-developer-console-ux'; const TAB_KEY='sunDeveloperActiveTabV22'; const ENHANCED_TABS=new Set(['companies','accounts','plans','journal']); const ASYNC_ROOTS={features:'sunDevFeatureBody',backups:'sunDevBackupBody'}; const FEATURES={orders:'Заказы',calendar:'Календарь',map:'Карта',clients:'Клиенты',catalog_view:'Каталог · просмотр',catalog_edit:'Каталог · редактирование',production:'Производство',shopping:'Закупки',stock:'Склад',routes:'Маршруты',mailings:'Рассылки',money:'Финансы',stats_basic:'Статистика',stats_advanced:'Расширенная статистика',team:'Команда',suppliers:'Поставщики',print:'Печать',settings:'Настройки',branding:'Фирменное оформление',client_offers:'Предложение клиенту',offer_templates:'Шаблоны предложения',backups:'Резервные копии',audit:'Журнал',users_manage:'Пользователи и права'}; const PLANS={basic:'Базовый',professional:'Профессиональный',full:'Полный'}; const KNOWN_DOM_RACE=/Cannot set properties of null \(setting 'className'\)|Cannot read properties of undefined \(reading 'add'\)/i; const $=id=>document.getElementById(id); const qa=(sel,root=document)=>[...root.querySelectorAll(sel)]; const esc=value=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(value??'')):String(value??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const cloud=()=>window.SunCloudV2||null; const client=()=>cloud()?.getClient?.()||null; const dev=()=>window.SunDeveloperV22||null; const fmtDate=v=>{if(!v)return'—';try{return new Date(v).toLocaleString('ru-RU',{dateStyle:'short',timeStyle:'short'})}catch(_){return String(v)}}; const fmtDay=v=>{if(!v)return'—';try{return new Date(v).toLocaleDateString('ru-RU')}catch(_){return String(v)}}; const toast=(text,type='info',ms=4500)=>{try{return window.SunEnterprise?.toast?.(text,type,ms)}catch(_){}console[type==='error'?'error':'log'](text)}; const activeTab=()=>document.querySelector('#sun-developer-console-v22 [data-dev-tab].on')?.dataset.devTab||localStorage.getItem(TAB_KEY)||'overview'; const developerVisible=()=>Boolean($('sun-developer-console-v22')?.classList?.contains('on')); async function rpc(name,args={}){const c=client();if(!c)throw new Error('Supabase не подключён.');const r=await c.rpc(name,args);if(r.error)throw r.error;return r.data} async function audit(action,workspace=null,user=null,details={}){try{await rpc('sun_dev_log_event',{p_action:action,p_workspace:workspace,p_user:user,p_details:details})}catch(_){}} function installStyles(){ if($('sunDeveloperUXV1768Style'))return; const st=document.createElement('style');st.id='sunDeveloperUXV1768Style';st.textContent=` .sun-dev-number{display:inline-flex;align-items:center;justify-content:center;min-width:34px;height:26px;padding:0 8px;border-radius:999px;background:#171815;color:#fff;font-size:11px;font-weight:900;white-space:nowrap} .sun-dev-company-title{display:flex;align-items:center;gap:8px}.sun-dev-company-title b{font-size:14px} .sun-dev-company-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:10px}.sun-dev-company-pick{border:1px solid #e0e3e7;border-radius:15px;background:#fff;padding:15px;text-align:left;cursor:pointer;box-shadow:0 6px 22px rgba(20,22,26,.035)}.sun-dev-company-pick:hover{border-color:#b99a5f;box-shadow:0 8px 28px rgba(20,22,26,.07)}.sun-dev-company-pick h3{display:flex;align-items:center;gap:8px;margin:0 0 8px;color:#252b2f}.sun-dev-company-pick p{margin:4px 0;color:#747b83;font-size:12px}.sun-dev-company-pick .count{font-size:22px;font-weight:900;color:#171815;margin-top:10px} .sun-dev-account-head{display:flex;align-items:center;gap:10px;justify-content:space-between;flex-wrap:wrap;margin-bottom:12px}.sun-dev-account-head-left{display:flex;align-items:center;gap:9px;flex-wrap:wrap}.sun-dev-account-role{display:inline-flex;padding:4px 8px;border-radius:999px;background:#f1f3f5;font-size:11px;font-weight:800}.sun-dev-account-role.admin{background:#e9f5ec;color:#22633b} .sun-dev-plan-top{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:12px}.sun-dev-plan-mini{background:#fff;border:1px solid #e2e4e9;border-radius:14px;padding:13px}.sun-dev-plan-mini h3{margin:0 0 3px}.sun-dev-plan-mini p{margin:0 0 9px;color:#777d86;font-size:11px;min-height:16px}.sun-dev-plan-limit{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px;align-items:end}.sun-dev-plan-limit input{width:100%;box-sizing:border-box;padding:8px 9px;border:1px solid #d8dbe0;border-radius:8px}.sun-dev-plan-matrix{min-width:620px}.sun-dev-plan-matrix th:not(:first-child),.sun-dev-plan-matrix td:not(:first-child){text-align:center;width:150px}.sun-dev-plan-matrix input[type=checkbox]{width:18px;height:18px;accent-color:#171815}.sun-dev-plan-feature{font-weight:750;color:#31373c} .sun-dev-journal-kpis{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:12px}.sun-dev-journal-ok{border:1px solid #cae8d5;background:#f2fbf5;border-radius:15px;padding:18px}.sun-dev-journal-ok h3{margin:0 0 5px;color:#17633a}.sun-dev-error-message{max-width:560px;white-space:normal;word-break:break-word}.sun-dev-error-count{font-size:12px;font-weight:900;padding:4px 8px;border-radius:999px;background:#f1f2f4;white-space:nowrap}.sun-dev-error-level{display:inline-flex;padding:4px 8px;border-radius:999px;font-size:10px;font-weight:900;text-transform:uppercase}.sun-dev-error-level.error{background:#fde9e9;color:#9e2828}.sun-dev-error-level.warn{background:#fff3dc;color:#855800} .sun-dev-modal-v1768{position:fixed;inset:0;z-index:100100;background:rgba(20,22,26,.54);display:grid;place-items:center;padding:18px}.sun-dev-modal-v1768-card{width:min(620px,100%);max-height:90vh;overflow:auto;background:#fff;border-radius:18px;padding:20px;box-shadow:0 24px 80px rgba(0,0,0,.28)}.sun-dev-modal-v1768-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.sun-dev-modal-v1768-head h2{margin:0}.sun-dev-modal-v1768-close{border:0;border-radius:10px;width:36px;height:36px;background:#f1f2f4;font-size:22px}.sun-dev-modal-v1768-card label{display:block;margin-top:10px}.sun-dev-modal-v1768-card input,.sun-dev-modal-v1768-card select{width:100%;box-sizing:border-box;padding:9px 10px;border:1px solid #d6d9df;border-radius:9px}.sun-dev-modal-v1768-actions{display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap;margin-top:16px} @media(max-width:900px){.sun-dev-plan-top,.sun-dev-journal-kpis{grid-template-columns:1fr}.sun-dev-plan-mini p{min-height:0}} `;document.head.appendChild(st); } function modal(title,html){ $('sunDevModalV1768')?.remove(); const host=document.createElement('div');host.id='sunDevModalV1768';host.className='sun-dev-modal-v1768';host.innerHTML=`
| № | Компания | Владелец | Тариф | Доступ | Сотрудники | Создана | Действия |
|---|
Номер присвоится автоматически и больше не изменится, даже если другие компании будут удалены.
`); $('sunDevNewCreateV1768').onclick=async()=>{const name=String($('sunDevNewCompanyNameV1768')?.value||'').trim(),email=String($('sunDevNewOwnerEmailV1768')?.value||'').trim(),plan=$('sunDevNewPlanV1768')?.value||'full',days=Math.max(1,Number($('sunDevNewDaysV1768')?.value||30));if(!name){$('sunDevNewErrorV1768').textContent='Введите название.';return}const btn=$('sunDevNewCreateV1768');btn.disabled=true;$('sunDevNewErrorV1768').textContent='Создаю…';try{await rpc('sun_dev_create_company',{p_name:name,p_owner_email:email||null,p_plan:plan,p_days:days,p_boxes:Array.isArray(window.SUN_OFFICIAL_CATALOG)?window.SUN_OFFICIAL_CATALOG:[],p_catalog_version:String(window.SUN_OFFICIAL_CATALOG_VERSION||'')});ui.close();toast('Компания создана и получила следующий номер.','success');renderEnhancedTab('companies')}catch(e){btn.disabled=false;$('sunDevNewErrorV1768').textContent=e?.message||String(e)}}; } async function deleteCompany(row){ if(!row)return;const name=String(row.workspace_name||''); if(!confirm(`Удалить компанию №${Number(row.company_number||0)} «${name}»?\n\nБудут удалены её заказы, клиенты, каталог, настройки, чаты и резервные копии. Сами аккаунты пользователей из Auth удаляться не будут.`))return; const typed=prompt(`Для подтверждения введите точное название компании:\n${name}`,'');if(typed===null)return;if(String(typed).trim()!==name){toast('Удаление отменено: название не совпало.','warn');return} try{await rpc('sun_dev_delete_company_v1768',{p_workspace:row.workspace_id,p_confirm_name:name});toast(`Компания №${Number(row.company_number||0)} удалена. Её номер больше не будет использоваться.`,`success`,6500);renderEnhancedTab('companies')}catch(e){toast(e?.message||String(e),'error',6500)} } async function toggleCompanyBlock(row){const blocked=row?.access_mode==='blocked';if(!blocked&&!confirm(`Заблокировать компанию «${row.workspace_name}»? Данные не удалятся.`))return;try{await rpc('sun_dev_set_subscription',{p_workspace:row.workspace_id,p_plan:row.plan_id||'full',p_days:blocked?30:0,p_status:blocked?'active':'expired'});await audit(blocked?'company.unblock':'company.block',row.workspace_id,null,{company:row.workspace_name});toast(blocked?'Компания разблокирована.':'Компания заблокирована.','success');renderEnhancedTab('companies')}catch(e){toast(e?.message||String(e),'error')}} function showCompanyPlan(row){const ui=modal(`Тариф · №${Number(row.company_number||0)} ${row.workspace_name}`,``);$('sunDevPlanSaveV1768').onclick=async()=>{try{const plan=$('sunDevPlanSelectV1768').value,days=Number($('sunDevPlanDaysV1768').value);await rpc('sun_dev_set_subscription',{p_workspace:row.workspace_id,p_plan:plan,p_days:days,p_status:'active'});await audit('company.subscription.set',row.workspace_id,null,{plan,days});ui.close();toast('Тариф обновлён.','success');renderEnhancedTab('companies')}catch(e){toast(e?.message||String(e),'error')}}} async function showDiagnostics(row){try{const d=await rpc('sun_dev_workspace_diagnostics',{p_workspace:row.workspace_id});modal(`Диагностика · №${Number(row.company_number||0)} ${row.workspace_name}`,`Revision: ${Number(d.revision||0)}
Обновлено: ${fmtDate(d.state_updated_at)}
Последняя копия: ${fmtDate(d.latest_backup)}
Режим доступа: ${esc(d.access_mode||'—')}
`)}catch(e){toast(e?.message||String(e),'error')}} async function enterSupport(row){if(!row)return;try{await cloud()?.enterSupportWorkspace?.(row.workspace_id,row.workspace_name);await audit('support.ui_enter',row.workspace_id,null,{company:row.workspace_name});for(const fn of ['render','renderOrders','renderStats']){try{window[fn]?.()}catch(_){}}const orders=qa('header nav button').find(b=>String(b.dataset.navLabel||b.textContent||'').trim()==='Заказы');orders?.click();toast('Режим поддержки открыт. Изменения не будут сохранены.','info',6500)}catch(e){toast(e?.message||String(e),'error')}} let selectedAccountsWorkspace=null; async function renderAccounts(root){ const [companies,rows]=await Promise.all([getCompanies(),getUsers()]);if(!root?.isConnected||activeTab()!=='accounts')return; const userMap=new Map();for(const r of rows){const id=String(r.user_id);if(!userMap.has(id))userMap.set(id,{id,email:r.email,created_at:r.created_at,last_sign_in_at:r.last_sign_in_at,is_platform_admin:r.is_platform_admin===true,workspaces:[]});if(r.workspace_id)userMap.get(id).workspaces.push({id:r.workspace_id,name:r.workspace_name,role:r.role,display_name:r.display_name,is_active:r.is_active!==false})} const users=[...userMap.values()]; const groups=companies.map(c=>({...c,users:users.filter(u=>u.workspaces.some(w=>String(w.id)===String(c.workspace_id)))})); const orphan=users.filter(u=>!(u.workspaces||[]).length); const selected=selectedAccountsWorkspace==='__orphan'?{workspace_id:'__orphan',company_number:null,workspace_name:'Без компании',users:orphan}:groups.find(g=>String(g.workspace_id)===String(selectedAccountsWorkspace)); if(selected){ root.innerHTML=`| Аккаунт | Роль в компании | Регистрация | Последний вход | Статус |
|---|---|---|---|---|
| ${esc(u.email||'—')}${u.is_platform_admin?' Разработчик ':''}${esc(u.id)} | ${memberships.length?memberships.map(w=>`${esc(w.role||'—')}${w.display_name?` ${esc(w.display_name)} `:''}`).join(''):'—'} | ${fmtDate(u.created_at)} | ${fmtDate(u.last_sign_in_at)} | ${memberships.some(w=>w.is_active===false)?'неактивен':'активен'} |
| В этой компании аккаунтов пока нет. | ||||
${esc(p.description||'')}
| Функция | ${plans.map(p=>`${esc(p.name)} | `).join('')}
|---|---|
| ${esc(FEATURES[f]||f)} | ${plans.map(p=>{const row=rows.find(x=>x.plan_id===p.id&&x.feature_key===f);return ``}).join('')} |
Матрица компактная: строка — функция, столбец — тариф. Изменения чекбоксов сохраняются сразу.
`; qa('[data-plan-mini]',root).forEach(card=>{card.querySelector('[data-plan-max-save]').onclick=async()=>{const raw=card.querySelector('[data-plan-max]').value;try{await rpc('sun_dev_set_plan_max_members',{p_plan:card.dataset.planMini,p_max_members:raw===''?null:Number(raw)});toast('Лимит сотрудников сохранён.','success')}catch(e){toast(e?.message||String(e),'error')}}}); qa('[data-plan-feature]',root).forEach(ch=>ch.onchange=async()=>{ch.disabled=true;try{await rpc('sun_dev_set_plan_feature',{p_plan:ch.dataset.planFeature,p_feature:ch.dataset.feature,p_enabled:ch.checked});toast('Функция тарифа обновлена.','success',2500)}catch(e){ch.checked=!ch.checked;toast(e?.message||String(e),'error')}finally{ch.disabled=false}}); } async function errorGroups(){ try{return await rpc('sun_dev_error_groups_v1768',{p_hours:168,p_limit:100})||[]}catch(_){const raw=await rpc('sun_dev_list_errors',{p_workspace:null,p_limit:100})||[],map=new Map();for(const e of raw){const key=`${e.workspace_name||''}|${e.app_version||''}|${e.level||''}|${e.message||''}`;if(!map.has(key))map.set(key,{workspace_name:e.workspace_name,app_version:e.app_version,level:e.level,message:e.message,occurrences:0,first_seen:e.created_at,last_seen:e.created_at});const g=map.get(key);g.occurrences++;if(new Date(e.created_at)| Дата | Действие | Компания / аккаунт | Детали |
|---|---|---|---|
| ${fmtDate(a.created_at)} | ${esc(a.action)} ${esc(a.actor_email||'')} | ${esc(a.workspace_name||a.target_email||'—')} | ${esc(JSON.stringify(a.details||{}))} |
| Записей пока нет. | |||
${esc(e?.message||e)}