wasrusgen1-crm/docs/client_card_demo.html

1210 lines
75 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{code}} · @wasrusgen1 CRM</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@700;800&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',sans-serif;background:#F5F6F8;color:#1A1A2E;height:100vh;overflow:hidden}
/* ── Layout shell (same as admin) ── */
.layout{display:flex;height:100vh}
.sidebar{width:220px;background:#0F0F1A;display:flex;flex-direction:column;flex-shrink:0;overflow:hidden}
.sb-top{padding:20px 18px 16px;border-bottom:1px solid rgba(255,255,255,.07)}
.sb-brand{font-family:'Montserrat',sans-serif;font-size:13px;font-weight:800;color:#fff;letter-spacing:.04em;display:flex;align-items:center;gap:8px}
.sb-sub{font-size:10px;color:rgba(255,255,255,.35);margin-top:3px;letter-spacing:.02em}
.sb-nav{flex:1;overflow-y:auto;padding:12px 0;scrollbar-width:none}
.sb-nav::-webkit-scrollbar{display:none}
.nav-section{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:rgba(255,255,255,.25);padding:12px 18px 4px}
.nav-item{display:flex;align-items:center;gap:9px;padding:8px 18px;cursor:pointer;font-size:13px;color:rgba(255,255,255,.55);border:none;background:none;width:100%;text-align:left;font-family:'Inter',sans-serif;transition:.15s;text-decoration:none}
.nav-item:hover{color:rgba(255,255,255,.85);background:rgba(255,255,255,.05)}
.nav-item.active{color:#fff;background:rgba(4,120,87,.12);border-left:2px solid #047857;padding-left:16px}
.ni-icon{font-size:14px;flex-shrink:0;width:18px;text-align:center}
.ni-label{flex:1}
.sb-client-box{margin:12px 14px 0;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);border-radius:10px;padding:10px 12px}
.sb-client-code{font-family:monospace;font-size:15px;font-weight:700;color:#10B981;letter-spacing:.06em}
.sb-client-name{font-size:11px;color:rgba(255,255,255,.45);margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sb-footer{padding:14px 18px;border-top:1px solid rgba(255,255,255,.07)}
.sb-stat{font-size:11px;color:rgba(255,255,255,.4);line-height:1.8}
.sb-stat b{color:rgba(255,255,255,.75);font-size:12px}
/* ── Content area ── */
.content{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}
.content-hdr{background:#fff;border-bottom:1.5px solid #E5E7EB;padding:0 28px;height:56px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0;gap:16px}
.breadcrumb{display:flex;align-items:center;gap:8px;font-size:13px;color:#94A3B8}
.breadcrumb a{color:#94A3B8;text-decoration:none;transition:.15s}
.breadcrumb a:hover{color:#047857}
.breadcrumb-sep{color:#CBD5E1}
.breadcrumb-cur{font-weight:700;color:#0f172a;font-family:monospace;font-size:14px}
.hdr-actions{display:flex;align-items:center;gap:10px}
.btn-save{background:linear-gradient(135deg,#064E3B,#047857);color:#fff;border:none;padding:8px 22px;border-radius:8px;font-family:'Montserrat',sans-serif;font-size:13px;font-weight:700;cursor:pointer;transition:.2s;white-space:nowrap}
.btn-save:hover{opacity:.9;transform:translateY(-1px)}
.btn-contract{background:#F0FDF4;border:1.5px solid #A7F3D0;color:#047857;padding:7px 14px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:6px;text-decoration:none;transition:.2s;white-space:nowrap}
.btn-contract:hover{background:#DCFCE7}
.save-status{font-size:12px;color:#94A3B8;transition:.3s;white-space:nowrap}
.save-status.ok{color:#059669}
.save-status.err{color:#DC2626}
.content-body{flex:1;overflow-y:auto;padding:24px 28px 40px}
.content-body::-webkit-scrollbar{width:4px}
.content-body::-webkit-scrollbar-thumb{background:#E2E8F0;border-radius:4px}
/* ── Hero card ── */
.hero-card{background:#fff;border-radius:14px;border:1.5px solid #E5E7EB;padding:20px 24px;margin-bottom:20px;display:flex;align-items:center;gap:20px;flex-wrap:wrap}
.hero-avatar{width:52px;height:52px;border-radius:14px;background:linear-gradient(135deg,#064E3B,#047857);display:flex;align-items:center;justify-content:center;font-family:'Montserrat',sans-serif;font-size:20px;font-weight:800;color:#fff;flex-shrink:0}
.hero-main{flex:1;min-width:200px}
.hero-name{font-family:'Montserrat',sans-serif;font-size:18px;font-weight:800;color:#0f172a;margin-bottom:4px}
.hero-meta{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.hero-code{font-family:monospace;font-size:11px;color:#94A3B8;background:#F8FAFC;border:1px solid #E2E8F0;padding:2px 8px;border-radius:6px}
.hero-stat{font-size:12px;color:#94A3B8}
.hero-badges{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px}
.hero-right{display:flex;flex-direction:column;align-items:flex-end;gap:8px}
.type-toggle{display:flex;background:#F1F5F9;border:1.5px solid #E2E8F0;border-radius:8px;padding:3px;gap:2px}
.type-btn{padding:5px 14px;border-radius:5px;border:none;font-family:'Inter',sans-serif;font-size:12px;font-weight:600;cursor:pointer;transition:.2s;background:transparent;color:#64748B}
.type-btn.active{background:#fff;color:#047857;box-shadow:0 1px 4px rgba(0,0,0,.1)}
/* ── Tab navigation ── */
.tabs-nav{display:flex;gap:2px;background:#fff;border-radius:14px 14px 0 0;border:1.5px solid #E5E7EB;border-bottom:none;padding:6px 6px 0;margin-bottom:0}
.tab-btn{padding:9px 18px;border-radius:8px 8px 0 0;border:none;font-family:'Inter',sans-serif;font-size:13px;font-weight:600;cursor:pointer;background:transparent;color:#94A3B8;transition:.15s;display:flex;align-items:center;gap:6px;position:relative;bottom:-1.5px}
.tab-btn:hover{color:#475569;background:#F8FAFC}
.tab-btn.active{color:#047857;background:#fff;border:1.5px solid #E5E7EB;border-bottom:1.5px solid #fff}
.tab-badge{background:#F1F5F9;color:#64748B;border-radius:10px;padding:1px 7px;font-size:10px;font-weight:700}
.tab-btn.active .tab-badge{background:#ECFDF5;color:#047857}
/* ── Tab panes ── */
.tab-pane{display:none;background:#fff;border:1.5px solid #E5E7EB;border-radius:0 14px 14px 14px;padding:24px}
.tab-pane.active{display:block}
/* ── Section inside tab ── */
.inner-section{margin-bottom:24px}
.inner-section:last-child{margin-bottom:0}
.inner-title{font-family:'Montserrat',sans-serif;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#94A3B8;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid #F1F5F9;display:flex;align-items:center;justify-content:space-between}
/* ── Fields grid ── */
.fields{display:grid;grid-template-columns:repeat(2,1fr);gap:14px}
.fields.cols3{grid-template-columns:repeat(3,1fr)}
.fields.cols1{grid-template-columns:1fr}
.field{display:flex;flex-direction:column;gap:5px}
.field.span2{grid-column:span 2}
.field.span3{grid-column:span 3}
.field label{font-size:10px;font-weight:600;color:#64748B;text-transform:uppercase;letter-spacing:.06em}
.field input,.field textarea,.field select{padding:9px 12px;border:1.5px solid #E2E8F0;border-radius:8px;font-family:'Inter',sans-serif;font-size:13px;outline:none;transition:.2s;background:#fff;color:#0f172a}
.field input:focus,.field textarea:focus,.field select:focus{border-color:#047857;box-shadow:0 0 0 3px rgba(4,120,87,.1)}
.field input::placeholder,.field textarea::placeholder{color:#CBD5E1}
.field textarea{resize:vertical;min-height:72px;line-height:1.5}
/* ── Collapse (паспорт) ── */
.collapse-toggle{display:flex;align-items:center;gap:8px;cursor:pointer;padding:10px 0;font-size:13px;font-weight:600;color:#64748B;border:none;background:none;font-family:'Inter',sans-serif;width:100%;transition:.15s}
.collapse-toggle:hover{color:#047857}
.collapse-arrow{font-size:10px;transition:transform .2s;color:#94A3B8}
.collapse-arrow.open{transform:rotate(90deg)}
.collapse-body{display:none;padding-top:14px}
.collapse-body.open{display:block}
/* ── Process flow ── */
.phase-flow{display:flex;gap:0;overflow-x:auto;margin-bottom:10px}
.phase-bar{height:6px;background:#F5F6F8;border-radius:100px;overflow:hidden;margin-bottom:4px}
.phase-bar-fill{height:100%;background:linear-gradient(90deg,#047857,#10B981);border-radius:100px;transition:width .5s ease}
.phase-pct-row{display:flex;justify-content:space-between;font-size:10px;color:#94A3B8}
/* ── Tasks ── */
.task-row{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid #F8FAFC}
.task-row:last-child{border-bottom:none}
.task-cb{width:15px;height:15px;accent-color:#047857;cursor:pointer;flex-shrink:0}
.task-text{flex:1;font-size:13px;color:#0f172a}
.task-text.done-text{text-decoration:line-through;color:#94A3B8}
.task-prio-tag{font-size:10px;padding:2px 8px;border-radius:10px;flex-shrink:0}
.task-del{color:#CBD5E1;font-size:17px;cursor:pointer;line-height:1;border:none;background:none;padding:0 2px}
.task-del:hover{color:#DC2626}
.task-add-row{display:flex;gap:8px;margin-top:12px}
.task-input{flex:1;padding:8px 12px;border:1.5px solid #E2E8F0;border-radius:8px;font-family:'Inter',sans-serif;font-size:13px;outline:none;transition:.2s}
.task-input:focus{border-color:#047857;box-shadow:0 0 0 3px rgba(4,120,87,.1)}
.task-prio-sel{padding:8px 10px;border:1.5px solid #E2E8F0;border-radius:8px;font-family:'Inter',sans-serif;font-size:12px;outline:none;background:#fff;color:#0f172a;transition:.2s}
.btn-add{background:linear-gradient(135deg,#064E3B,#047857);color:#fff;border:none;padding:8px 16px;border-radius:8px;font-family:'Inter',sans-serif;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}
/* ── Interview card ── */
.iv-empty{font-size:13px;color:#94A3B8;text-align:center;padding:20px 0}
.iv-session{border:1.5px solid #E5E7EB;border-radius:12px;overflow:hidden;margin-bottom:14px}
.iv-session-hdr{padding:11px 16px;background:#F8FAFC;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;justify-content:space-between;gap:10px}
/* ── Intake brief ── */
.intake-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px}
.intake-block{border:1.5px solid #E5E7EB;border-radius:10px;padding:14px}
.intake-block.empty{opacity:.5;border-color:#F1F5F9}
.intake-field-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#94A3B8;margin-bottom:3px}
.intake-field-val{font-size:13px;color:#0f172a;background:#F8FAFC;border:1px solid #E2E8F0;border-radius:7px;padding:7px 10px;line-height:1.5}
/* ── Events ── */
.event-row{display:flex;gap:12px;padding:9px 0;border-bottom:1px solid #F8FAFC;align-items:flex-start}
.event-row:last-child{border-bottom:none}
.event-icon{font-size:17px;flex-shrink:0;margin-top:1px}
.event-title{font-size:13px;font-weight:600;color:#0f172a}
.event-ts{font-size:11px;color:#94A3B8;margin-top:2px}
.note-add-row{display:flex;gap:8px;margin-top:12px}
.note-input{flex:1;padding:8px 12px;border:1.5px solid #E2E8F0;border-radius:8px;font-family:'Inter',sans-serif;font-size:13px;outline:none;transition:.2s}
.note-input:focus{border-color:#047857;box-shadow:0 0 0 3px rgba(4,120,87,.1)}
/* ── Modals ── */
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.52);z-index:1000;align-items:center;justify-content:center}
.modal-box{background:#fff;border-radius:16px;padding:28px;width:400px;max-width:92vw;box-shadow:0 20px 60px rgba(0,0,0,.25)}
.modal-title{font-family:'Montserrat',sans-serif;font-size:15px;font-weight:800;color:#0f172a;margin-bottom:20px}
.modal-footer{display:flex;gap:10px;margin-top:20px}
.btn-modal-cancel{flex:1;background:#F1F5F9;border:1.5px solid #E2E8F0;color:#64748B;padding:10px;border-radius:10px;font-family:'Inter',sans-serif;font-size:13px;font-weight:600;cursor:pointer}
.btn-modal-ok{flex:2;background:linear-gradient(135deg,#022C22,#047857);color:#fff;border:none;padding:10px;border-radius:10px;font-family:'Inter',sans-serif;font-size:14px;font-weight:700;cursor:pointer}
.btn-modal-blue{flex:2;background:linear-gradient(135deg,#1D4ED8,#3B82F6);color:#fff;border:none;padding:10px;border-radius:10px;font-family:'Inter',sans-serif;font-size:14px;font-weight:700;cursor:pointer}
.modal-field{margin-bottom:14px}
.modal-label{font-size:11px;font-weight:600;color:#64748B;text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:6px}
.modal-select,.modal-textarea{width:100%;padding:9px 12px;border:1.5px solid #E2E8F0;border-radius:8px;font-family:'Inter',sans-serif;font-size:13px;outline:none;background:#fff;color:#0f172a;transition:.2s}
.modal-select:focus,.modal-textarea:focus{border-color:#047857;box-shadow:0 0 0 3px rgba(4,120,87,.1)}
.modal-textarea{resize:vertical;min-height:72px;line-height:1.5}
.modal-tip{background:#ECFDF5;border:1px solid #A7F3D0;border-radius:8px;padding:11px;font-size:12px;color:#065F46;margin-bottom:16px}
.modal-radio-row{display:flex;gap:8px;margin-top:6px}
.modal-radio-lbl{flex:1;border:1.5px solid #E2E8F0;border-radius:8px;padding:9px 11px;cursor:pointer;display:flex;align-items:center;gap:7px;transition:.2s;font-size:13px}
/* ── Suggest modal ── */
.suggest-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:1001;align-items:flex-start;justify-content:center;overflow-y:auto;padding:40px 16px}
.suggest-box{background:#fff;border-radius:16px;padding:28px;width:560px;max-width:100%;box-shadow:0 20px 60px rgba(0,0,0,.3);margin:auto}
/* ── Status / priority badges ── */
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:600}
</style>
</head>
<body>
<div class="layout">
<!-- ── SIDEBAR ── -->
<aside class="sidebar">
<div class="sb-top">
<div class="sb-brand">
<div style="width:28px;height:28px;border-radius:8px;background:#047857;display:flex;align-items:center;justify-content:center;flex-shrink:0">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
</div>
<div>
<div style="font-family:'Montserrat',sans-serif;font-size:9px;font-weight:500;letter-spacing:.06em;color:rgba(255,255,255,.4);line-height:1;margin-bottom:2px">@wasrusgen1</div>
<div style="font-family:'Montserrat',sans-serif;font-size:13px;font-weight:800;color:#fff;letter-spacing:.02em;line-height:1">КОНСАЛТИНГ</div>
</div>
</div>
<div class="sb-sub" style="margin-top:4px;padding-left:36px">Admin Panel</div>
</div>
<div class="sb-client-box">
<div class="sb-client-code">{{code}}</div>
<div class="sb-client-name" id="sb-name">{{name}}</div>
</div>
<nav class="sb-nav">
<div class="nav-section">КЛИЕНТЫ</div>
<a class="nav-item" href="/consult/admin">
<span class="ni-icon">📋</span><span class="ni-label">Все клиенты</span>
</a>
<div class="nav-section">КАРТОЧКА</div>
<a class="nav-item active" href="#" onclick="switchTab('work');return false">
<span class="ni-icon">💼</span><span class="ni-label">Работа</span>
</a>
<a class="nav-item" href="#" onclick="switchTab('interview');return false">
<span class="ni-icon">📋</span><span class="ni-label">Интервью</span>
</a>
<a class="nav-item" href="#" onclick="switchTab('details');return false">
<span class="ni-icon">🗂️</span><span class="ni-label">Реквизиты</span>
</a>
<a class="nav-item" href="#" onclick="switchTab('history');return false">
<span class="ni-icon">🕐</span><span class="ni-label">История</span>
</a>
<a class="nav-item" href="#" onclick="switchTab('finance');return false">
<span class="ni-icon">💰</span><span class="ni-label">Финансы</span>
</a>
</nav>
<div class="sb-footer">
<div class="sb-stat">Добавлен <b>{{created}}</b></div>
<div class="sb-stat"><b>{{msg_count}}</b> сообщений</div>
<div class="sb-stat">Визит: <b>{{last_active}}</b></div>
</div>
</aside>
<!-- ── CONTENT ── -->
<main class="content">
<div class="content-hdr">
<div class="breadcrumb">
<a href="/consult/admin">Клиенты</a>
<span class="breadcrumb-sep"></span>
<span class="breadcrumb-cur">{{code}}</span>
<span id="hdr-type-chip"></span>
</div>
<div class="hdr-actions">
<span class="save-status" id="save-status"></span>
<a href="/consult/admin/client/{{code}}/contract" target="_blank" class="btn-contract">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
Договор
</a>
<button class="btn-save" onclick="saveCard()">Сохранить</button>
</div>
</div>
<div class="content-body">
<!-- ── HERO ── -->
<div class="hero-card">
<div class="hero-avatar" id="hero-avatar">?</div>
<div class="hero-main">
<div class="hero-name" id="hero-name">{{name}}</div>
<div class="hero-meta">
<span class="hero-code">{{code}}</span>
<span class="hero-stat">{{msg_count}} сообщ.</span>
<span class="hero-stat">Визит: {{last_active}}</span>
</div>
<div class="hero-badges" id="hero-badges"></div>
</div>
<div class="hero-right">
<div class="type-toggle">
<button class="type-btn" id="btn-fiz" onclick="setType('fiz')">Физ. лицо</button>
<button class="type-btn" id="btn-yur" onclick="setType('yur')">Юр. лицо</button>
</div>
</div>
</div>
<!-- ── TAB NAV ── -->
<div class="tabs-nav">
<button class="tab-btn active" id="tbtn-work" onclick="switchTab('work')">💼 Работа</button>
<button class="tab-btn" id="tbtn-interview" onclick="switchTab('interview')">📋 Интервью</button>
<button class="tab-btn" id="tbtn-details" onclick="switchTab('details')">🗂 Реквизиты</button>
<button class="tab-btn" id="tbtn-history" onclick="switchTab('history')">🕐 История</button>
<button class="tab-btn" id="tbtn-finance" onclick="switchTab('finance')">💰 Финансы</button>
</div>
<!-- ══ TAB: РАБОТА ══ -->
<div class="tab-pane active" id="tab-work">
<!-- CRM статус -->
<div class="inner-section">
<div class="inner-title">CRM статус</div>
<div class="fields cols3" style="margin-bottom:16px">
<div class="field">
<label>Статус лида</label>
<select id="f-status">
<option value="new">🔵 Лид</option>
<option value="in_progress">🟡 В работе</option>
<option value="proposal">🟠 Предложение отправлено</option>
<option value="active">🟢 Работаем</option>
<option value="one_time">🟣 Разовая консультация</option>
<option value="rejected">🔴 Отказ</option>
</select>
</div>
<div class="field">
<label>Приоритет</label>
<select id="f-priority-override" title="Перебивает автоформулу">
<option value="">⚙️ Авто</option>
<option value="urgent">🔴 Срочно</option>
<option value="high">🟠 Важно</option>
<option value="normal">🟡 Обычный</option>
<option value="low">⚪ Низкий</option>
</select>
</div>
<div class="field" id="wformat-field" style="display:none">
<label>Формат работы</label>
<select id="f-work-format">
<option value="">— не выбрано —</option>
<option value="express">Экспресс-диагностика</option>
<option value="audit">Полный аудит + план</option>
<option value="turnkey">Внедрение под ключ</option>
</select>
</div>
</div>
<!-- Фаза проекта -->
<div class="fields" style="margin-bottom:14px">
<div class="field">
<label>Фаза проекта</label>
<select id="f-project-phase" onchange="updateProcessFlow(this.value)">
<option value="">— не начат —</option>
<option value="discovery">1 · Диагностика AS-IS</option>
<option value="audit">2 · Аудит и отчёт</option>
<option value="design">3 · Проектирование TO-BE</option>
<option value="impl">4 · Внедрение CRM</option>
<option value="support">5 · Сопровождение</option>
</select>
</div>
</div>
<div id="process-flow-wrap" style="display:none;margin-bottom:14px">
<div style="display:flex;gap:0;overflow-x:auto" id="process-flow-steps"></div>
<div class="phase-bar" style="margin-top:10px"><div class="phase-bar-fill" id="process-flow-bar" style="width:0"></div></div>
<div class="phase-pct-row" style="margin-top:3px"><span>0%</span><span id="process-flow-pct" style="font-weight:700;color:#047857"></span><span>100%</span></div>
</div>
<div class="fields cols1">
<div class="field">
<label>Потребность / запрос клиента</label>
<textarea id="f-need" placeholder="Что хочет клиент, какую задачу решает…" style="min-height:64px"></textarea>
</div>
</div>
</div>
<!-- Задачи -->
<div class="inner-section">
<div class="inner-title">
<span>Задачи</span>
<span id="tasks-open-cnt" style="font-size:11px;color:#047857;font-weight:600"></span>
</div>
<div id="tasks-list"></div>
<div class="task-add-row">
<input type="text" class="task-input" id="task-input" placeholder="Новая задача…" onkeydown="if(event.key==='Enter')addTask()">
<select class="task-prio-sel" id="task-prio">
<option value="medium">Обычная</option>
<option value="high">Важная</option>
<option value="low">Низкая</option>
</select>
<button class="btn-add" onclick="addTask()">+ Добавить</button>
</div>
</div>
<!-- Примечание -->
<div class="inner-section">
<div class="inner-title">Примечание консультанта</div>
<div class="fields cols1">
<div class="field">
<textarea id="f-notes" placeholder="Внутренние заметки по клиенту…" style="min-height:80px"></textarea>
</div>
</div>
</div>
</div><!-- /tab-work -->
<!-- ══ TAB: ИНТЕРВЬЮ ══ -->
<div class="tab-pane" id="tab-interview">
<!-- Запуск интервью -->
<div class="inner-section">
<div class="inner-title">
<span>ТЗ / Интервью <span id="interview-status-chip"></span></span>
<div style="display:flex;gap:8px">
<button onclick="openSuggestModal()" style="background:#EFF6FF;border:1.5px solid #BFDBFE;color:#1D4ED8;padding:6px 12px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer">🤖 Вопросы по чату</button>
<button onclick="openLaunchModal()" style="background:linear-gradient(135deg,#064E3B,#047857);color:#fff;border:none;padding:6px 14px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:5px">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="5 3 19 12 5 21 5 3"/></svg> Запустить
</button>
</div>
</div>
<div id="interview-body"><div class="iv-empty">Интервью не запущено</div></div>
</div>
<!-- Первичный бриф -->
<div class="inner-section">
<div class="inner-title">
<span>Первичный бриф</span>
<span id="intake-status-chip"></span>
</div>
<div id="intake-body"><div class="iv-empty">Загрузка…</div></div>
</div>
</div><!-- /tab-interview -->
<!-- ══ TAB: РЕКВИЗИТЫ ══ -->
<div class="tab-pane" id="tab-details">
<!-- Системные данные -->
<div class="inner-section">
<div class="inner-title">Системные данные</div>
<div class="fields cols3">
<div class="field">
<label>Имя / Псевдоним</label>
<input type="text" id="f-name" placeholder="Отображаемое имя">
</div>
<div class="field">
<label>№ Договора</label>
<input type="text" id="f-contract" placeholder="КС-2026-001">
</div>
<div class="field">
<label>Email для входа</label>
<input type="email" id="f-email" placeholder="client@company.ru">
</div>
</div>
</div>
<!-- Физ. лицо -->
<div id="section-fiz">
<div class="inner-section">
<div class="inner-title">Физическое лицо</div>
<div class="fields">
<div class="field span2">
<label>ФИО полностью</label>
<input type="text" id="fiz-full_name" placeholder="Иванов Иван Петрович">
</div>
<div class="field">
<label>Телефон</label>
<input type="tel" id="fiz-phone" placeholder="+7 900 000-00-00">
</div>
<div class="field">
<label>Дата рождения</label>
<input type="text" id="fiz-birthdate" placeholder="01.01.1980">
</div>
<div class="field span2">
<label>Адрес регистрации</label>
<input type="text" id="fiz-address" placeholder="г. Москва, ул. Примерная, д. 1, кв. 1">
</div>
</div>
</div>
<!-- Паспорт — collapsed -->
<div class="inner-section">
<button class="collapse-toggle" onclick="togglePassport()" id="passport-toggle">
<span class="collapse-arrow" id="passport-arrow"></span>
🔐 Паспортные данные
<span style="font-size:11px;color:#CBD5E1;font-weight:400;margin-left:4px">(для договора)</span>
</button>
<div class="collapse-body" id="passport-body">
<div class="fields cols3">
<div class="field">
<label>Серия</label>
<input type="text" id="fiz-passport_series" placeholder="4510">
</div>
<div class="field">
<label>Номер</label>
<input type="text" id="fiz-passport_number" placeholder="123456">
</div>
<div class="field">
<label>Дата выдачи</label>
<input type="text" id="fiz-passport_date" placeholder="15.06.2015">
</div>
<div class="field span3">
<label>Кем выдан</label>
<input type="text" id="fiz-passport_issued" placeholder="УФМС России по г. Москве…">
</div>
<div class="field span3">
<label>Код подразделения</label>
<input type="text" id="fiz-passport_code" placeholder="770-001">
</div>
</div>
</div>
</div>
</div>
<!-- Юр. лицо -->
<div id="section-yur" style="display:none">
<div class="inner-section">
<div class="inner-title">Юридическое лицо</div>
<div class="fields">
<div class="field span2">
<label>Полное наименование</label>
<input type="text" id="yur-org_name" placeholder="ООО «Ромашка»">
</div>
<div class="field"><label>ИНН</label><input type="text" id="yur-inn" placeholder="7700000000"></div>
<div class="field"><label>КПП</label><input type="text" id="yur-kpp" placeholder="770001001"></div>
<div class="field"><label>ОГРН</label><input type="text" id="yur-ogrn" placeholder="1027700000000"></div>
<div class="field"><label>ОКПО</label><input type="text" id="yur-okpo" placeholder="00000000"></div>
<div class="field span2"><label>Юридический адрес</label><input type="text" id="yur-legal_address" placeholder="127000, г. Москва…"></div>
<div class="field span2"><label>Фактический адрес</label><input type="text" id="yur-actual_address" placeholder="если отличается"></div>
<div class="field"><label>Контактное лицо (ФИО)</label><input type="text" id="yur-contact_person" placeholder="Иванов Иван Иванович"></div>
<div class="field"><label>Должность</label><input type="text" id="yur-contact_position" placeholder="Директор"></div>
<div class="field"><label>Телефон</label><input type="tel" id="yur-phone" placeholder="+7 495 000-00-00"></div>
<div class="field"><label>Действует на основании</label><input type="text" id="yur-basis" placeholder="Устава"></div>
</div>
</div>
<div class="inner-section">
<div class="inner-title">Банковские реквизиты</div>
<div class="fields">
<div class="field span2"><label>Банк</label><input type="text" id="yur-bank_name" placeholder="ПАО Сбербанк, г. Москва"></div>
<div class="field"><label>Расчётный счёт (р/с)</label><input type="text" id="yur-bank_account" placeholder="40702810000000000000"></div>
<div class="field"><label>БИК</label><input type="text" id="yur-bank_bik" placeholder="044525225"></div>
<div class="field"><label>Корр. счёт (к/с)</label><input type="text" id="yur-bank_corr" placeholder="30101810400000000225"></div>
</div>
</div>
</div>
</div><!-- /tab-details -->
<!-- ══ TAB: ИСТОРИЯ ══ -->
<div class="tab-pane" id="tab-history">
<div class="inner-section">
<div class="inner-title">
<span>История взаимодействий</span>
<span id="events-count" style="font-size:11px;color:#047857;font-weight:600"></span>
</div>
<div id="events-list"></div>
<div class="note-add-row">
<input type="text" class="note-input" id="note-input" placeholder="Добавить заметку консультанта…"
onkeydown="if(event.key==='Enter')addNote()">
<button class="btn-add" onclick="addNote()">+ Добавить</button>
</div>
</div>
</div><!-- /tab-history -->
<!-- ══ TAB: ФИНАНСЫ ══ -->
<div class="tab-pane" id="tab-finance">
<!-- Договор -->
<div class="inner-section">
<div class="inner-title">
<span>Договор</span>
<span id="contract-chip"></span>
</div>
<div class="fields cols3" style="margin-bottom:16px">
<div class="field">
<label>Тариф</label>
<select id="fin-tariff" onchange="onTariffChange()">
<option value="express">Экспресс-диагностика (0 ₽)</option>
<option value="audit" selected>Полный аудит + план (150 000 ₽)</option>
<option value="impl">Внедрение под ключ (350 000 ₽)</option>
</select>
</div>
<div class="field">
<label>Сумма договора, ₽</label>
<input type="number" id="fin-price" value="150000" min="0" step="1000">
</div>
<div class="field" style="justify-content:flex-end;align-items:flex-end;display:flex;flex-direction:column">
<div style="display:flex;gap:8px;width:100%;justify-content:flex-end;align-items:flex-end;height:100%">
<button onclick="saveContract()" style="background:#F8FAFC;border:1.5px solid #E2E8F0;color:#64748B;padding:8px 14px;border-radius:8px;font-family:'Inter',sans-serif;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap">💾 Сохранить</button>
<button onclick="sendContract()" id="btn-send-contract" style="background:linear-gradient(135deg,#064E3B,#047857);color:#fff;border:none;padding:8px 16px;border-radius:8px;font-family:'Montserrat',sans-serif;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;display:flex;align-items:center;gap:5px">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="5 3 19 12 5 21 5 3"/></svg>Отправить клиенту
</button>
</div>
</div>
</div>
<div id="contract-sign-info" style="display:none;background:#ECFDF5;border:1.5px solid #A7F3D0;border-radius:10px;padding:12px 16px;font-size:13px;color:#047857;margin-top:4px"></div>
<div id="contract-send-msg" style="font-size:12px;color:#94A3B8;margin-top:6px"></div>
</div>
<!-- Просмотр договора -->
<div class="inner-section">
<div class="inner-title">Документ</div>
<a href="/consult/admin/client/{{code}}/contract" target="_blank" style="display:inline-flex;align-items:center;gap:7px;background:#F8FAFC;border:1.5px solid #E2E8F0;color:#047857;padding:9px 16px;border-radius:8px;font-size:13px;font-weight:600;text-decoration:none;transition:.2s" onmouseover="this.style.background='#F0FDF4'" onmouseout="this.style.background='#F8FAFC'">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
Открыть договор для просмотра / редактирования
</a>
</div>
<!-- График платежей -->
<div class="inner-section">
<div class="inner-title">
<span>График платежей</span>
<span id="pay-summary-chip" style="font-size:11px;font-weight:600;color:#047857"></span>
</div>
<div id="payments-list"><div style="color:#94A3B8;font-size:13px">Договор ещё не отправлен клиенту</div></div>
</div>
<!-- Критерии приёмки -->
<div class="inner-section">
<div class="inner-title">Критерии приёмки услуг</div>
<div style="display:flex;flex-direction:column;gap:10px">
<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 12px;background:#F8FAFC;border-radius:8px;border:1px solid #E2E8F0">
<span style="font-size:16px;flex-shrink:0">1</span>
<div><div style="font-size:12px;font-weight:700;color:#0f172a;margin-bottom:2px">Диагностика AS-IS</div><div style="font-size:12px;color:#64748B">Передан отчёт + клиент подтвердил получение</div></div>
</div>
<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 12px;background:#F8FAFC;border-radius:8px;border:1px solid #E2E8F0">
<span style="font-size:16px;flex-shrink:0">2</span>
<div><div style="font-size:12px;font-weight:700;color:#0f172a;margin-bottom:2px">Аудит + TO-BE</div><div style="font-size:12px;color:#64748B">Согласован план внедрения (дорожная карта)</div></div>
</div>
<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 12px;background:#F8FAFC;border-radius:8px;border:1px solid #E2E8F0">
<span style="font-size:16px;flex-shrink:0">3</span>
<div><div style="font-size:12px;font-weight:700;color:#0f172a;margin-bottom:2px">Внедрение</div><div style="font-size:12px;color:#64748B">Подписан итоговый Акт об оказанных услугах</div></div>
</div>
<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 12px;background:#F8FAFC;border-radius:8px;border:1px solid #E2E8F0">
<span style="font-size:16px;flex-shrink:0">🔄</span>
<div><div style="font-size:12px;font-weight:700;color:#0f172a;margin-bottom:2px">Сопровождение</div><div style="font-size:12px;color:#64748B">Ежемесячный мини-акт по итогам периода</div></div>
</div>
<div style="background:#FFFBEB;border:1px solid #FDE68A;border-radius:8px;padding:10px 12px;font-size:12px;color:#92400E">
⚖️ По п. 3.4 договора: при отсутствии мотивированного отказа в течение 5 рабочих дней с момента направления Акта — услуги считаются принятыми.
</div>
</div>
</div>
</div><!-- /tab-finance -->
</div><!-- /content-body -->
</main>
</div><!-- /layout -->
<!-- ══ MODAL: Launch Interview ══ -->
<div class="modal-overlay" id="launch-modal" onclick="if(event.target===this)closeLaunchModal()">
<div class="modal-box">
<div class="modal-title">🚀 Запустить интервью</div>
<div class="modal-field">
<label class="modal-label">Фаза проекта</label>
<select id="modal-phase" class="modal-select">
<option value="discovery">Диагностика AS-IS (10 вопросов)</option>
<option value="audit">Аудит (5 вопросов)</option>
<option value="design">TO-BE Проектирование (3 вопроса)</option>
<option value="impl">Внедрение (3 вопроса)</option>
<option value="support">Поддержка (2 вопроса)</option>
</select>
</div>
<div class="modal-field">
<label class="modal-label">Формат</label>
<div class="modal-radio-row">
<label class="modal-radio-lbl" id="mode-qa-lbl">
<input type="radio" name="modal-mode" value="qa" checked style="accent-color:#047857">
📋 Анкета <span style="font-size:11px;color:#94A3B8">(бесплатно)</span>
</label>
<label class="modal-radio-lbl" id="mode-dialog-lbl">
<input type="radio" name="modal-mode" value="dialog" style="accent-color:#047857">
🤖 ИИ-Диалог <span style="font-size:11px;color:#94A3B8">(API)</span>
</label>
</div>
</div>
<div class="modal-field">
<label class="modal-label">Контекст / первичное обращение <span style="font-weight:400;text-transform:none;color:#CBD5E1">(необязательно)</span></label>
<textarea id="modal-context" class="modal-textarea" placeholder="Кратко: откуда клиент, важный контекст для интервью…"></textarea>
</div>
<div class="modal-tip">Клиент увидит предложение пройти интервью при следующем открытии чата.</div>
<div class="modal-footer">
<button class="btn-modal-cancel" onclick="closeLaunchModal()">Отмена</button>
<button class="btn-modal-ok" id="launch-btn" onclick="launchInterview()">Запустить</button>
</div>
</div>
</div>
<!-- ══ MODAL: Suggest Questions ══ -->
<div class="suggest-overlay" id="suggest-modal" onclick="if(event.target===this)closeSuggestModal()">
<div class="suggest-box">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
<div style="font-family:'Montserrat',sans-serif;font-size:15px;font-weight:800;color:#0f172a">🤖 AI-вопросы по чату</div>
<button onclick="closeSuggestModal()" style="background:#F1F5F9;border:none;width:28px;height:28px;border-radius:7px;cursor:pointer;font-size:15px;color:#64748B">×</button>
</div>
<div class="modal-field">
<label class="modal-label">Фаза</label>
<select id="suggest-phase" class="modal-select">
<option value="discovery">Диагностика AS-IS</option>
<option value="audit">Аудит</option>
<option value="design">TO-BE Проектирование</option>
<option value="impl">Внедрение</option>
<option value="support">Поддержка</option>
</select>
</div>
<div id="suggest-result" style="min-height:60px"></div>
<div class="modal-footer">
<button class="btn-modal-cancel" onclick="closeSuggestModal()">Закрыть</button>
<button class="btn-modal-blue" id="run-suggest-btn" onclick="runSuggest()">✨ Сформировать</button>
</div>
</div>
</div>
<script>
const CODE = '{{code}}';
const INIT_TYPE = '{{client_type}}';
const CARD = {{card_json}};
// ── Prefill ──────────────────────────────────────────────────────────────────
document.getElementById('f-name').value = '{{name}}';
document.getElementById('f-contract').value = '{{contract_no}}';
document.getElementById('f-email').value = '{{email}}';
document.getElementById('f-notes').value = CARD.notes || '';
document.getElementById('f-status').value = '{{status}}' || 'new';
document.getElementById('f-need').value = '{{need_text}}';
document.getElementById('f-work-format').value = '{{work_format}}';
document.getElementById('f-priority-override').value = '{{priority_override}}';
const _initPhase = CARD.project_phase || '';
if(_initPhase){ document.getElementById('f-project-phase').value = _initPhase; updateProcessFlow(_initPhase); }
Object.keys(CARD).forEach(k => {
const el = document.getElementById('fiz-'+k) || document.getElementById('yur-'+k);
if(el) el.value = CARD[k] || '';
});
// ── Hero ─────────────────────────────────────────────────────────────────────
function updateHero(){
const name = document.getElementById('f-name').value || CODE;
document.getElementById('hero-name').textContent = name;
document.getElementById('sb-name').textContent = name;
const initials = name.split(' ').map(w=>w[0]||'').join('').slice(0,2).toUpperCase() || CODE.slice(0,2);
document.getElementById('hero-avatar').textContent = initials;
const statusLabels = {new:'🔵 Лид',in_progress:'🟡 В работе',proposal:'🟠 Предложение',active:'🟢 Работаем',one_time:'🟣 Разовая',rejected:'🔴 Отказ'};
const statusColors = {new:'#EFF6FF:#1D4ED8',in_progress:'#FFFBEB:#D97706',proposal:'#FFF7ED:#EA580C',active:'#ECFDF5:#047857',one_time:'#F5F3FF:#7C3AED',rejected:'#FEF2F2:#DC2626'};
const st = document.getElementById('f-status').value;
const [sbg,stxt] = (statusColors[st]||'#F1F5F9:#64748B').split(':');
const prioMap = {urgent:'🔴 Срочно',high:'🟠 Важно',normal:'🟡 Обычный',low:'⚪ Низкий','':`⚙️ Авто`};
const prio = document.getElementById('f-priority-override').value;
const phase = document.getElementById('f-project-phase').value;
const phaseLabels = {discovery:'AS-IS',audit:'Аудит',design:'TO-BE',impl:'Внедрение',support:'Сопровождение'};
let html = `<span class="badge" style="background:${sbg};color:${stxt}">${statusLabels[st]||st}</span>`;
if(prio) html += `<span class="badge" style="background:#F1F5F9;color:#64748B">${prioMap[prio]}</span>`;
if(phase) html += `<span class="badge" style="background:#ECFDF5;color:#047857;border:1px solid #A7F3D0">📍 ${phaseLabels[phase]||phase}</span>`;
document.getElementById('hero-badges').innerHTML = html;
// type chip in breadcrumb
const t = currentType;
document.getElementById('hdr-type-chip').innerHTML = `<span style="font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:${t==='fiz'?'#F0FDF4':'#EFF6FF'};color:${t==='fiz'?'#059669':'#1D4ED8'};margin-left:8px">${t==='fiz'?'Физ. лицо':'Юр. лицо'}</span>`;
}
document.getElementById('f-status').addEventListener('change', ()=>{toggleWorkFormat();updateHero();});
document.getElementById('f-priority-override').addEventListener('change', updateHero);
document.getElementById('f-project-phase').addEventListener('change', updateHero);
document.getElementById('f-name').addEventListener('input', updateHero);
// ── Type ─────────────────────────────────────────────────────────────────────
let currentType = INIT_TYPE || 'fiz';
function setType(t){
currentType = t;
document.getElementById('section-fiz').style.display = t==='fiz'?'':'none';
document.getElementById('section-yur').style.display = t==='yur'?'':'none';
document.getElementById('btn-fiz').classList.toggle('active', t==='fiz');
document.getElementById('btn-yur').classList.toggle('active', t==='yur');
updateHero();
}
setType(currentType);
function toggleWorkFormat(){
const s = document.getElementById('f-status').value;
document.getElementById('wformat-field').style.display = (s==='active'||s==='proposal')?'':'none';
}
toggleWorkFormat();
// ── Tabs ─────────────────────────────────────────────────────────────────────
const TAB_IDS = ['work','interview','details','history','finance'];
function switchTab(id){
TAB_IDS.forEach(t=>{
document.getElementById('tab-'+t).classList.toggle('active', t===id);
document.getElementById('tbtn-'+t).classList.toggle('active', t===id);
const navItem = document.querySelector(`.nav-item[onclick*="'${t}'"]`);
if(navItem) navItem.classList.toggle('active', t===id);
});
if(id==='finance') loadFinances();
}
// ── Collapse (passport) ──────────────────────────────────────────────────────
function togglePassport(){
const body = document.getElementById('passport-body');
const arrow = document.getElementById('passport-arrow');
const open = body.classList.toggle('open');
arrow.classList.toggle('open', open);
}
// ── Process flow ──────────────────────────────────────────────────────────────
const PHASE_META = {
discovery:{label:'Диагностика',short:'AS-IS',bg:'#DBEAFE',color:'#1D4ED8',pct:20},
audit: {label:'Аудит', short:'Аудит', bg:'#FEF3C7',color:'#D97706',pct:40},
design: {label:'Проектирование',short:'TO-BE',bg:'#F3E8FF',color:'#7C3AED',pct:60},
impl: {label:'Внедрение', short:'CRM', bg:'#ECFDF5',color:'#047857',pct:80},
support: {label:'Сопровождение',short:'6мес',bg:'#F1F5F9',color:'#64748B',pct:100},
};
const PHASE_KEYS = ['discovery','audit','design','impl','support'];
function updateProcessFlow(phase){
const wrap = document.getElementById('process-flow-wrap');
const bar = document.getElementById('process-flow-bar');
const pctEl = document.getElementById('process-flow-pct');
const stepsEl= document.getElementById('process-flow-steps');
if(!phase){wrap.style.display='none';return;}
wrap.style.display='block';
const pm = PHASE_META[phase];
bar.style.width = pm.pct+'%';
pctEl.textContent = pm.pct+'%';
const activeIdx = PHASE_KEYS.indexOf(phase);
stepsEl.innerHTML = PHASE_KEYS.map((k,i)=>{
const m=PHASE_META[k];
const isDone=i<activeIdx, isActive=i===activeIdx;
const cbg=isDone?'#ECFDF5':isActive?'#047857':'#F9FAFB';
const cc =isDone?'#047857':isActive?'#fff':'#9CA3AF';
const cb =isDone?'2px solid #10B981':isActive?'none':'2px solid #E5E7EB';
const num=isDone?`<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="${cc}" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>`
:`<span style="font-size:11px;font-weight:800;color:${cc}">${i+1}</span>`;
return `<div style="flex:1;display:flex;flex-direction:column;align-items:center;position:relative;min-width:0">
${i>0?`<div style="position:absolute;left:-50%;top:17px;right:50%;height:1.5px;background:${isDone?'#10B981':'#E5E7EB'}"></div>`:''}
<div style="width:34px;height:34px;border-radius:50%;background:${cbg};border:${cb};display:flex;align-items:center;justify-content:center;z-index:1;box-shadow:${isActive?'0 4px 12px rgba(4,120,87,.3)':'none'}">${num}</div>
<div style="font-size:10px;font-weight:${isActive?700:500};color:${isActive?'#047857':isDone?'#6B7280':'#9CA3AF'};text-align:center;margin-top:5px;line-height:1.2">${m.short}</div>
</div>`;
}).join('');
}
// ── collectCard ───────────────────────────────────────────────────────────────
function collectCard(){
const prefix = currentType==='fiz'?'fiz-':'yur-';
const card = { notes: document.getElementById('f-notes').value, project_phase: document.getElementById('f-project-phase').value };
document.querySelectorAll('[id^="'+prefix+'"]').forEach(el=>{ card[el.id.replace(prefix,'')] = el.value; });
return card;
}
// ── Tasks ─────────────────────────────────────────────────────────────────────
const PRIO_LABELS = {high:'🔴 Важная',medium:'🟡 Обычная',low:'⚪ Низкая'};
async function loadTasks(){
const r = await fetch('/consult/api/tasks/'+CODE,{credentials:'include'});
if(!r.ok) return;
renderTasks(await r.json());
}
function renderTasks(tasks){
const open=tasks.filter(t=>!t.done), done=tasks.filter(t=>t.done);
document.getElementById('tasks-open-cnt').textContent = open.length?open.length+' открытых':'';
const el=document.getElementById('tasks-list');
if(!tasks.length){el.innerHTML='<div style="color:#94A3B8;font-size:13px;padding:4px 0">Задач нет</div>';return;}
el.innerHTML=[...open,...done].map(t=>`
<div class="task-row">
<input type="checkbox" class="task-cb" ${t.done?'checked':''} onchange="toggleTask(${t.id},this.checked)">
<span class="task-text ${t.done?'done-text':''}">${t.text}</span>
<span class="task-prio-tag" style="background:#F1F5F9;color:#64748B">${PRIO_LABELS[t.priority]||''}</span>
<button class="task-del" onclick="deleteTask(${t.id})">×</button>
</div>`).join('');
}
async function addTask(){
const inp=document.getElementById('task-input');
const text=inp.value.trim(); if(!text)return;
inp.value='';
await fetch('/consult/api/tasks',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify({code:CODE,text,priority:document.getElementById('task-prio').value})});
await loadTasks();
}
async function toggleTask(id,done){
await fetch('/consult/api/tasks/'+id+'/done',{method:'PATCH',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify({done})});
await loadTasks();
}
async function deleteTask(id){
if(!confirm('Удалить задачу?'))return;
await fetch('/consult/api/tasks/'+id,{method:'DELETE',credentials:'include'});
await loadTasks();
}
loadTasks();
// ── Interview ─────────────────────────────────────────────────────────────────
const PHASE_LABELS={discovery:'Диагностика AS-IS',audit:'Аудит',design:'TO-BE',impl:'Внедрение',support:'Поддержка'};
const STATUS_COLORS={pending:{bg:'#FEF3C7',color:'#D97706',text:'Ожидает'},active:{bg:'#DBEAFE',color:'#1D4ED8',text:'Активно'},completed:{bg:'#ECFDF5',color:'#047857',text:'Завершено'}};
async function loadInterview(){
const r=await fetch('/consult/api/admin/interview/'+CODE,{credentials:'include'});
if(!r.ok)return;
const sessions=await r.json();
const body=document.getElementById('interview-body');
const chip=document.getElementById('interview-status-chip');
if(!sessions.length){body.innerHTML='<div class="iv-empty">Интервью не запущено</div>';chip.innerHTML='';return;}
const latest=sessions[0], sc=STATUS_COLORS[latest.status]||STATUS_COLORS.pending;
chip.innerHTML=`<span style="font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:${sc.bg};color:${sc.color};margin-left:8px">${sc.text}</span>`;
body.innerHTML=sessions.map(s=>renderSessionCard(s)).join('');
}
function renderSessionCard(s){
const sc=STATUS_COLORS[s.status]||STATUS_COLORS.pending;
const blocks={};
s.questions.forEach(q=>{if(!blocks[q.block])blocks[q.block]=[];blocks[q.block].push(q);});
const blocksHtml=Object.entries(blocks).map(([block,qs])=>`
<div style="margin-bottom:14px">
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#94A3B8;margin-bottom:7px;padding-bottom:3px;border-bottom:1px solid #F1F5F9">${block}</div>
${qs.map(q=>`<div style="margin-bottom:9px">
<div style="font-size:12px;font-weight:600;color:#475569;margin-bottom:3px">${q.text}</div>
${q.answer?`<div style="font-size:13px;color:#0f172a;background:#F8FAFC;border:1px solid #E2E8F0;border-radius:7px;padding:7px 10px;line-height:1.5">${escHtml(q.answer)}</div>`
:`<div style="font-size:12px;color:#CBD5E1;font-style:italic">Не отвечено</div>`}
</div>`).join('')}
</div>`).join('');
return `<div class="iv-session">
<div class="iv-session-hdr">
<div>
<span style="font-weight:700;font-size:13px;color:#0f172a">${PHASE_LABELS[s.phase]||s.phase}</span>
<span style="font-size:11px;color:#94A3B8;margin-left:8px">${s.mode==='dialog'?'🤖 ИИ-Диалог':'📋 Анкета'}</span>
<span style="font-size:11px;color:#94A3B8;margin-left:6px">${s.answered}/${s.total}</span>
</div>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:${sc.bg};color:${sc.color}">${sc.text}</span>
<button onclick="deleteInterview(${s.session_id})" style="background:#FEF2F2;border:1px solid #FECACA;color:#DC2626;padding:2px 8px;border-radius:6px;cursor:pointer;font-size:11px">🗑</button>
</div>
</div>
<div style="padding:16px">${s.answered>0?blocksHtml:'<div class="iv-empty">Ответов пока нет — ожидаем клиента</div>'}</div>
</div>`;
}
function escHtml(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>');}
function openLaunchModal(){document.getElementById('launch-modal').style.display='flex';}
function closeLaunchModal(){document.getElementById('launch-modal').style.display='none';}
async function launchInterview(){
const phase=document.getElementById('modal-phase').value;
const mode=document.querySelector('input[name="modal-mode"]:checked').value;
const context=document.getElementById('modal-context').value.trim();
const btn=document.getElementById('launch-btn');
btn.disabled=true;btn.textContent='Запуск…';
try{
const r=await fetch('/consult/api/admin/interview/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({code:CODE,phase,mode,context}),credentials:'include'});
const d=await r.json();
closeLaunchModal();
if(d.ok){document.getElementById('modal-context').value='';await loadInterview();}
else if(d.error==='already_active') alert('У клиента уже есть активное интервью для этой фазы.');
}catch(e){alert('Ошибка запуска.');}
btn.disabled=false;btn.textContent='Запустить';
}
async function deleteInterview(sessionId){
if(!confirm('Удалить интервью и все ответы?'))return;
await fetch('/consult/api/admin/interview/'+sessionId,{method:'DELETE',credentials:'include'});
await loadInterview();
}
loadInterview();
// ── Intake Brief ──────────────────────────────────────────────────────────────
const INTAKE_STEPS_META=[
{icon:'🏢',title:'Бизнес',fields:['biz_sphere','biz_size','biz_model']},
{icon:'🔥',title:'Боль',fields:['pain_main','pain_cost','pain_tried']},
{icon:'⚙️',title:'Процесс',fields:['proc_steps','proc_bottleneck','proc_tools']},
{icon:'🎯',title:'Цель',fields:['goal_result','goal_metrics','goal_budget']},
{icon:'📍',title:'Контекст',fields:['ctx_team','ctx_deadline','ctx_decider']},
];
const INTAKE_LABELS={
biz_sphere:'Сфера',biz_size:'Размер',biz_model:'Бизнес-модель',
pain_main:'Проблема',pain_cost:'Цена боли',pain_tried:'Пробовали',
proc_steps:'Шаги',proc_bottleneck:'Узкое место',proc_tools:'Инструменты',
goal_result:'Результат',goal_metrics:'Метрики',goal_budget:'Бюджет',
ctx_team:'Команда',ctx_deadline:'Сроки',ctx_decider:'ЛПР',
};
async function loadIntakeBrief(){
try{
const r=await fetch('/consult/api/admin/intake/'+CODE,{credentials:'include'});
if(!r.ok)return;
const data=await r.json();
const body=document.getElementById('intake-body');
const chip=document.getElementById('intake-status-chip');
const allKeys=Object.keys(INTAKE_LABELS);
const filled=allKeys.filter(k=>data[k]&&data[k].value&&data[k].value.trim()).length;
if(!filled){body.innerHTML='<div class="iv-empty">Клиент ещё не заполнил бриф</div>';chip.innerHTML='';return;}
const pct=Math.round(filled/allKeys.length*100);
const isComplete=filled===allKeys.length;
chip.innerHTML=`<span style="font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:${isComplete?'#ECFDF5':'#FEF3C7'};color:${isComplete?'#047857':'#D97706'}">${isComplete?'✅ Заполнен':pct+'% заполнено'}</span>`;
body.innerHTML=`
<div style="height:4px;background:#F1F5F9;border-radius:4px;overflow:hidden;margin-bottom:16px">
<div style="height:100%;width:${pct}%;background:linear-gradient(90deg,#047857,#10B981);border-radius:4px"></div>
</div>
<div class="intake-grid">
${INTAKE_STEPS_META.map(step=>{
const hasAny=step.fields.some(k=>data[k]&&data[k].value&&data[k].value.trim());
if(!hasAny) return `<div class="intake-block empty"><div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#94A3B8;margin-bottom:6px">${step.icon} ${step.title}</div><div style="font-size:12px;color:#CBD5E1;font-style:italic">Не заполнено</div></div>`;
return `<div class="intake-block"><div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#047857;margin-bottom:10px">${step.icon} ${step.title}</div>
${step.fields.map(k=>{
const v=data[k];
if(!v||!v.value||!v.value.trim()) return `<div style="margin-bottom:5px"><span style="font-size:11px;color:#CBD5E1">${INTAKE_LABELS[k]}: <i>не указано</i></span></div>`;
return `<div style="margin-bottom:9px"><div class="intake-field-label">${INTAKE_LABELS[k]}</div><div class="intake-field-val">${escHtml(v.value)}</div></div>`;
}).join('')}
</div>`;
}).join('')}
</div>`;
}catch(e){}
}
loadIntakeBrief();
// ── Events ────────────────────────────────────────────────────────────────────
async function loadEvents(){
try{
const r=await fetch('/consult/api/admin/events/'+CODE,{credentials:'include'});
if(!r.ok)return;
const events=await r.json();
document.getElementById('events-count').textContent=events.length?events.length+' событий':'';
const list=document.getElementById('events-list');
if(!events.length){list.innerHTML='<div style="color:#94A3B8;font-size:13px;padding:4px 0">Активности пока нет</div>';return;}
list.innerHTML=events.map(e=>`
<div class="event-row">
<div class="event-icon">${evIcon(e.event_type)}</div>
<div><div class="event-title">${escHtml(e.title)}</div><div class="event-ts">${fmtTime(e.created_at)}</div></div>
</div>`).join('');
}catch(e){}
}
function evIcon(t){
if(t.startsWith('intake')) return t==='intake_completed'?'✅':'📝';
if(t.startsWith('interview')) return '📋';
if(t==='admin_note') return '📌';
if(t==='chat_message') return '💬';
if(t==='contract_viewed') return '📄';
return '📎';
}
function fmtTime(ts){
if(!ts)return'';
try{const d=new Date(ts.replace(' ','T'));return d.toLocaleDateString('ru',{day:'2-digit',month:'2-digit',year:'numeric'})+' '+d.toLocaleTimeString('ru',{hour:'2-digit',minute:'2-digit'});}catch(e){return ts;}
}
async function addNote(){
const inp=document.getElementById('note-input');
const note=inp.value.trim();if(!note)return;
inp.value='';
await fetch('/consult/api/admin/events/'+CODE+'/note',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify({note})});
await loadEvents();
}
loadEvents();
// ── Suggest Questions ─────────────────────────────────────────────────────────
function openSuggestModal(){
document.getElementById('suggest-result').innerHTML='<div style="font-size:13px;color:#94A3B8;text-align:center;padding:20px 0">Выберите фазу и нажмите «Сформировать»</div>';
document.getElementById('suggest-modal').style.display='flex';
}
function closeSuggestModal(){document.getElementById('suggest-modal').style.display='none';}
async function runSuggest(){
const phase=document.getElementById('suggest-phase').value;
const btn=document.getElementById('run-suggest-btn');
const res=document.getElementById('suggest-result');
btn.disabled=true;btn.textContent=' Анализирую';
res.innerHTML='<div style="font-size:13px;color:#64748B;padding:16px 0;text-align:center">AI читает переписку</div>';
try{
const r=await fetch('/consult/api/admin/interview/suggest-questions',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify({code:CODE,phase})});
const d=await r.json();
if(d.ok&&d.questions&&d.questions.length){
res.innerHTML=`<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:#94A3B8;margin-bottom:10px">${d.questions.length} персонализированных вопросов</div>
${d.questions.map((q,i)=>`<div style="border:1.5px solid #E5E7EB;border-radius:10px;padding:11px 13px;margin-bottom:7px;background:#FAFAFA">
<div style="font-size:13px;font-weight:600;color:#0f172a;margin-bottom:3px">${i+1}. ${escHtml(q.text)}</div>
${q.why?`<div style="font-size:11px;color:#94A3B8">💡 ${escHtml(q.why)}</div>`:''}
</div>`).join('')}
<div style="background:#EFF6FF;border:1px solid #BFDBFE;border-radius:8px;padding:9px 12px;margin-top:8px;font-size:12px;color:#1D4ED8">Скопируйте нужные вопросы для контекста интервью.</div>`;
} else if(d.error==='no_data'){
res.innerHTML='<div style="background:#FEF3C7;border:1px solid #FDE68A;border-radius:8px;padding:11px;font-size:13px;color:#92400E">У клиента ещё нет переписки. Попросите клиента написать первые сообщения.</div>';
} else {
res.innerHTML=`<div style="background:#FEF2F2;border:1px solid #FECACA;border-radius:8px;padding:11px;font-size:13px;color:#991B1B">Ошибка: ${escHtml(d.error||'неизвестная')}</div>`;
}
}catch(e){res.innerHTML='<div style="background:#FEF2F2;border:1px solid #FECACA;border-radius:8px;padding:11px;font-size:13px;color:#991B1B">Ошибка подключения.</div>';}
btn.disabled=false;btn.textContent='✨ Сформировать';
}
// ── Finances ──────────────────────────────────────────────────────────────────
const TARIFF_DEFAULTS = {express:0, audit:150000, impl:350000};
const TARIFF_NAMES = {express:'Экспресс-диагностика', audit:'Полный аудит + план', impl:'Внедрение под ключ'};
function onTariffChange(){
const t = document.getElementById('fin-tariff').value;
document.getElementById('fin-price').value = TARIFF_DEFAULTS[t] ?? 0;
}
async function loadFinances(){
try{
const r = await fetch('/consult/api/admin/finances/'+CODE, {credentials:'include'});
if(!r.ok) return;
const d = await r.json();
renderFinances(d);
}catch(e){}
}
function fmtRub(n){ return n===0?'0 ₽':(n.toLocaleString('ru')+' ₽'); }
function renderFinances(d){
const c = d.contract;
const chip = document.getElementById('contract-chip');
const signInfo = document.getElementById('contract-sign-info');
// Contract chip
const statusMap = {
draft: {bg:'#F1F5F9',color:'#64748B',text:'Черновик'},
sent: {bg:'#FEF3C7',color:'#D97706',text:'📨 Отправлен'},
signed: {bg:'#ECFDF5',color:'#047857',text:'✅ Подписан'},
};
if(c){
const s = statusMap[c.status] || statusMap.draft;
chip.innerHTML = `<span style="font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:${s.bg};color:${s.color};margin-left:8px">${s.text}</span>`;
// Prefill form
if(c.tariff) document.getElementById('fin-tariff').value = c.tariff;
if(c.price) document.getElementById('fin-price').value = c.price;
// Signed info
if(c.status === 'signed' && c.signed_at){
signInfo.style.display = 'block';
const sigImgHtml = c.signature_image
? `<div style="margin-top:10px"><div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#64748B;margin-bottom:6px">Подпись клиента</div><div style="border:1.5px solid #A7F3D0;border-radius:8px;background:#fff;display:inline-block;padding:4px"><img src="${c.signature_image}" style="max-width:280px;height:auto;display:block;border-radius:4px"></div></div>`
: '';
signInfo.innerHTML = `✅ <b>Подписан:</b> ${fmtTime(c.signed_at)} · IP: ${c.sign_ip||'—'}${sigImgHtml}`;
} else {
signInfo.style.display = 'none';
}
} else {
chip.innerHTML = `<span style="font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:#F1F5F9;color:#94A3B8;margin-left:8px">Не создан</span>`;
}
// Payments
const payList = document.getElementById('payments-list');
const payChip = document.getElementById('pay-summary-chip');
if(!d.payments || !d.payments.length){
payList.innerHTML = '<div style="color:#94A3B8;font-size:13px">Договор ещё не отправлен клиенту — график платежей сформируется автоматически</div>';
payChip.textContent = '';
return;
}
const paidColor = d.balance===0 ? '#047857' : '#D97706';
payChip.textContent = `${fmtRub(d.paid)} из ${fmtRub(d.total)}`;
payChip.style.color = paidColor;
payList.innerHTML = `
<div style="display:grid;grid-template-columns:1fr 100px 100px 90px 130px;gap:0;border:1.5px solid #E5E7EB;border-radius:10px;overflow:hidden">
<div style="display:contents;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#64748B">
<div style="padding:9px 14px;background:#F8FAFC;border-bottom:1px solid #E5E7EB">Этап</div>
<div style="padding:9px 10px;background:#F8FAFC;border-bottom:1px solid #E5E7EB;text-align:right">Сумма</div>
<div style="padding:9px 10px;background:#F8FAFC;border-bottom:1px solid #E5E7EB;text-align:right">Оплачено</div>
<div style="padding:9px 10px;background:#F8FAFC;border-bottom:1px solid #E5E7EB;text-align:center">Статус</div>
<div style="padding:9px 10px;background:#F8FAFC;border-bottom:1px solid #E5E7EB;text-align:center">Действие</div>
</div>
${d.payments.map((p,i)=>{
const last = i===d.payments.length-1;
const isPaid = p.status==='paid';
const rowBg = isPaid ? '#F0FDF4' : '#fff';
const statusBadge = isPaid
? '<span style="font-size:10px;font-weight:700;padding:2px 7px;border-radius:10px;background:#ECFDF5;color:#047857">✓ Оплачен</span>'
: '<span style="font-size:10px;font-weight:700;padding:2px 7px;border-radius:10px;background:#FEF3C7;color:#D97706">Ожидает</span>';
const border = last?'':'border-bottom:1px solid #E5E7EB';
const action = isPaid
? `<button onclick="markPayment(${p.id},0)" style="font-size:11px;color:#94A3B8;background:none;border:none;cursor:pointer;padding:0">↩ Отменить</button>`
: `<button onclick="openMarkPayModal(${p.id},${p.amount})" style="font-size:11px;font-weight:600;color:#fff;background:#047857;border:none;padding:4px 10px;border-radius:6px;cursor:pointer">✓ Оплачен</button>`;
const noteSpan = p.note ? `<div style="font-size:11px;color:#94A3B8;margin-top:2px">${p.note}</div>` : '';
return `<div style="display:contents">
<div style="padding:10px 14px;background:${rowBg};${border}"><div style="font-size:13px;font-weight:600;color:#0f172a">${p.phase_label}</div>${noteSpan}${p.paid_at?`<div style="font-size:10px;color:#94A3B8">Оплачен: ${fmtTime(p.paid_at)}</div>`:''}</div>
<div style="padding:10px 10px;background:${rowBg};${border};text-align:right;font-size:13px;font-weight:600;color:#0f172a">${fmtRub(p.amount)}</div>
<div style="padding:10px 10px;background:${rowBg};${border};text-align:right;font-size:13px;color:${isPaid?'#047857':'#94A3B8'}">${fmtRub(p.paid_amount)}</div>
<div style="padding:10px 10px;background:${rowBg};${border};text-align:center;display:flex;align-items:center;justify-content:center">${statusBadge}</div>
<div style="padding:10px 10px;background:${rowBg};${border};text-align:center;display:flex;align-items:center;justify-content:center">${action}</div>
</div>`;
}).join('')}
<div style="display:contents;font-size:13px;font-weight:700;color:#0f172a">
<div style="padding:10px 14px;background:#F8FAFC;border-top:2px solid #E5E7EB">ИТОГО</div>
<div style="padding:10px 10px;background:#F8FAFC;border-top:2px solid #E5E7EB;text-align:right">${fmtRub(d.total)}</div>
<div style="padding:10px 10px;background:#F8FAFC;border-top:2px solid #E5E7EB;text-align:right;color:#047857">${fmtRub(d.paid)}</div>
<div style="padding:10px 10px;background:#F8FAFC;border-top:2px solid #E5E7EB;text-align:center;font-size:12px;color:${paidColor}">${d.balance===0?'✅ Закрыт':''+fmtRub(d.balance)}</div>
<div style="padding:10px 10px;background:#F8FAFC;border-top:2px solid #E5E7EB"></div>
</div>
</div>`;
}
async function saveContract(action='save'){
const tariff = document.getElementById('fin-tariff').value;
const price = parseInt(document.getElementById('fin-price').value)||0;
const msg = document.getElementById('contract-send-msg');
msg.textContent = action==='send'?'Отправляю…':'Сохраняю…'; msg.style.color='#94A3B8';
try{
const r = await fetch('/consult/api/admin/finances/'+CODE+'/contract', {
method:'POST', credentials:'include',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({tariff, tariff_name: TARIFF_NAMES[tariff]||tariff, price, action})
});
const d = await r.json();
if(d.ok){
msg.textContent = action==='send'?'✓ Договор отправлен клиенту':'✓ Сохранено'; msg.style.color='#059669';
setTimeout(()=>{msg.textContent='';}, 3000);
await loadFinances();
} else { msg.textContent='Ошибка'; msg.style.color='#DC2626'; }
}catch(e){ msg.textContent='Ошибка подключения'; msg.style.color='#DC2626'; }
}
function sendContract(){
if(!confirm('Отправить договор клиенту? Будет сформирован график платежей и клиент получит доступ к документу.')) return;
saveContract('send');
}
async function markPayment(payId, amount){
await fetch('/consult/api/admin/finances/'+CODE+'/payment/'+payId, {
method:'PATCH', credentials:'include',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({paid_amount: amount})
});
await loadFinances();
}
function openMarkPayModal(payId, amount){
const entered = prompt(`Введите оплаченную сумму (₽):\nПлановая: ${fmtRub(amount)}`, amount);
if(entered===null) return;
const paid = parseInt(entered)||0;
markPayment(payId, paid);
}
// ── Save ──────────────────────────────────────────────────────────────────────
async function saveCard(){
const status=document.getElementById('save-status');
status.textContent='Сохраняю…';status.className='save-status';
const payload={
client_type:currentType,
name:document.getElementById('f-name').value,
contract_no:document.getElementById('f-contract').value,
email:document.getElementById('f-email').value,
status:document.getElementById('f-status').value,
need_text:document.getElementById('f-need').value,
work_format:document.getElementById('f-work-format').value,
priority_override:document.getElementById('f-priority-override').value,
card:collectCard()
};
try{
const res=await fetch('/consult/api/admin/update-client/'+CODE,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload),credentials:'include'});
if(!res.ok) throw new Error(res.status);
status.textContent='✓ Сохранено';status.className='save-status ok';
updateHero();
setTimeout(()=>{status.textContent='';},3000);
}catch(e){status.textContent='✗ Ошибка';status.className='save-status err';}
}
// ── Init hero ─────────────────────────────────────────────────────────────────
updateHero();
</script>
</body>
</html>