import React, { useEffect, useMemo, useState } from "react"; // ABQD CRM — 1-file, preview-safe (JSX only) // ✅ Без shadcn/ui, без TS-синтаксиса (чтобы превью точно запускалось) // ✅ Montserrat + матовое стекло + Light/Dark (сохранение) // ✅ Канбан: 4 карточки вниз → следующий «этаж» вправо // ========================= // DEMO DATA // ========================= const demoStages = [ { key: "inbox", title: "Входящие", hint: "Новые касания и лиды" }, { key: "qual", title: "Квалификация", hint: "Понимаем цель/контекст" }, { key: "plan", title: "Стратегия", hint: "План доведения до результата" }, { key: "work", title: "В работе", hint: "Касания, созвоны, задачи" }, { key: "won", title: "Результат", hint: "Достигнута цель" }, ]; const demoLeads = [ { id: "L-1021", name: "Ольга М.", company: "Салон красоты", stage: "inbox", score: 62, goal: "Повысить повторные покупки", next: "Сценарий касаний на 14 дней", tags: ["NFC", "retail"], lastTouch: "NFC tap — меню услуг", touches7d: 5, }, { id: "L-1044", name: "Илья К.", company: "Автодетейлинг", stage: "qual", score: 74, goal: "Увеличить конверсию лид→запись", next: "Запуск WhatsApp-цепочки", tags: ["WhatsApp", "calls"], lastTouch: "Звонок — пропущен", touches7d: 9, }, { id: "L-1098", name: "Марина С.", company: "Онлайн‑школа", stage: "plan", score: 81, goal: "Систематизировать отдел продаж", next: "Карта метрик и ролей", tags: ["AI", "ops"], lastTouch: "Telegram — ответила", touches7d: 12, }, { id: "L-1106", name: "Дмитрий П.", company: "Юр. услуги", stage: "work", score: 69, goal: "Ускорить обработку заявок", next: "Включить AI‑телефонию", tags: ["Telegram", "AI"], lastTouch: "NFC tap — прайс", touches7d: 7, }, { id: "L-1120", name: "Алексей В.", company: "Студия дизайна", stage: "won", score: 90, goal: "Упаковать продукт и воронку", next: "Сопровождение 30 дней", tags: ["NFC", "brand"], lastTouch: "Договор — подписан", touches7d: 18, }, // ещё для демонстрации «4 вниз → вправо» { id: "L-1131", name: "Елена Р.", company: "Стоматология", stage: "inbox", score: 58, goal: "Стабилизировать поток первичек", next: "Скрипт первого касания + FAQ", tags: ["NFC", "calls"], lastTouch: "WhatsApp — прочитано", touches7d: 4, }, { id: "L-1137", name: "Роман Г.", company: "Кофейня", stage: "inbox", score: 71, goal: "Увеличить средний чек", next: "Шаблон оффера + QR-постер", tags: ["retail", "ops"], lastTouch: "NFC tap — меню", touches7d: 6, }, { id: "L-1142", name: "Светлана Н.", company: "Фитнес-студия", stage: "qual", score: 66, goal: "Дожим до абонемента", next: "Цепочка: польза → кейс → оффер", tags: ["WhatsApp"], lastTouch: "Telegram — вопрос", touches7d: 8, }, ]; function scoreLabel(score) { if (score >= 85) return { text: "Горячий", tone: "hot" }; if (score >= 70) return { text: "Тёплый", tone: "warm" }; if (score >= 55) return { text: "Холодный", tone: "cold" }; return { text: "Слабый", tone: "weak" }; } // ========================= // Tiny runtime checks (safe) // ========================= (function runtimeChecks() { try { console.assert(scoreLabel(90).tone === "hot"); console.assert(scoreLabel(74).tone === "warm"); console.assert(scoreLabel(62).tone === "cold"); console.assert(scoreLabel(10).tone === "weak"); } catch { // ignore } })(); function cx() { return Array.prototype.slice.call(arguments).filter(Boolean).join(" "); } function useStoredTheme() { const [theme, setTheme] = useState("dark"); useEffect(() => { try { const saved = window.localStorage.getItem("abqd_crm_theme"); if (saved === "dark" || saved === "light") { setTheme(saved); return; } const prefersLight = typeof window !== "undefined" && !!window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches; setTheme(prefersLight ? "light" : "dark"); } catch { // ignore } }, []); const set = (t) => { setTheme(t); try { window.localStorage.setItem("abqd_crm_theme", t); } catch { // ignore } }; return [theme, set]; } // ========================= // CSS // ========================= const css = ` @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800&display=swap'); [data-theme='dark']{ --bg:#060607; --fg:#f4f4f5; --muted:rgba(244,244,245,.62); --muted2:rgba(244,244,245,.45); --bd:rgba(244,244,245,.16); --bd2:rgba(244,244,245,.10); --panel:rgba(24,24,27,.34); --chip:rgba(9,9,11,.24); --chip2:rgba(9,9,11,.34); --shadow:rgba(0,0,0,.50); --shine:rgba(255,255,255,.06); } [data-theme='light']{ --bg:#f5f6f8; --fg:#0b0b0f; --muted:rgba(11,11,15,.62); --muted2:rgba(11,11,15,.42); --bd:rgba(11,11,15,.14); --bd2:rgba(11,11,15,.10); --panel:rgba(255,255,255,.74); --chip:rgba(255,255,255,.60); --chip2:rgba(230,233,238,.70); --shadow:rgba(0,0,0,.16); --shine:rgba(0,0,0,.05); } .abqd-root{min-height:100vh;background:var(--bg);color:var(--fg);font-family:Montserrat,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;} .abqd-wrap{max-width:1720px;margin:0 auto;padding:16px 12px 28px;} .abqd-grid{display:grid;grid-template-columns:1fr;gap:14px;} @media (min-width:1024px){.abqd-grid{grid-template-columns:340px 1fr;}} .abqd-radius{border-radius:22px;} .abqd-glass{position:relative;border:1px solid var(--bd);background:linear-gradient(135deg,var(--panel),rgba(255,255,255,.03));backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);box-shadow:0 18px 60px var(--shadow);} .abqd-glow{pointer-events:none;position:absolute;inset:0;border-radius:22px;mask-image:radial-gradient(60% 60% at 50% 0%,#000,transparent);-webkit-mask-image:radial-gradient(60% 60% at 50% 0%,#000,transparent);} .abqd-glow::before{content:'';position:absolute;left:50%;top:-56px;transform:translateX(-50%);width:560px;height:140px;border-radius:999px;background:var(--shine);} .abqd-top{position:sticky;top:0;z-index:10;display:grid;gap:10px;} .abqd-bar{padding:12px 12px;} .abqd-barRow{display:flex;flex-wrap:wrap;align-items:center;gap:10px;} .abqd-brand{display:flex;align-items:center;gap:10px;min-width:240px;} .abqd-brandLogo{display:block;height:34px;width:auto;} .abqd-brandMeta{line-height:1.1;} .abqd-brandSub{font-size:12px;color:var(--muted);} .abqd-inputWrap{position:relative;flex:1;min-width:260px;} .abqd-input{width:100%;border-radius:18px;border:1px solid var(--bd);background:var(--chip2);padding:11px 12px 11px 34px;color:var(--fg);outline:none;} .abqd-input::placeholder{color:var(--muted2);} .abqd-inputIcon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted2);font-size:14px;} .abqd-btn{border-radius:16px;border:1px solid var(--bd);background:rgba(255,255,255,.08);color:var(--fg);padding:10px 12px;font-weight:700;font-size:13px;cursor:pointer;transition:transform .08s ease,filter .2s ease;} [data-theme='light'] .abqd-btn{background:rgba(0,0,0,.04);} .abqd-btn:hover{filter:brightness(1.06);} .abqd-btn:active{transform:translateY(1px);} .abqd-btn--secondary{background:var(--chip2);} .abqd-btn--sm{padding:7px 10px;font-size:12px;border-radius:14px;} .abqd-btn--full{width:100%;margin-top:10px;} .abqd-toggle{display:flex;align-items:center;border:1px solid var(--bd);background:var(--chip2);border-radius:16px;padding:3px;} .abqd-toggleBtn{border:0;background:transparent;color:var(--muted);font-weight:800;font-size:12px;padding:7px 10px;border-radius:13px;cursor:pointer;} .abqd-toggleBtn.is-active{background:var(--chip);color:var(--fg);} .abqd-switch{width:44px;height:26px;border-radius:999px;border:1px solid var(--bd);background:var(--chip2);position:relative;cursor:pointer;} .abqd-switchDot{position:absolute;left:4px;top:4px;width:18px;height:18px;border-radius:50%;background:var(--fg);opacity:.8;transition:left .18s ease,opacity .18s ease;} .abqd-switch.is-on .abqd-switchDot{left:22px;opacity:.95;} .abqd-pill{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--bd);background:var(--chip);padding:4px 10px;border-radius:999px;font-size:12px;font-weight:800;} .abqd-ibox{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:12px;border:1px solid var(--bd);background:var(--chip2);font-size:12px;opacity:.9;} .abqd-section{padding:14px;} .abqd-h2{font-size:18px;font-weight:900;letter-spacing:-.01em;} .abqd-h3{font-size:16px;font-weight:900;letter-spacing:-.01em;} .abqd-muted{color:var(--muted);} .abqd-strong{font-weight:900;} .abqd-trunc{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} .abqd-sep{height:1px;background:var(--bd2);} .abqd-stat{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;border-radius:18px;border:1px solid var(--bd);background:var(--chip);} .abqd-statTitle{font-weight:900;font-size:13px;} .abqd-statSub{font-size:12px;color:var(--muted);} .abqd-statValue{font-weight:900;font-size:14px;} .abqd-tabs{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;} .abqd-tab{border:1px solid var(--bd);background:var(--chip2);color:var(--muted);border-radius:999px;padding:8px 12px;font-weight:900;font-size:12px;cursor:pointer;} .abqd-tab.is-active{background:var(--chip);color:var(--fg);} .abqd-kanban{display:flex;gap:12px;overflow-x:auto;padding-bottom:6px;} .abqd-kanban::-webkit-scrollbar{height:8px;} .abqd-kanban::-webkit-scrollbar-thumb{background:rgba(127,127,127,.25);border-radius:999px;} .abqd-col{width:300px;min-width:300px;padding:12px;} .abqd-colHead{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;} .abqd-colTitle{font-weight:900;font-size:13px;} .abqd-colHint{font-size:12px;color:var(--muted);} .abqd-cardRail{margin-top:10px;overflow-x:auto;padding-bottom:6px;} .abqd-cardRail::-webkit-scrollbar{height:8px;} .abqd-cardRail::-webkit-scrollbar-thumb{background:rgba(127,127,127,.20);border-radius:999px;} .abqd-cardGrid{display:grid;grid-auto-flow:column;grid-template-rows:repeat(4,auto);gap:10px;align-content:start;} .abqd-cardGrid.is-compact{grid-auto-columns:250px;} .abqd-cardGrid:not(.is-compact){grid-auto-columns:270px;} .abqd-leadBtn{border:0;background:transparent;padding:0;cursor:pointer;text-align:left;} .abqd-lead{padding:10px;transition:filter .18s ease;} .abqd-lead:hover{filter:brightness(1.05);} .abqd-leadTop{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;} .abqd-leadTitle{display:flex;align-items:center;gap:8px;font-weight:900;font-size:13px;} .abqd-leadSub{font-size:12px;color:var(--muted);margin-top:2px;} .abqd-leadMeta{display:flex;gap:8px;margin-top:8px;} .abqd-metaChip{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--bd);background:var(--chip2);border-radius:999px;padding:6px 10px;font-size:12px;color:var(--fg);max-width:100%;} .abqd-leadNext{margin-top:10px;} .abqd-leadNextText{margin-top:2px;font-weight:800;font-size:13px;line-height:1.25;} .abqd-leadNextCompact{margin-top:8px;font-size:12px;color:var(--muted);} .abqd-tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;} .abqd-tag{border:1px solid var(--bd);background:var(--chip);border-radius:999px;padding:6px 10px;font-size:12px;font-weight:800;} .abqd-score{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--bd);background:var(--chip2);border-radius:999px;padding:6px 10px;font-weight:900;font-size:12px;white-space:nowrap;} .abqd-scoreDot{width:7px;height:7px;border-radius:50%;background:var(--fg);opacity:.75;} .abqd-score.tone-hot{border-color:rgba(16,185,129,.30);background:rgba(16,185,129,.10);} .abqd-score.tone-warm{border-color:rgba(245,158,11,.26);background:rgba(245,158,11,.10);} .abqd-score.tone-cold{border-color:rgba(161,161,170,.26);background:rgba(161,161,170,.10);} .abqd-score.tone-weak{border-color:rgba(244,63,94,.26);background:rgba(244,63,94,.10);} .abqd-score.is-compact{padding:6px 10px;} .abqd-drawerWrap{position:fixed;inset:0;z-index:50;} .abqd-drawerBackdrop{position:absolute;inset:0;background:rgba(0,0,0,.42);border:0;} .abqd-drawer{position:absolute;right:0;top:0;height:100%;width:min(560px,100%);padding:12px;} .abqd-drawerCard{height:100%;display:flex;flex-direction:column;overflow:hidden;} .abqd-drawerHead{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px;} .abqd-drawerBody{padding:14px;overflow:auto;} .abqd-box{border:1px solid var(--bd);background:var(--chip);border-radius:22px;padding:14px;margin-bottom:12px;} .abqd-boxTitle{font-weight:900;margin-bottom:6px;} .abqd-boxText{font-weight:800;font-size:13px;line-height:1.35;} .abqd-boxGrid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px;} .abqd-mini{border:1px solid var(--bd);background:var(--chip2);border-radius:18px;padding:10px;} .abqd-aiList{margin-top:10px;display:grid;gap:10px;} .abqd-aiRow{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid var(--bd);background:var(--chip2);border-radius:18px;padding:10px;} .abqd-list{display:grid;gap:8px;margin-top:8px;} .abqd-listRow{border:1px solid var(--bd);background:var(--chip2);border-radius:18px;padding:10px;font-weight:700;font-size:13px;} .abqd-foot{margin-top:10px;font-size:12px;color:var(--muted2);padding:0 4px;} `; // ========================= // UI atoms // ========================= function GlassCard(props) { const { children, className } = props; return (
{children}
); } function Pill(props) { return {props.children}; } function IconBox(props) { return {props.children}; } function Button(props) { const { children, variant, onClick, small, className, type } = props; const v = variant || "primary"; const t = type || "button"; return ( ); } function Input(props) { const { value, onChange, placeholder } = props; return (
onChange(e.target.value)} placeholder={placeholder} />
); } function ThemeToggle(props) { const { theme, setTheme } = props; return (
); } function Switch(props) { const { checked, onChange } = props; return ( ); } function ScoreBadge(props) { const { score, compact } = props; const s = scoreLabel(score); return ( {compact ? score : `${s.text} · ${score}`} ); } function LeadCard(props) { const { lead, compact, onOpen } = props; const s = scoreLabel(lead.score); return ( ); } function Drawer(props) { const { open, lead, onClose, compact } = props; if (!open || !lead) return null; return (
Цель клиента
{lead.goal}
ID
{lead.id}
Касаний 7д
{lead.touches7d}
AI · Next Best Action
Мок. В проде тут будет: резюме, риск‑факторы, следующий шаг, шаблоны сообщений.
{["Скрипт звонка", "WhatsApp‑шаблон", "Контент‑план", "Сегментация"].map((x) => (
{x}
))}
Лента касаний
{["NFC tap → страница", "Telegram → сообщение", "Звонок → попытка"].map((x) => (
{x}
))}
{!compact ? (
Теги
{lead.tags.map((t) => ( #{t} ))}
) : null}
); } export default function ABQDCrmPrototype() { const [theme, setTheme] = useStoredTheme(); const [query, setQuery] = useState(""); const [compact, setCompact] = useState(true); const [tab, setTab] = useState("board"); const [drawerOpen, setDrawerOpen] = useState(false); const [activeLead, setActiveLead] = useState(null); const filtered = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return demoLeads; return demoLeads.filter((l) => { const hay = [l.name, l.company, l.id, l.goal, l.next, l.lastTouch].concat(l.tags || []).join(" ").toLowerCase(); return hay.includes(q); }); }, [query]); const byStage = useMemo(() => { const map = { inbox: [], qual: [], plan: [], work: [], won: [] }; for (let i = 0; i < filtered.length; i++) { const l = filtered[i]; if (map[l.stage]) map[l.stage].push(l); } return map; }, [filtered]); const totals = useMemo(() => { const total = demoLeads.length; const touches = demoLeads.reduce((a, b) => a + (b.touches7d || 0), 0); const hot = demoLeads.filter((l) => l.score >= 85).length; const warm = demoLeads.filter((l) => l.score >= 70 && l.score < 85).length; const cold = total - hot - warm; return { total, touches, hot, warm, cold }; }, []); const openLead = (l) => { setActiveLead(l); setDrawerOpen(true); }; return (
abqd
CRM (прототип)
Канбан · NFC/касания · автоматизация · AI
Компактно Скор: 55+ Теги: любые Сорт: скор
Метрики (MVP)
Только по делу: лиды, касания, температура.
Лидов всего
Сейчас демо‑данные
{totals.total}
Касаний 7д
NFC + мессенджеры + звонки
{totals.touches}
Горячих
{totals.hot}
Тёплых
{totals.warm}
Холодных
{totals.cold}
План касаний
Дисциплина процесса + автоматизация → рост конверсии.
{["День 0: приветствие", "День 1: польза", "День 3: кейс", "День 7: оффер"].map((x, i) => (
{i + 1} {x}
))}
В проде: правила (если не ответил → следующий канал), лимиты, согласия, логирование, отчёт по эффективности.
AI‑агенты
Роль + доступы + ответственность + метрика. Никакой «магии».
{["Sales‑Coach", "Ops‑Analyst", "Marketing‑Planner", "Quality‑Control"].map((x) => (
AI {x}
demo
))}
Рабочие карточки
Канбан (процесс) + касания + интеграции. Без лишних модулей.
{tab === "board" ? (
{demoStages.map((stage) => (
{stage.title}
{stage.hint}
{(byStage[stage.key] || []).length}
{(byStage[stage.key] || []).map((lead) => ( ))}
))}
) : null} {tab === "touch" ? (
NFC‑касания
Счётчики, источники, страницы, устройства. Основа УТП.
{["NFC tap → прайс", "NFC tap → портфолио", "NFC tap → запись"].map((x) => (
{x}
))}
В проде: уникальные посетители, attribution, события в мессенджеры, конверсии по шагам.
Клиенты клиента (B2B2C)
Лиды/контакты твоих клиентов — их «боевые солдаты».
{demoLeads.slice(0, 4).map((l) => (
{l.company}
{l.lastTouch}
))}
) : null} {tab === "auto" ? (
Telegram/WhatsApp
{["Новый NFC tap → Telegram", "Нет ответа 24ч → WhatsApp", "Сделка выиграна → опрос"].map((x) => (
{x}
))}
AI‑телефония
MVP: «умный ассистент подтверждения записи» лучше, чем попытка сразу заменить менеджера.
n8n / Webhooks
{["Webhook: payment_succeeded", "Event: nfc_touch", "Action: create_task", "Action: send_message"].map( (x) => (
{x}
) )}
) : null} {tab === "arch" ? (
Модули продукта (roadmap)
Порядок сборки как у больших систем, но без лишней тяжести.
{["Core CRM: Contacts/Companies/Deals + Timeline событий", "Automation: Rules + цепочки касаний + webhooks", "AI Layer: Next Action + Summary + агенты по ролям", "Analytics: конверсия, time-to-win, прогноз"].map( (x) => (
{x}
) )}
Enterprise‑готовность
{["Custom objects (как Salesforce Objects)", "RBAC + аудит + журнал действий", "Мульти‑тенант + лимиты + биллинг", "Событийная модель (event log) → отчёты без потерь", "Маркетплейс интеграций (модули)"].map( (x) => (
{x}
) )}
Принцип: не копировать Salesforce «всё сразу». Сначала Core + события + автоматизация, потом AI‑роль‑агенты + отчёты.
) : null}
Прототип интерфейса. Без бэкенда. Дальше подключим API, роли, биллинг и события NFC/мессенджеров.
setDrawerOpen(false)} compact={compact} />
); }