mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 19:44:47 +00:00
feat: screen mockup generator — Elena designs UI previews per module with client data
This commit is contained in:
parent
2fab7092b3
commit
f5d1c466c5
@ -242,6 +242,29 @@ function exportSpecPDF(){
|
|||||||
w.document.close();
|
w.document.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function designScreen(module,idx){
|
||||||
|
const box=document.getElementById(`screen-${idx}`);box.innerHTML='<div style="font-size:12px;color:#9ca3af"><span class="spin">⏳</span> Елена рисует экран...</div>';
|
||||||
|
try{const r=await fetch(`${API}/api/design-screen`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,module})});const d=await r.json();
|
||||||
|
if(d.error){box.innerHTML=`<div style="color:#DC2626;font-size:12px">Ошибка: ${d.error}</div>`;return}
|
||||||
|
box.innerHTML=renderScreen(d.screen);
|
||||||
|
}catch(e){box.innerHTML=`<div style="color:#DC2626">${e.message}</div>`}
|
||||||
|
}
|
||||||
|
function renderScreen(s){
|
||||||
|
const frame=inner=>`<div style="border:1.5px solid var(--border);border-radius:12px;overflow:hidden;background:#fff;max-width:640px"><div style="background:#0F0F1A;padding:8px 14px;display:flex;align-items:center;gap:8px"><span style="display:flex;gap:5px"><span style="width:9px;height:9px;border-radius:50%;background:#FF5F57"></span><span style="width:9px;height:9px;border-radius:50%;background:#FEBC2E"></span><span style="width:9px;height:9px;border-radius:50%;background:#28C840"></span></span><span style="color:rgba(255,255,255,.6);font-size:11px;margin-left:6px">${esc(s.title)}</span></div><div style="padding:16px">${inner}</div></div>`;
|
||||||
|
const acts=`<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:12px">${(s.actions||[]).map((a,i)=>`<span style="font-size:12px;font-weight:600;padding:7px 13px;border-radius:8px;${i===0?'background:#047857;color:#fff':'background:#F1F5F9;color:#475569'}">${esc(a)}</span>`).join("")}</div>`;
|
||||||
|
let inner="";
|
||||||
|
if(s.type==="list"){
|
||||||
|
inner=`<table style="width:100%;border-collapse:collapse;font-size:12px"><tr>${(s.columns||[]).map(c=>`<th style="text-align:left;padding:7px 9px;background:#F5F6F8;border-bottom:2px solid #E5E7EB;font-size:10px;text-transform:uppercase;color:#9ca3af;letter-spacing:.04em">${esc(c)}</th>`).join("")}</tr>${(s.rows||[]).map(r=>`<tr>${r.map(c=>`<td style="padding:8px 9px;border-bottom:1px solid #F1F5F9">${esc(c)}</td>`).join("")}</tr>`).join("")}</table>`+acts;
|
||||||
|
}else if(s.type==="kanban"){
|
||||||
|
inner=`<div style="display:flex;gap:8px;overflow-x:auto">${(s.columns||[]).map(c=>`<div style="flex:1;min-width:120px;background:#F1F5F9;border-radius:8px;padding:8px"><div style="font-size:11px;font-weight:700;margin-bottom:6px">${esc(c)}</div><div style="background:#fff;border:1px solid #E5E7EB;border-radius:6px;padding:8px;font-size:11px">${(s.rows&&s.rows[0]&&s.rows[0][0])||'Карточка'}</div></div>`).join("")}</div>`+acts;
|
||||||
|
}else if(s.type==="dashboard"){
|
||||||
|
inner=`<div style="display:grid;grid-template-columns:repeat(${Math.min(4,(s.kpis||[]).length||1)},1fr);gap:10px">${(s.kpis||[]).map(k=>`<div style="background:#F5F6F8;border-radius:9px;padding:12px"><div style="font-size:22px;font-weight:800;font-family:Montserrat;color:#047857">${esc(k.value)}</div><div style="font-size:11px;color:#6B7280;margin-top:3px">${esc(k.label)}</div></div>`).join("")}</div>`+acts;
|
||||||
|
}else{ // form/card
|
||||||
|
inner=`<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">${(s.fields||[]).map(f=>`<div><div style="font-size:11px;font-weight:600;color:#6B7280;margin-bottom:4px">${esc(f.label)}</div><div style="border:1.5px solid #E5E7EB;border-radius:7px;padding:8px 11px;font-size:12px;color:#9ca3af;background:#FAFBFC">${f.kind==='select'?'▾ выбрать':f.kind==='status'?'● статус':f.kind==='money'?'0 ₽':f.kind==='date'?'дд.мм.гггг':'значение'}</div></div>`).join("")}</div>`+acts;
|
||||||
|
}
|
||||||
|
return frame(inner);
|
||||||
|
}
|
||||||
|
|
||||||
const TABS=[{id:"interview",name:"Интервью",icon:"💬"},{id:"methods",name:"Методологии",icon:"🎯"},{id:"canvas",name:"Стратегия",icon:"📊"},{id:"idef0",name:"Функции",icon:"🔧"},{id:"spec",name:"ТЗ",icon:"📋"}];
|
const TABS=[{id:"interview",name:"Интервью",icon:"💬"},{id:"methods",name:"Методологии",icon:"🎯"},{id:"canvas",name:"Стратегия",icon:"📊"},{id:"idef0",name:"Функции",icon:"🔧"},{id:"spec",name:"ТЗ",icon:"📋"}];
|
||||||
function approved(s){return state.approvals&&state.approvals[s]}
|
function approved(s){return state.approvals&&state.approvals[s]}
|
||||||
async function approve(s){await fetch(`${API}/api/project/approve`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,stage:s,approved:true})});state.approvals=state.approvals||{};state.approvals[s]=1;renderClient();}
|
async function approve(s){await fetch(`${API}/api/project/approve`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,stage:s,approved:true})});state.approvals=state.approvals||{};state.approvals[s]=1;renderClient();}
|
||||||
@ -263,7 +286,7 @@ async function rerun(stage){const [ep,key]=BUILD[stage];const btn=document.getEl
|
|||||||
|
|
||||||
function renderCanvas(c){const B=(k,cls,l)=>{const b=c[k];return `<div class="cv ${cls}"><div class="cv-h">${l}<span>${b.completeness}%</span></div><ul>${b.items.map(i=>`<li>${esc(i)}</li>`).join("")}</ul></div>`};return `<div class="canvas-grid">${B("key_partners","kp","Партнёры")}${B("key_activities","ka","Активности")}${B("value_propositions","vp","Ценность")}${B("customer_relationships","cr","Отношения")}${B("customer_segments","cs","Сегменты")}${B("key_resources","kr","Ресурсы")}${B("channels","ch","Каналы")}${B("cost_structure","co","Издержки")}${B("revenue_streams","rev","Доходы")}<div class="cv-ins"><b>Вывод:</b> ${esc(c.insight)}</div></div>`;}
|
function renderCanvas(c){const B=(k,cls,l)=>{const b=c[k];return `<div class="cv ${cls}"><div class="cv-h">${l}<span>${b.completeness}%</span></div><ul>${b.items.map(i=>`<li>${esc(i)}</li>`).join("")}</ul></div>`};return `<div class="canvas-grid">${B("key_partners","kp","Партнёры")}${B("key_activities","ka","Активности")}${B("value_propositions","vp","Ценность")}${B("customer_relationships","cr","Отношения")}${B("customer_segments","cs","Сегменты")}${B("key_resources","kr","Ресурсы")}${B("channels","ch","Каналы")}${B("cost_structure","co","Издержки")}${B("revenue_streams","rev","Доходы")}<div class="cv-ins"><b>Вывод:</b> ${esc(c.insight)}</div></div>`;}
|
||||||
function renderIdef(m){const box=(fn,ct,ins,outs,me,id,pct)=>{const C=(ct&&ct.length)?ct.map(c=>`<span class="ar">${esc(c.name)}</span>`).join(""):`<span class="ar nomiss">нет управления</span>`;const I=(ins||[]).map(a=>`<span class="ar">${esc(a.name)}</span>`).join("")||'<span class="ar">—</span>';const O=(outs||[]).map(a=>`<span class="ar ${a.target==='НИКУДА'?'dead':''}">${esc(a.name)}${a.target==='НИКУДА'?' ⊘':''}</span>`).join("")||'<span class="ar">—</span>';const M=(me||[]).map(x=>`<span class="ar">${esc(x.name)}</span>`).join("")||'<span class="ar">—</span>';return `<div class="idef"><div class="idef-c">${C}</div><div class="idef-mid"><div class="idef-i">${I}</div><div class="idef-fn">${id?`<b>${id}</b>`:''}${esc(fn)}${pct!=null?`<i style="color:${pct>=70?'#047857':pct>=45?'#F59E0B':'#EF4444'}">${pct}%</i>`:''}</div><div class="idef-o">${O}</div></div><div class="idef-m">${M}</div></div>`};let h=`<div style="background:var(--ink);color:#fff;border-radius:10px;padding:11px 15px;margin-bottom:12px;font-size:13px"><b style="color:var(--mid)">Паттерн:</b> ${esc(m.business_pattern)}</div>`;if(m.context)h+=`<div class="idef-lbl">A-0 Контекст</div>`+box(m.context.function,m.context.controls,m.context.inputs,m.context.outputs,m.context.mechanisms,"A0");h+=`<div class="idef-lbl">Декомпозиция · ${m.activities.length}</div>`;m.activities.forEach(a=>h+=box(a.function,a.controls,a.inputs,a.outputs,a.mechanisms,a.node_id,a.completeness));if(m.arrow_issues&&m.arrow_issues.length){h+=`<div class="idef-lbl">Разрывы · ${m.arrow_issues.length}</div>`;m.arrow_issues.forEach(g=>{const col=g.severity==='critical'?'#DC2626':g.severity==='high'?'#92400E':'#1E40AF';h+=`<div class="blk" style="border-left:3px solid ${col};padding:10px 13px"><div style="font-size:10px;font-weight:700;color:#9ca3af">${esc(g.node_id)} · ${g.type}</div><div style="font-size:13px;font-weight:700">${esc(g.title)}</div><div style="font-size:12px;color:#6b7280;margin-top:3px">${esc(g.description)}</div></div>`})}return h;}
|
function renderIdef(m){const box=(fn,ct,ins,outs,me,id,pct)=>{const C=(ct&&ct.length)?ct.map(c=>`<span class="ar">${esc(c.name)}</span>`).join(""):`<span class="ar nomiss">нет управления</span>`;const I=(ins||[]).map(a=>`<span class="ar">${esc(a.name)}</span>`).join("")||'<span class="ar">—</span>';const O=(outs||[]).map(a=>`<span class="ar ${a.target==='НИКУДА'?'dead':''}">${esc(a.name)}${a.target==='НИКУДА'?' ⊘':''}</span>`).join("")||'<span class="ar">—</span>';const M=(me||[]).map(x=>`<span class="ar">${esc(x.name)}</span>`).join("")||'<span class="ar">—</span>';return `<div class="idef"><div class="idef-c">${C}</div><div class="idef-mid"><div class="idef-i">${I}</div><div class="idef-fn">${id?`<b>${id}</b>`:''}${esc(fn)}${pct!=null?`<i style="color:${pct>=70?'#047857':pct>=45?'#F59E0B':'#EF4444'}">${pct}%</i>`:''}</div><div class="idef-o">${O}</div></div><div class="idef-m">${M}</div></div>`};let h=`<div style="background:var(--ink);color:#fff;border-radius:10px;padding:11px 15px;margin-bottom:12px;font-size:13px"><b style="color:var(--mid)">Паттерн:</b> ${esc(m.business_pattern)}</div>`;if(m.context)h+=`<div class="idef-lbl">A-0 Контекст</div>`+box(m.context.function,m.context.controls,m.context.inputs,m.context.outputs,m.context.mechanisms,"A0");h+=`<div class="idef-lbl">Декомпозиция · ${m.activities.length}</div>`;m.activities.forEach(a=>h+=box(a.function,a.controls,a.inputs,a.outputs,a.mechanisms,a.node_id,a.completeness));if(m.arrow_issues&&m.arrow_issues.length){h+=`<div class="idef-lbl">Разрывы · ${m.arrow_issues.length}</div>`;m.arrow_issues.forEach(g=>{const col=g.severity==='critical'?'#DC2626':g.severity==='high'?'#92400E':'#1E40AF';h+=`<div class="blk" style="border-left:3px solid ${col};padding:10px 13px"><div style="font-size:10px;font-weight:700;color:#9ca3af">${esc(g.node_id)} · ${g.type}</div><div style="font-size:13px;font-weight:700">${esc(g.title)}</div><div style="font-size:12px;color:#6b7280;margin-top:3px">${esc(g.description)}</div></div>`})}return h;}
|
||||||
function renderSpec(s){let h=`<div class="spec-h"><span class="pl">A</span>Обзор</div><div class="blk">${esc(s.overview)}</div><div class="spec-h"><span class="pl">A</span>Роли (${s.roles.length})</div>`;s.roles.forEach(r=>h+=`<div class="blk" style="padding:11px 14px"><b>${esc(r.name)}</b> — ${esc(r.does)}<div style="font-size:11px;color:#9ca3af;margin-top:3px">Доступ: ${esc(r.access)}</div></div>`);h+=`<div class="spec-h"><span class="pl">B</span>Модули (${s.modules.length})</div>`;s.modules.forEach(m=>h+=`<div class="mod"><div style="display:flex;align-items:center;gap:8px;margin-bottom:5px"><span class="mod-node">${esc(m.source_node)}</span><b>${esc(m.name)}</b></div><div style="font-size:12px;color:var(--muted)">${esc(m.purpose)}</div><div style="margin:5px 0">${m.screens.map(x=>`<span class="scr">${esc(x)}</span>`).join("")}</div><div style="font-size:12px;color:#374151">📥 ${esc(m.inputs_data)} · 📤 ${esc(m.outputs_data)}</div>${m.rules.length?`<div style="margin-top:5px">${m.rules.map(r=>`<div class="blk-pain">${esc(r)}</div>`).join("")}</div>`:''}</div>`);h+=`<div class="spec-h"><span class="pl">C</span>Данные (${s.entities.length} таблиц)</div>`;s.entities.forEach(e=>h+=`<div class="ent"><b style="font-family:Montserrat">◆ ${esc(e.name)}</b><div class="ent-fields" style="margin-top:7px">${e.fields.map(f=>`<div class="fld"><b>${esc(f.field)}</b> <em>${esc(f.type)}</em></div>`).join("")}</div>${e.relations.length?`<div style="font-size:11px;color:#6b7280;margin-bottom:5px">🔗 ${e.relations.map(esc).join(" · ")}</div>`:''}<div class="ent-ex">${esc(e.example)}</div></div>`);if(s.open_questions&&s.open_questions.length){h+=`<div class="blk" style="border-color:#FDE68A;background:#FFFBEB"><b>Уточнить перед разработкой</b>${s.open_questions.map(q=>`<div class="blk-pain" style="margin-top:6px">${esc(q)}</div>`).join("")}</div>`}return h;}
|
function renderSpec(s){let h=`<div class="spec-h"><span class="pl">A</span>Обзор</div><div class="blk">${esc(s.overview)}</div><div class="spec-h"><span class="pl">A</span>Роли (${s.roles.length})</div>`;s.roles.forEach(r=>h+=`<div class="blk" style="padding:11px 14px"><b>${esc(r.name)}</b> — ${esc(r.does)}<div style="font-size:11px;color:#9ca3af;margin-top:3px">Доступ: ${esc(r.access)}</div></div>`);h+=`<div class="spec-h"><span class="pl">B</span>Модули (${s.modules.length})</div>`;s.modules.forEach((m,mi)=>h+=`<div class="mod"><div style="display:flex;align-items:center;gap:8px;margin-bottom:5px"><span class="mod-node">${esc(m.source_node)}</span><b>${esc(m.name)}</b><button class="cp-btn cp-r" style="margin-left:auto;padding:5px 11px" onclick="designScreen('${esc(m.name).replace(/'/g,"")}',${mi})">🖼 Экран</button></div><div style="font-size:12px;color:var(--muted)">${esc(m.purpose)}</div><div style="margin:5px 0">${m.screens.map(x=>`<span class="scr">${esc(x)}</span>`).join("")}</div><div style="font-size:12px;color:#374151">📥 ${esc(m.inputs_data)} · 📤 ${esc(m.outputs_data)}</div>${m.rules.length?`<div style="margin-top:5px">${m.rules.map(r=>`<div class="blk-pain">${esc(r)}</div>`).join("")}</div>`:''}<div id="screen-${mi}" style="margin-top:10px"></div></div>`);h+=`<div class="spec-h"><span class="pl">C</span>Данные (${s.entities.length} таблиц)</div>`;s.entities.forEach(e=>h+=`<div class="ent"><b style="font-family:Montserrat">◆ ${esc(e.name)}</b><div class="ent-fields" style="margin-top:7px">${e.fields.map(f=>`<div class="fld"><b>${esc(f.field)}</b> <em>${esc(f.type)}</em></div>`).join("")}</div>${e.relations.length?`<div style="font-size:11px;color:#6b7280;margin-bottom:5px">🔗 ${e.relations.map(esc).join(" · ")}</div>`:''}<div class="ent-ex">${esc(e.example)}</div></div>`);if(s.open_questions&&s.open_questions.length){h+=`<div class="blk" style="border-color:#FDE68A;background:#FFFBEB"><b>Уточнить перед разработкой</b>${s.open_questions.map(q=>`<div class="blk-pain" style="margin-top:6px">${esc(q)}</div>`).join("")}</div>`}return h;}
|
||||||
|
|
||||||
loadProjects().then(render);
|
loadProjects().then(render);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user