krona-front/design/penpot/generate-crm-v2.mjs

60 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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.

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const out=path.dirname(fileURLToPath(import.meta.url));
const C={bg:'#F5EFDF',paper:'#FFF9ED',ink:'#26342C',muted:'#77776E',line:'#DCCFB8',green:'#2E684A',green2:'#DDE9DA',gold:'#A8752F',red:'#A94C3F',dark:'#20352A'};
const e=s=>String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');
const text=(x,y,s,size=13,fill=C.ink,weight=400,anchor='start')=>`<text x="${x}" y="${y}" font-family="Arial" font-size="${size}" fill="${fill}" font-weight="${weight}" text-anchor="${anchor}">${e(s)}</text>`;
const rect=(x,y,w,h,fill=C.paper,rx=12,stroke=C.line)=>`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="${rx}" fill="${fill}"${stroke?` stroke="${stroke}"`:''}/>`;
const pill=(x,y,w,s,fill=C.green2,color=C.green)=>`${rect(x,y,w,28,fill,14,null)}${text(x+w/2,y+19,s,11,color,700,'middle')}`;
const nav=['Главная','Аналитика','Маркетинг','Коммуникации','CRM','Продажи','Склад','Закупки','Финансы','Сотрудники','Проекты','Задачи'];
function shell(title,sub,body,action='+ Новая сделка'){
const n=nav.map((s,i)=>`<g transform="translate(18 ${116+i*47})">${rect(0,-25,196,39,s==='CRM'?'#486B55':'transparent',9,null)}<circle cx="12" cy="-6" r="6" fill="${s==='CRM'?'#E8C47A':'#839488'}"/>${text(30,0,s,12,'#F8F1E2',s==='CRM'?700:400)}</g>`).join('');
return `<svg xmlns="http://www.w3.org/2000/svg" width="1536" height="1024" viewBox="0 0 1536 1024"><rect width="1536" height="1024" fill="${C.bg}"/><rect width="224" height="1024" fill="${C.dark}"/>${text(28,42,'♛ KRONA',25,'#E8C47A',700)}${text(29,62,'БИЗНЕС-ПЛАТФОРМА',9,'#DCC58F',700)}${n}${text(28,970,'Владелец бизнеса',11,'#F8F1E2',700)}${text(28,990,'Горский 1/1',9,'#9CB0A3')}<rect x="224" width="1312" height="70" fill="#FCF8EF" stroke="${C.line}"/>${rect(250,15,228,40,'#F5EEDC',10,C.line)}${text(268,40,'Ресторан: Зал + Доставка⌄',12)}${rect(800,15,366,40,'#FCF8EF',10,C.line)}${text(824,40,'⌕ Поиск по платформе…',12,'#89877E')}${text(1446,40,'Виктор ⌄',12,C.ink,700,'end')}${text(258,116,title,30,C.ink,700)}${text(258,143,sub,12,C.muted)}${rect(1320,96,184,42,C.green,10,null)}${text(1412,122,action,12,'#FFF9EC',700,'middle')}<g transform="translate(250 164)">${body}</g></svg>`;
}
function pipeline(){
const stages=[['Новые','7','840 000 ₽'],['Квалификация','5','1 120 000 ₽'],['Предложение','4','920 000 ₽'],['Переговоры','3','1 480 000 ₽'],['Согласование','2','610 000 ₽']];
const cards=[['Поставка упаковки','ООО «Ресурс»','180 000 ₽','Сегодня, 15:00'],['Корпоративное меню','БЦ «Север»','240 000 ₽','Нет задачи'],['Сеть кофеен «Маяк»','Анна Белова','450 000 ₽','Звонок · 16:30'],['Банкеты на октябрь','Отель «Гранд»','680 000 ₽','Завтра'],['Линия бизнес-ланчей','IT Park','520 000 ₽','Просрочено']];
let s=`${rect(0,0,1260,58,'#FCF8EF',12,C.line)}${text(20,35,'Воронка B2B продаж⌄',13,C.ink,700)}${pill(208,15,108,'Канбан')}${pill(324,15,82,'Список','#F3EBDD',C.muted)}${text(480,35,'⌕ Поиск сделок, контактов и компаний',12,C.muted)}${text(1218,35,'Фильтры 3 ···',12,C.ink,700,'end')}`;
s+=`${rect(0,72,1260,56,'#EDF3EA',10,'#C8D8CA')}${text(18,96,'План сентября',11,C.muted)}${text(18,118,'6 400 000 ₽',18,C.green,700)}${text(210,96,'В работе',11,C.muted)}${text(210,118,'4 970 000 ₽',18,C.ink,700)}${text(420,96,'Прогноз',11,C.muted)}${text(420,118,'5 860 000 ₽ · 92%',18,C.gold,700)}<rect x="680" y="96" width="550" height="8" rx="4" fill="#D8E0D5"/><rect x="680" y="96" width="506" height="8" rx="4" fill="${C.green}"/>`;
stages.forEach((st,i)=>{const x=i*250;s+=`${rect(x,144,238,680,'#F9F4E8',12,C.line)}${text(x+14,172,st[0],13,C.ink,700)}${pill(x+184,153,38,st[1],'#E8DECB',C.ink)}${text(x+14,196,st[2],11,C.muted)}${rect(x+10,218,218,188,'#FFFDF6',10,C.line)}${text(x+24,248,cards[i][0],13,C.ink,700)}${text(x+24,274,cards[i][1],11,C.muted)}${text(x+24,308,cards[i][2],17,C.ink,700)}${pill(x+24,324,88,i===0?'40% шанс':'60% шанс')}${text(x+24,382,cards[i][3],10,i===4?C.red:C.gold,700)}${text(x+200,382,'●',12,i===4?C.red:C.green)}${rect(x+10,418,218,146,'#FFFDF6',10,C.line)}${text(x+24,450,i%2?'Поставка обедов':'Осенний кейтеринг',12,C.ink,700)}${text(x+24,475,i%2?'Новый клиент':'Повторная продажа',10,C.muted)}${text(x+24,512,i%2?'320 000 ₽':'260 000 ₽',15,C.ink,700)}${text(x+24,545,'+ Запланировать дело',10,C.green,700)}${text(x+119,804,'+ Быстро добавить',11,C.green,700,'middle')}`});return s;
}
function deal(){
let s=`${rect(0,0,1260,62,'#FCF8EF',12,C.line)}${text(18,25,'← Сделки / Поставка упаковки',11,C.muted)}${text(18,49,'Поставка упаковки',19,C.ink,700)}${pill(248,17,118,'Квалификация')}${text(1228,39,'♡ ···',18,C.gold,700,'end')}`;
s+=`${rect(0,76,374,748,'#FCF8EF',12,C.line)}${text(20,110,'ДАННЫЕ СДЕЛКИ',11,C.muted,700)}${text(20,148,'180 000 ₽',26,C.ink,700)}${text(20,178,'Вероятность 40% · B2B',11,C.muted)}${text(20,224,'Клиент',10,C.muted,700)}${text(20,247,'ООО «Ресурс»',14,C.ink,700)}${text(20,269,'Иван Петров · Директор',11,C.muted)}${text(20,310,'Контакты',10,C.muted,700)}${text(20,336,'☎ +7 900 123-45-67',12,C.green)}${text(20,361,'✉ ivan@resurs.ru',12,C.green)}${text(20,401,'Ответственный',10,C.muted,700)}${text(20,426,'Александр Соколов',12,C.ink,700)}${text(20,470,'Следующее действие',10,C.muted,700)}${rect(16,484,342,70,'#F5E6D7',9,'#E6C5AB')}${text(30,509,'Звонок по условиям поставки',12,C.ink,700)}${text(30,534,'Сегодня, 15:00 · через 2 ч 14 мин',10,C.red,700)}${text(20,594,'Связанные объекты',10,C.muted,700)}${pill(20,608,96,'2 задачи')}${pill(124,608,92,'3 файла')}${pill(224,608,116,'1 предложение')}${text(20,684,'Пользовательские поля',10,C.muted,700)}${text(20,710,'Тип упаковки',11,C.muted)}${text(340,710,'ПЭТ',11,C.ink,700,'end')}${text(20,741,'Объём в месяц',11,C.muted)}${text(340,741,'12 000 шт',11,C.ink,700,'end')}${text(20,772,'Адрес доставки',11,C.muted)}${text(340,772,'Горский 1/1',11,C.ink,700,'end')}${text(187,807,'⚙ Настроить поля',11,C.green,700,'middle')}`;
s+=`${rect(388,76,600,748,'#FCF8EF',12,C.line)}${text(408,108,'Активность',15,C.ink,700)}${pill(728,88,70,'Все')}${pill(806,88,82,'Сообщения','#F3EBDD',C.muted)}${pill(896,88,72,'Задачи','#F3EBDD',C.muted)}${rect(406,132,564,74,'#F7F1E5',10,C.line)}${text(424,159,'Написать сообщение, заметку или поставить задачу…',11,C.muted)}${text(424,190,'✉ ☎ ☑ 📎',16,C.gold)}${rect(426,236,520,88,'#EDF3EA',12,null)}${text(444,260,'Telegram · Иван Петров',10,C.green,700)}${text(444,284,'Интересует поставка по новому прайсу.',12,C.ink)}${text(444,306,'Сегодня, 12:17',9,C.muted)}${rect(490,344,456,76,'#F2E8D5',12,null)}${text(508,370,'Вы · Telegram',10,C.gold,700)}${text(508,394,'Отправил актуальные условия.',12,C.ink)}${text(426,452,'● Задача выполнена',11,C.green,700)}${text(448,478,'Подготовить предварительный расчёт',12,C.ink)}${text(448,498,'Сегодня, 11:40 · Александр Соколов',9,C.muted)}${text(426,542,'☎ Исходящий звонок · 04:18',11,C.ink,700)}${text(448,568,'Обсудили объём и сроки поставки',11,C.muted)}${text(426,622,'▤ Коммерческое предложение.pdf',11,C.ink,700)}${text(448,647,'2,4 МБ · отправлено клиенту',10,C.muted)}${text(426,704,'◉ Стадия изменена',11,C.gold,700)}${text(448,730,'Первичный контакт → Квалификация',11,C.muted)}${text(448,752,'Вчера, 17:12',9,C.muted)}`;
s+=`${rect(1002,76,258,748,'#F8F3E7',12,C.line)}${text(1020,108,'Следующий шаг',13,C.ink,700)}${rect(1018,126,226,88,'#F5E6D7',9,'#E6C5AB')}${text(1032,151,'Связаться сегодня',11,C.red,700)}${text(1032,176,'Уточнить условия договора',11,C.ink)}${text(1032,198,'15:00',10,C.muted)}${text(1020,252,'KRONA AI',11,C.gold,700)}${text(1020,280,'Клиент настроен позитивно.',11,C.ink)}${text(1020,300,'Главный вопрос — срок',11,C.ink)}${text(1020,320,'первой поставки.',11,C.ink)}${rect(1018,344,226,38,C.green,9,null)}${text(1131,368,'Подготовить ответ',11,'#FFF',700,'middle')}${text(1020,426,'Документы',11,C.muted,700)}${text(1020,454,'Предложение · отправлено',10,C.ink)}${text(1020,480,'Договор · не создан',10,C.red)}${text(1020,524,'Оплата',11,C.muted,700)}${text(1020,552,'Счета отсутствуют',10,C.ink)}${text(1020,596,'Автоматизации этапа',11,C.muted,700)}${text(1020,624,'✓ Создать задачу через 1 день',10,C.green)}${text(1020,650,'✓ Напомнить при молчании',10,C.green)}`;return s;
}
function customer(){
let s=`${rect(0,0,1260,126,'#FCF8EF',14,C.line)}<circle cx="56" cy="62" r="36" fill="#DDE9DA"/>${text(56,70,'ИП',18,C.green,700,'middle')}${text(110,44,'Иван Петров',22,C.ink,700)}${text(110,70,'Директор · ООО «Ресурс»',12,C.muted)}${pill(110,84,82,'Клиент')}${pill(200,84,58,'VIP','#F5E4C2',C.gold)}${text(1220,52,'☎ Позвонить ✉ Написать ···',12,C.green,700,'end')}${text(1220,88,'Последняя активность сегодня, 12:41',10,C.muted,'400','end')}`;
s+=`${rect(0,142,810,682,'#FCF8EF',12,C.line)}${text(18,177,'Хронология отношений',15,C.ink,700)}${pill(562,157,70,'Все')}${pill(640,157,70,'Сделки','#F3EBDD',C.muted)}${pill(718,157,72,'Диалоги','#F3EBDD',C.muted)}`;
const ev=[['Сегодня, 12:41','Telegram','Подтвердил интерес к поставке упаковки'],['Сегодня, 11:40','Задача','Подготовлен предварительный расчёт'],['Вчера, 17:12','Сделка','Переведена на этап «Квалификация»'],['16 сентября','Email','Открыто коммерческое предложение'],['12 сентября','Звонок','Первичный разговор · 06:24']];ev.forEach((a,i)=>{let y=220+i*104;s+=`<circle cx="40" cy="${y}" r="8" fill="${i?C.gold:C.green}"/><line x1="40" y1="${y+10}" x2="40" y2="${y+88}" stroke="${C.line}"/>${text(66,y-4,a[1],11,i?C.gold:C.green,700)}${text(66,y+22,a[2],13,C.ink,700)}${text(66,y+46,a[0],10,C.muted)}`});
s+=`${rect(826,142,434,330,'#FCF8EF',12,C.line)}${text(846,177,'Показатели клиента',15,C.ink,700)}${text(846,224,'Сумма сделок',10,C.muted)}${text(1240,224,'1 420 000 ₽',17,C.ink,700,'end')}${text(846,267,'Оплачено',10,C.muted)}${text(1240,267,'860 000 ₽',14,C.green,700,'end')}${text(846,310,'Активные сделки',10,C.muted)}${text(1240,310,'3',14,C.ink,700,'end')}${text(846,353,'Средний цикл',10,C.muted)}${text(1240,353,'18 дней',14,C.ink,700,'end')}${text(846,396,'Последняя покупка',10,C.muted)}${text(1240,396,'12 августа',14,C.ink,700,'end')}${text(846,439,'Риск оттока',10,C.muted)}${pill(1136,420,104,'Низкий')}`;
s+=`${rect(826,488,434,336,'#FCF8EF',12,C.line)}${text(846,523,'Связи',15,C.ink,700)}${text(846,558,'Компания',10,C.muted)}${text(846,582,'ООО «Ресурс»',12,C.green,700)}${text(846,620,'Контактные каналы',10,C.muted)}${text(846,646,'Telegram @ivan_petrov',11,C.ink)}${text(846,672,'WhatsApp +7 900 123-45-67',11,C.ink)}${text(846,698,'Email ivan@resurs.ru',11,C.ink)}${text(846,744,'Ответственный',10,C.muted)}${text(846,770,'Александр Соколов',12,C.ink,700)}${text(846,804,'+ Связать объект',11,C.green,700)}`;return s;
}
function analytics(){
let s=`${rect(0,0,1260,58,'#FCF8EF',12,C.line)}${text(20,35,'B2B продажи · Сентябрь 2026⌄',13,C.ink,700)}${text(1230,35,'Сравнить период · Экспорт',11,C.green,700,'end')}`;
const k=[['Новые сделки','24','+18%'],['Выиграно','1,94 млн ₽','+12%'],['Конверсия','31%','+4,2 п.п.'],['Средний цикл','18 дней','−3 дня']];k.forEach((a,i)=>{let x=i*310;s+=`${text(x+10,104,a[0],10,C.muted)}${text(x+10,136,a[1],23,C.ink,700)}${text(x+10,158,a[2],10,C.green,700)}${i<3?`<line x1="${x+286}" y1="90" x2="${x+286}" y2="162" stroke="${C.line}"/>`:''}`});
s+=`${rect(0,188,760,330,'#FCF8EF',12,C.line)}${text(20,222,'Динамика выручки и прогноза',14,C.ink,700)}<path d="M30 448 C140 420 180 450 270 370 S430 330 510 280 S650 250 730 240" fill="none" stroke="${C.green}" stroke-width="4"/><path d="M30 470 C140 460 220 430 300 414 S500 350 730 298" fill="none" stroke="${C.gold}" stroke-width="3" stroke-dasharray="8 7"/>${text(28,492,'1 сен',10,C.muted)}${text(710,492,'30 сен',10,C.muted,'400','end')}${text(590,224,'— факт – – прогноз',10,C.muted)}`;
s+=`${rect(776,188,484,330,'#FCF8EF',12,C.line)}${text(796,222,'Причины потерь',14,C.ink,700)}`;[['Цена',38],['Срок поставки',24],['Конкурент',18],['Нет ответа',12]].forEach((a,i)=>{let y=266+i*56;s+=`${text(796,y,a[0],11,C.ink)}<rect x="920" y="${y-14}" width="260" height="12" rx="6" fill="#EFE5D4"/><rect x="920" y="${y-14}" width="${a[1]*5}" height="12" rx="6" fill="${i?C.gold:C.red}"/>${text(1208,y,a[1]+'%',10,C.muted)}`});
s+=`${rect(0,534,1260,290,'#FCF8EF',12,C.line)}${text(20,568,'Эффективность этапов',14,C.ink,700)}`;[['Новый → Квалификация','72%','1,8 дня'],['Квалификация → Предложение','61%','3,4 дня'],['Предложение → Переговоры','48%','5,2 дня'],['Переговоры → Согласование','66%','4,1 дня'],['Согласование → Выиграна','74%','3,5 дня']].forEach((a,i)=>{let y=614+i*39;s+=`${text(24,y,a[0],11,C.ink)}${text(910,y,a[1],11,C.green,700)}${text(1218,y,a[2],11,C.muted,400,'end')}<line x1="24" y1="${y+15}" x2="1220" y2="${y+15}" stroke="${C.line}"/>`});return s;
}
function automation(){
let s=`${rect(0,0,1260,64,'#FCF8EF',12,C.line)}${text(20,28,'Воронка B2B продаж',13,C.ink,700)}${text(20,50,'Роботы выполняются сверху вниз при входе сделки на этап',10,C.muted)}${text(1228,38,'Проверить журнал · ⚙',11,C.green,700,'end')}`;
const cols=[['Новая сделка',['Создать задачу менеджеру','Проверить дубликаты','Отправить приветствие']],['Квалификация',['Через 1 день: напомнить','Если молчит 3 дня: задача','AI: оценить вероятность']],['Предложение',['Создать предложение','Отправить клиенту','Уведомить при открытии']],['Согласование',['Создать проект договора','Задача юристу','Контроль оплаты']]];cols.forEach((c,i)=>{let x=i*310;s+=`${rect(x,82,294,742,'#F9F4E8',12,C.line)}${text(x+16,112,c[0],13,C.ink,700)}${pill(x+235,94,42,String(c[1].length))}`;c[1].forEach((a,j)=>{let y=144+j*128;s+=`${rect(x+14,y,266,108,'#FFFDF6',10,C.line)}${text(x+30,y+30,a,11,C.ink,700)}${text(x+30,y+55,j===2?'Условие: всегда':'Сразу после входа',9,C.muted)}${pill(x+30,y+68,78,'Активен')}${text(x+250,y+88,'⋮',16,C.gold,700)}`});s+=`${rect(x+14,548,266,42,'#EDF3EA',9,'#C8D8CA')}${text(x+147,574,'+ Добавить действие',11,C.green,700,'middle')}${text(x+147,792,'Перетащите действие сюда',10,C.muted,400,'middle')}`});return s;
}
function settings(){
let s=`${rect(0,0,254,824,'#FCF8EF',12,C.line)}${text(20,38,'Настройки CRM',15,C.ink,700)}`;['Воронки и этапы','Поля карточек','Карточка канбана','Источники лидов','Права доступа','Дубликаты','Импорт и экспорт'].forEach((a,i)=>{let y=74+i*52;s+=`${rect(10,y-27,234,40,i===0?'#E6ECDD':'transparent',8,null)}${text(24,y,a,11,i===0?C.green:C.ink,i===0?700:400)}`});
s+=`${rect(270,0,990,824,'#FCF8EF',12,C.line)}${text(294,40,'Воронки и этапы',19,C.ink,700)}${text(294,64,'Настройте путь сделки, обязательные поля и автоматизации',11,C.muted)}${rect(294,88,944,54,'#F7F1E5',9,C.line)}${text(312,121,'B2B продажи⌄',12,C.ink,700)}${text(1216,121,'+ Новая воронка',11,C.green,700,'end')}`;
const stages=['Первичный контакт','Квалификация','Предложение','Переговоры','Согласование','Выиграна','Проиграна'];stages.forEach((a,i)=>{let y=170+i*76;s+=`${rect(294,y,944,58,'#FFFDF6',9,C.line)}<circle cx="316" cy="${y+29}" r="7" fill="${i<5?C.green:i===5?'#4B8D63':C.red}"/>${text(338,y+25,a,12,C.ink,700)}${text(338,y+44,i===0?'Начальный этап':i>4?'Финальный этап':'Обязательных полей: '+(i+1),9,C.muted)}${text(1160,y+34,'Автоматизации '+(i+1)+' ⋮',10,C.green,700,'end')}`});s+=`${rect(294,716,944,82,'#EDF3EA',10,'#C8D8CA')}${text(316,748,'+ Добавить этап',12,C.green,700)}${text(316,775,'Этапы можно перетаскивать. Изменения применятся ко всем сделкам.',10,C.muted)}`;return s;
}
const screens=[
['70-crm-v2-pipeline.svg','CRM · Воронка продаж','Сделки, задачи и прогноз в одном рабочем поле',pipeline(),'+ Новая сделка'],
['71-crm-v2-deal.svg','CRM · Сделка','Данные, коммуникации и следующее действие',deal(),'Сохранено'],
['72-crm-v2-customer.svg','CRM · Клиент 360°','Полная история отношений и связанные объекты',customer(),'+ Новая сделка'],
['73-crm-v2-analytics.svg','CRM · Аналитика','Конверсия, скорость прохождения этапов и прогноз',analytics(),'Скачать отчёт'],
['74-crm-v2-automation.svg','CRM · Автоматизация','Действия и условия для каждого этапа воронки',automation(),'+ Новый робот'],
['75-crm-v2-settings.svg','CRM · Настройки','Воронки, поля, представления и права доступа',settings(),'Сохранить изменения']
];
for(const [file,title,sub,body,action] of screens)fs.writeFileSync(path.join(out,file),shell(title,sub,body,action),'utf8');
console.log(`generated ${screens.length} CRM V2 screens`);