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 (
);
}
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.name}
{lead.company}
Цель клиента
{lead.goal}
Касаний 7д
{lead.touches7d}
AI · Next Best Action
Мок. В проде тут будет: резюме, риск‑факторы, следующий шаг, шаблоны сообщений.
{["Скрипт звонка", "WhatsApp‑шаблон", "Контент‑план", "Сегментация"].map((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 (
Компактно Скор: 55+ Теги: любые Сорт: скор
Метрики (MVP)
Только по делу: лиды, касания, температура.
Лидов всего
Сейчас демо‑данные
{totals.total}
Касаний 7д
NFC + мессенджеры + звонки
{totals.touches}
План касаний
Дисциплина процесса + автоматизация → рост конверсии.
{["День 0: приветствие", "День 1: польза", "День 3: кейс", "День 7: оффер"].map((x, i) => (
))}
В проде: правила (если не ответил → следующий канал), лимиты, согласия, логирование,
отчёт по эффективности.
AI‑агенты
Роль + доступы + ответственность + метрика. Никакой «магии».
{["Sales‑Coach", "Ops‑Analyst", "Marketing‑Planner", "Quality‑Control"].map((x) => (
))}
Рабочие карточки
Канбан (процесс) + касания + интеграции. Без лишних модулей.
{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} />
);
}