494 lines
22 KiB
JavaScript
494 lines
22 KiB
JavaScript
import { mkdirSync, writeFileSync } from 'node:fs';
|
||
import { dirname, join } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const outDir = join(__dirname, '..', 'public', 'prototypes');
|
||
|
||
const W = 1440;
|
||
const C = {
|
||
page: '#f4f4f4',
|
||
paper: '#ffffff',
|
||
panel: '#ffffff',
|
||
soft: '#f7f7f7',
|
||
line: '#d5d5d5',
|
||
dark: '#181818',
|
||
mid: '#545454',
|
||
muted: '#777777',
|
||
fill: '#e8e8e8',
|
||
fill2: '#f0f0f0',
|
||
};
|
||
const font = '"Times New Roman", Times, serif';
|
||
|
||
function esc(value) {
|
||
return String(value)
|
||
.replaceAll('&', '&')
|
||
.replaceAll('<', '<')
|
||
.replaceAll('>', '>')
|
||
.replaceAll('"', '"');
|
||
}
|
||
|
||
function attrs(values) {
|
||
return Object.entries(values)
|
||
.filter(([, value]) => value !== undefined && value !== null)
|
||
.map(([key, value]) => `${key}="${esc(value)}"`)
|
||
.join(' ');
|
||
}
|
||
|
||
function rect(x, y, width, height, options = {}) {
|
||
const {
|
||
rx = 18,
|
||
fill = C.panel,
|
||
stroke = C.line,
|
||
sw = 1.5,
|
||
} = options;
|
||
|
||
return `<rect ${attrs({ x, y, width, height, rx, fill, stroke, 'stroke-width': sw })} />`;
|
||
}
|
||
|
||
function line(x1, y1, x2, y2, options = {}) {
|
||
return `<line ${attrs({
|
||
x1,
|
||
y1,
|
||
x2,
|
||
y2,
|
||
stroke: options.stroke ?? C.line,
|
||
'stroke-width': options.sw ?? 1.5,
|
||
})} />`;
|
||
}
|
||
|
||
function text(x, y, value, options = {}) {
|
||
const {
|
||
size = 16,
|
||
weight = 500,
|
||
fill = C.dark,
|
||
anchor = 'start',
|
||
} = options;
|
||
|
||
return `<text ${attrs({
|
||
x,
|
||
y,
|
||
'font-family': font,
|
||
'font-size': size,
|
||
'font-weight': weight,
|
||
fill,
|
||
'text-anchor': anchor,
|
||
})}>${esc(value)}</text>`;
|
||
}
|
||
|
||
function circle(cx, cy, r, options = {}) {
|
||
return `<circle ${attrs({
|
||
cx,
|
||
cy,
|
||
r,
|
||
fill: options.fill ?? C.fill,
|
||
stroke: options.stroke,
|
||
'stroke-width': options.sw,
|
||
})} />`;
|
||
}
|
||
|
||
function chip(x, y, value, options = {}) {
|
||
const width = options.width ?? Math.max(76, value.length * 9 + 28);
|
||
const selected = options.selected ?? false;
|
||
return [
|
||
rect(x, y, width, 34, {
|
||
rx: 17,
|
||
fill: selected ? C.dark : C.soft,
|
||
stroke: selected ? C.dark : C.line,
|
||
}),
|
||
text(x + width / 2, y + 22, value, {
|
||
size: 13,
|
||
weight: 700,
|
||
fill: selected ? '#ffffff' : C.mid,
|
||
anchor: 'middle',
|
||
}),
|
||
].join('');
|
||
}
|
||
|
||
function input(x, y, width, label) {
|
||
return [
|
||
text(x, y - 12, label, { size: 13, weight: 700, fill: C.mid }),
|
||
rect(x, y, width, 48, { rx: 16, fill: C.paper }),
|
||
].join('');
|
||
}
|
||
|
||
function button(x, y, width, label, options = {}) {
|
||
const dark = options.dark ?? false;
|
||
return [
|
||
rect(x, y, width, 44, {
|
||
rx: 22,
|
||
fill: dark ? C.dark : C.fill,
|
||
stroke: dark ? C.dark : C.line,
|
||
}),
|
||
text(x + width / 2, y + 28, label, {
|
||
size: 14,
|
||
weight: 700,
|
||
fill: dark ? '#ffffff' : C.mid,
|
||
anchor: 'middle',
|
||
}),
|
||
].join('');
|
||
}
|
||
|
||
function topShell(label, nav = [], active = '') {
|
||
const parts = [
|
||
rect(24, 24, 1392, 56, { rx: 28, fill: '#fafafa' }),
|
||
rect(24, 52, 1392, 28, { rx: 0, fill: '#fafafa', stroke: '#fafafa' }),
|
||
circle(58, 52, 7, { fill: '#b7b7b7' }),
|
||
circle(82, 52, 7, { fill: '#d2d2d2' }),
|
||
circle(106, 52, 7, { fill: '#d4d4d4' }),
|
||
text(136, 58, label, { size: 17, weight: 700 }),
|
||
];
|
||
|
||
let x = 820;
|
||
for (const item of nav) {
|
||
parts.push(chip(x, 36, item, { width: Math.max(88, item.length * 10 + 34), selected: item === active }));
|
||
x += Math.max(88, item.length * 10 + 34) + 12;
|
||
}
|
||
|
||
return parts.join('');
|
||
}
|
||
|
||
function page(label, height, body, options = {}) {
|
||
const nav = options.nav ?? ['Каталог', 'Мои заказы', 'Корзина', 'Профиль'];
|
||
const active = options.active ?? '';
|
||
return `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${height}" viewBox="0 0 ${W} ${height}" fill="none"><rect width="${W}" height="${height}" fill="${C.page}" />${rect(24, 24, 1392, height - 48, { rx: 28, fill: C.paper })}${topShell(label, nav, active)}${body.join('')}</svg>`;
|
||
}
|
||
|
||
function titleBlock(title, y = 132, x = 72) {
|
||
return text(x, y, title, { size: 32, weight: 800 });
|
||
}
|
||
|
||
function searchHero(title, placeholder, controls = []) {
|
||
const parts = [
|
||
titleBlock(title),
|
||
rect(72, 168, 600, 54, { rx: 27, fill: C.paper }),
|
||
text(98, 201, placeholder, { size: 15, weight: 500, fill: C.muted }),
|
||
];
|
||
let x = 700;
|
||
for (const control of controls) {
|
||
parts.push(chip(x, 178, control, { width: Math.max(120, control.length * 9 + 34), selected: control === controls[0] }));
|
||
x += Math.max(120, control.length * 9 + 34) + 12;
|
||
}
|
||
return parts.join('');
|
||
}
|
||
|
||
function catalogCards(y = 260) {
|
||
const cards = ['Стретч-пленка', 'Скотч', 'Пакеты', 'Пленка ПВД', 'Воздушно-пузырьковая', 'Картон'];
|
||
const parts = [];
|
||
cards.forEach((name, index) => {
|
||
const col = index % 3;
|
||
const row = Math.floor(index / 3);
|
||
const x = 72 + col * 432;
|
||
const yy = y + row * 238;
|
||
parts.push(rect(x, yy, 396, 202, { rx: 26 }));
|
||
parts.push(rect(x + 20, yy + 20, 356, 118, { rx: 22, fill: C.fill2 }));
|
||
parts.push(text(x + 28, yy + 170, name, { size: 22, weight: 800 }));
|
||
});
|
||
return parts.join('');
|
||
}
|
||
|
||
function orderRows(x, y, width, rows, options = {}) {
|
||
const parts = [];
|
||
const rowH = options.rowH ?? 72;
|
||
rows.forEach((row, index) => {
|
||
const yy = y + index * (rowH + 12);
|
||
parts.push(rect(x, yy, width, rowH, { rx: 20, fill: index % 2 ? '#fbfbfb' : C.paper }));
|
||
parts.push(text(x + 24, yy + 30, row[0], { size: 17, weight: 800 }));
|
||
parts.push(text(x + 24, yy + 54, row[1], { size: 14, weight: 500, fill: C.mid }));
|
||
if (row[2]) {
|
||
parts.push(chip(x + width - 210, yy + 18, row[2], { width: 150 }));
|
||
}
|
||
});
|
||
return parts.join('');
|
||
}
|
||
|
||
function cardGrid(x, y, labels, columns = 3) {
|
||
const parts = [];
|
||
labels.forEach((label, index) => {
|
||
const col = index % columns;
|
||
const row = Math.floor(index / columns);
|
||
const w = columns === 4 ? 300 : 388;
|
||
const xx = x + col * (w + 24);
|
||
const yy = y + row * 128;
|
||
parts.push(rect(xx, yy, w, 104, { rx: 24 }));
|
||
parts.push(circle(xx + 44, yy + 52, 24, { fill: C.fill2 }));
|
||
parts.push(text(xx + 82, yy + 48, label[0], { size: 17, weight: 800 }));
|
||
if (label[1]) {
|
||
parts.push(text(xx + 82, yy + 72, label[1], { size: 14, weight: 500, fill: C.mid }));
|
||
}
|
||
});
|
||
return parts.join('');
|
||
}
|
||
|
||
const pages = {
|
||
'dashboard.svg': page('Главная страница клиента', 900, [
|
||
searchHero('Каталог', 'Поиск по типу товара', []),
|
||
catalogCards(260),
|
||
], { active: 'Каталог' }),
|
||
|
||
'catalog-grid.svg': page('Каталог продукции', 900, [
|
||
searchHero('Каталог', 'Поиск по типу товара', []),
|
||
catalogCards(260),
|
||
], { active: 'Каталог' }),
|
||
|
||
'product-card.svg': page('Карточка товара', 1040, [
|
||
button(72, 116, 110, 'Назад'),
|
||
titleBlock('Алюминиевый скотч', 166),
|
||
rect(72, 220, 400, 330, { rx: 32 }),
|
||
rect(102, 252, 340, 228, { rx: 26, fill: C.fill2 }),
|
||
text(272, 510, 'Изображение товара', { size: 16, weight: 700, fill: C.mid, anchor: 'middle' }),
|
||
rect(504, 220, 536, 330, { rx: 32 }),
|
||
text(536, 258, 'Параметры', { size: 22, weight: 800 }),
|
||
text(536, 304, 'Ширина', { size: 14, weight: 700, fill: C.mid }),
|
||
chip(536, 320, '48 мм', { selected: true }),
|
||
chip(628, 320, '75 мм'),
|
||
text(780, 304, 'Длина', { size: 14, weight: 700, fill: C.mid }),
|
||
chip(780, 320, '25 м', { selected: true }),
|
||
chip(862, 320, '50 м'),
|
||
chip(944, 320, '100 м'),
|
||
text(536, 386, 'Толщина', { size: 14, weight: 700, fill: C.mid }),
|
||
chip(536, 402, '43 мкм', { selected: true }),
|
||
chip(638, 402, '45 мкм'),
|
||
text(780, 386, 'Втулка', { size: 14, weight: 700, fill: C.mid }),
|
||
chip(780, 402, 'Стандарт', { selected: true, width: 112 }),
|
||
chip(904, 402, 'Логотип', { width: 104 }),
|
||
text(536, 468, 'Цвет', { size: 14, weight: 700, fill: C.mid }),
|
||
chip(536, 484, 'Серебристый', { selected: true, width: 126 }),
|
||
text(780, 468, 'Надпись', { size: 14, weight: 700, fill: C.mid }),
|
||
chip(780, 484, 'Без надписи', { selected: true, width: 136 }),
|
||
rect(1072, 220, 296, 330, { rx: 32 }),
|
||
text(1100, 262, 'FRG-ALU-48-50', { size: 20, weight: 800 }),
|
||
text(1100, 310, 'В наличии', { size: 16, weight: 700, fill: C.mid }),
|
||
text(1100, 342, '2 140', { size: 38, weight: 800 }),
|
||
button(1100, 394, 220, 'В корзину', { dark: true }),
|
||
text(72, 624, 'Доступные варианты', { size: 24, weight: 800 }),
|
||
rect(72, 652, 1296, 258, { rx: 24 }),
|
||
text(104, 698, 'SKU', { size: 14, weight: 800, fill: C.mid }),
|
||
text(312, 698, 'Ширина', { size: 14, weight: 800, fill: C.mid }),
|
||
text(470, 698, 'Длина', { size: 14, weight: 800, fill: C.mid }),
|
||
text(620, 698, 'Толщина', { size: 14, weight: 800, fill: C.mid }),
|
||
text(790, 698, 'Втулка', { size: 14, weight: 800, fill: C.mid }),
|
||
text(970, 698, 'Остаток', { size: 14, weight: 800, fill: C.mid }),
|
||
text(1160, 698, 'Действие', { size: 14, weight: 800, fill: C.mid }),
|
||
line(96, 716, 1344, 716),
|
||
orderRows(96, 738, 1248, [
|
||
['FRG-ALU-48-50', '48 мм · 50 м · 43 мкм · стандарт', 'В корзину'],
|
||
['FRG-ALU-75-50', '75 мм · 50 м · 45 мкм · стандарт', 'В корзину'],
|
||
], { rowH: 64 }),
|
||
], { active: 'Каталог' }),
|
||
|
||
'cart.svg': page('Корзина', 900, [
|
||
titleBlock('Корзина'),
|
||
rect(72, 168, 1296, 68, { rx: 24, fill: C.soft }),
|
||
text(102, 210, 'Заполните карточку контрагента перед оформлением заявки', { size: 16, weight: 700, fill: C.mid }),
|
||
text(72, 284, 'Состав заказа', { size: 24, weight: 800 }),
|
||
rect(72, 312, 760, 330, { rx: 28 }),
|
||
orderRows(104, 344, 696, [
|
||
['Стретч-пленка', '48 мм · 50 м · 43 мкм', '2 шт'],
|
||
['Скотч упаковочный', '75 мм · 66 м', '4 шт'],
|
||
['Пакет ПВД', '300 x 400 мм', '1 шт'],
|
||
], { rowH: 72 }),
|
||
rect(872, 312, 496, 330, { rx: 28 }),
|
||
text(904, 354, 'Информация о доставке', { size: 22, weight: 800 }),
|
||
chip(904, 390, 'Склад клиента', { selected: true, width: 160 }),
|
||
chip(904, 444, 'Новый адрес', { width: 148 }),
|
||
input(904, 532, 380, 'Комментарий'),
|
||
button(904, 610, 260, 'Оформить заявку', { dark: true }),
|
||
], { active: 'Корзина' }),
|
||
|
||
'client-order.svg': page('Карточка заказа клиента', 860, [
|
||
button(72, 116, 190, 'Назад к моим заказам'),
|
||
titleBlock('Заказ FRG-1024', 170),
|
||
rect(72, 220, 1296, 118, { rx: 28 }),
|
||
text(104, 262, 'Статус заказа', { size: 16, weight: 700, fill: C.mid }),
|
||
chip(104, 282, 'Предложение', { selected: true, width: 148 }),
|
||
chip(282, 282, 'Подтвердить', { width: 150 }),
|
||
chip(456, 282, 'Отклонить', { width: 130 }),
|
||
text(72, 394, 'Состав заказа', { size: 24, weight: 800 }),
|
||
rect(72, 426, 1296, 260, { rx: 28 }),
|
||
orderRows(104, 458, 1232, [
|
||
['Стретч-пленка', '48 мм · 50 м · количество 2', 'Цена задана'],
|
||
['Скотч упаковочный', '75 мм · 66 м · количество 4', 'Цена задана'],
|
||
], { rowH: 76 }),
|
||
rect(72, 720, 1296, 80, { rx: 24, fill: C.soft }),
|
||
text(104, 754, 'Доставка', { size: 17, weight: 800 }),
|
||
text(260, 754, 'Адрес, срок и стоимость доставки показываются в одной строке', { size: 15, weight: 500, fill: C.mid }),
|
||
], { active: 'Мои заказы' }),
|
||
|
||
'login.svg': page('Логин', 760, [
|
||
rect(450, 130, 540, 520, { rx: 32 }),
|
||
text(720, 196, 'Фрегат', { size: 14, weight: 800, fill: C.mid, anchor: 'middle' }),
|
||
text(720, 244, 'Вход', { size: 36, weight: 800, anchor: 'middle' }),
|
||
input(510, 304, 420, 'E-mail'),
|
||
button(510, 386, 420, 'Получить код', { dark: true }),
|
||
line(510, 464, 930, 464),
|
||
text(720, 492, 'или войти через', { size: 13, weight: 700, fill: C.mid, anchor: 'middle' }),
|
||
button(510, 526, 196, 'Telegram'),
|
||
button(734, 526, 196, 'Max'),
|
||
], { nav: [] }),
|
||
|
||
'bonus-cabinet.svg': page('Бонусный кабинет', 940, [
|
||
titleBlock('Чёрный кабинет бонусной программы'),
|
||
rect(72, 178, 820, 250, { rx: 30 }),
|
||
text(112, 230, 'Аккаунт', { size: 15, weight: 700, fill: C.mid }),
|
||
text(112, 280, 'Клиент бонусной программы', { size: 32, weight: 800, fill: C.dark }),
|
||
text(112, 354, 'Доступный баланс', { size: 15, weight: 700, fill: C.mid }),
|
||
text(112, 398, '12 400', { size: 48, weight: 800, fill: C.dark }),
|
||
rect(928, 178, 440, 250, { rx: 30 }),
|
||
text(968, 230, 'Вывод бонусов', { size: 20, weight: 800, fill: C.dark }),
|
||
input(968, 292, 320, 'Сумма заявки'),
|
||
button(968, 370, 280, 'Подать заявку', { dark: false }),
|
||
rect(72, 472, 620, 300, { rx: 30 }),
|
||
text(112, 522, 'История бонусов', { size: 24, weight: 800, fill: C.dark }),
|
||
orderRows(112, 552, 540, [
|
||
['+1 500', 'Начисление по заказу', ''],
|
||
['+900', 'Реферальное начисление', ''],
|
||
], { rowH: 68 }),
|
||
rect(748, 472, 620, 300, { rx: 30 }),
|
||
text(788, 522, 'Вознаграждения', { size: 24, weight: 800, fill: C.dark }),
|
||
button(788, 566, 170, 'Ozon 3000'),
|
||
button(980, 566, 210, 'Wildberries 4000'),
|
||
button(788, 634, 190, 'М.Видео 5000'),
|
||
], { active: 'Профиль' }),
|
||
|
||
'client-list.svg': page('Клиенты', 900, [
|
||
searchHero('Клиенты', 'Имя, компания или email', ['Пригласить']),
|
||
cardGrid(72, 270, [
|
||
['Иван Петров', 'ООО Альфа'],
|
||
['Мария Соколова', 'ИП Соколова'],
|
||
['Дмитрий Иванов', 'ООО Север'],
|
||
['Анна Смирнова', 'ООО Вектор'],
|
||
['Павел Морозов', 'Завод Мир'],
|
||
['Елена Орлова', 'ТД Орлова'],
|
||
], 3),
|
||
], { nav: ['Заказы', 'Бонусы', 'Настройки'], active: 'Заказы' }),
|
||
|
||
'client-card.svg': page('Карточка клиента', 880, [
|
||
button(72, 116, 170, 'Назад к клиентам'),
|
||
titleBlock('Клиент Иван Петров', 170),
|
||
cardGrid(72, 224, [
|
||
['Email', 'client@company.ru'],
|
||
['Telegram', 'Подключен'],
|
||
['Компания', 'ООО Альфа'],
|
||
['ИНН', '7700000000'],
|
||
], 4),
|
||
text(72, 500, 'Заказы пользователя', { size: 24, weight: 800 }),
|
||
rect(72, 532, 1296, 240, { rx: 28 }),
|
||
orderRows(104, 564, 1232, [
|
||
['FRG-1024', 'Стретч-пленка · Москва', 'В работе'],
|
||
['FRG-1017', 'Скотч · Санкт-Петербург', 'Завершен'],
|
||
], { rowH: 72 }),
|
||
], { nav: ['Заказы', 'Бонусы', 'Настройки'], active: 'Заказы' }),
|
||
|
||
'manager-order.svg': page('Обработка заявки', 900, [
|
||
button(72, 116, 170, 'Назад к заказам'),
|
||
titleBlock('Заказ FRG-1024', 170),
|
||
rect(72, 220, 1296, 118, { rx: 28 }),
|
||
text(104, 262, 'Статус заказа', { size: 16, weight: 700, fill: C.mid }),
|
||
chip(104, 282, 'В обработке', { selected: true, width: 148 }),
|
||
chip(282, 282, 'Предложение', { width: 150 }),
|
||
rect(72, 382, 920, 300, { rx: 28 }),
|
||
text(104, 426, 'Состав заказа', { size: 24, weight: 800 }),
|
||
orderRows(104, 460, 856, [
|
||
['Стретч-пленка', 'Количество 2 · цена редактируется', 'Цена'],
|
||
['Скотч упаковочный', 'Количество 4 · цена редактируется', 'Цена'],
|
||
], { rowH: 76 }),
|
||
rect(1028, 382, 340, 300, { rx: 28 }),
|
||
text(1060, 426, 'Условия', { size: 24, weight: 800 }),
|
||
input(1060, 484, 250, 'Срок доставки'),
|
||
input(1060, 578, 250, 'Стоимость доставки'),
|
||
button(1060, 650, 230, 'Сохранить', { dark: true }),
|
||
], { nav: ['Заказы', 'Бонусы', 'Настройки'], active: 'Заказы' }),
|
||
|
||
'manager-orders.svg': page('Заказы менеджера', 920, [
|
||
searchHero('Заказы', 'Номер заказа, клиент, адрес или товар', ['Список', 'Календарь']),
|
||
chip(72, 250, 'Все', { selected: true, width: 88 }),
|
||
chip(174, 250, 'Заявки', { width: 112 }),
|
||
chip(300, 250, 'Предложения', { width: 150 }),
|
||
chip(464, 250, 'В работе', { width: 126 }),
|
||
chip(604, 250, 'Закрытые', { width: 126 }),
|
||
rect(72, 320, 1296, 430, { rx: 30 }),
|
||
orderRows(104, 356, 1232, [
|
||
['FRG-1024', 'Иван Петров · Стретч-пленка · Москва', 'Заявка'],
|
||
['FRG-1025', 'Мария Соколова · Скотч · Казань', 'Предложение'],
|
||
['FRG-1026', 'Дмитрий Иванов · Пакеты · СПб', 'В работе'],
|
||
['FRG-1027', 'Анна Смирнова · Пленка ПВД · Москва', 'Закрыт'],
|
||
], { rowH: 76 }),
|
||
], { nav: ['Заказы', 'Бонусы', 'Настройки'], active: 'Заказы' }),
|
||
|
||
'catalog-settings.svg': page('Настройки каталога', 980, [
|
||
titleBlock('Каталог'),
|
||
rect(72, 184, 1296, 104, { rx: 28 }),
|
||
text(104, 226, 'Стретч-пленка', { size: 22, weight: 800 }),
|
||
text(104, 256, '6 параметров, 3 кастомные возможности', { size: 15, weight: 500, fill: C.mid }),
|
||
rect(72, 318, 1296, 446, { rx: 28 }),
|
||
text(104, 362, 'Кастомные возможности', { size: 22, weight: 800 }),
|
||
chip(104, 390, 'Любая длина', { selected: true, width: 140 }),
|
||
chip(262, 390, 'Логотип на втулке', { width: 190 }),
|
||
chip(470, 390, 'Нанесение надписи', { width: 200 }),
|
||
text(104, 478, 'Диапазон длины', { size: 18, weight: 800 }),
|
||
input(104, 520, 240, 'Мин. длина, м'),
|
||
input(378, 520, 240, 'Макс. длина, м'),
|
||
input(652, 520, 240, 'Шаг, м'),
|
||
text(104, 638, 'Параметры', { size: 18, weight: 800 }),
|
||
chip(104, 664, 'Ширина', { width: 110 }),
|
||
chip(232, 664, 'Длина', { width: 100 }),
|
||
chip(350, 664, 'Толщина', { width: 120 }),
|
||
chip(488, 664, 'Втулка', { width: 108 }),
|
||
chip(614, 664, 'Цвет', { width: 96 }),
|
||
chip(728, 664, 'Надпись', { width: 120 }),
|
||
button(1100, 804, 190, 'Сохранить', { dark: true }),
|
||
], { nav: ['Заказы', 'Бонусы', 'Настройки'], active: 'Настройки' }),
|
||
|
||
'sync-settings.svg': page('Настройки синхронизации', 900, [
|
||
titleBlock('1С'),
|
||
text(72, 168, 'Статус загрузки файлов обмена', { size: 16, weight: 500, fill: C.mid }),
|
||
cardGrid(72, 230, [
|
||
['counterparties_snapshot', 'Контрагенты'],
|
||
['catalog_snapshot', 'Каталог и остатки'],
|
||
['balances_snapshot', 'Задолженность клиентов'],
|
||
['orders_snapshot', 'Заказы клиентов'],
|
||
], 4),
|
||
rect(72, 450, 1296, 250, { rx: 28 }),
|
||
text(104, 494, 'Последние загрузки', { size: 24, weight: 800 }),
|
||
orderRows(104, 530, 1232, [
|
||
['Контрагенты', 'Загружены реквизиты и признаки доступа', 'Работает'],
|
||
['Каталог и остатки', 'Загружено 2 418 записей · последний run сегодня', 'Работает'],
|
||
['Задолженность клиентов', 'Баланс по клиентам с личным кабинетом', 'Работает'],
|
||
['Заказы клиентов', 'Статусы заказов за рабочий период', 'Работает'],
|
||
], { rowH: 62 }),
|
||
], { nav: ['Заказы', 'Бонусы', 'Настройки'], active: 'Настройки' }),
|
||
|
||
'profile.svg': page('Профиль клиента', 820, [
|
||
titleBlock('Профиль'),
|
||
cardGrid(72, 210, [
|
||
['Карточка контрагента', 'Реквизиты и ИНН'],
|
||
['Уведомления', 'Telegram и Max'],
|
||
['Адреса доставки', 'Список адресов'],
|
||
], 3),
|
||
], { active: 'Профиль' }),
|
||
|
||
'bonus-manager.svg': page('Бонусная система менеджера', 920, [
|
||
searchHero('Бонусы', 'Клиент, связанный клиент или email', ['Добавить']),
|
||
chip(72, 250, 'Балансы', { selected: true, width: 120 }),
|
||
chip(208, 250, 'Заявки', { width: 110 }),
|
||
chip(334, 250, 'Награды', { width: 116 }),
|
||
cardGrid(72, 320, [
|
||
['Иван Петров', '12 400 ₽'],
|
||
['Мария Соколова', '8 250 ₽'],
|
||
['Дмитрий Иванов', '5 100 ₽'],
|
||
['Анна Смирнова', '2 900 ₽'],
|
||
], 4),
|
||
rect(72, 610, 1296, 170, { rx: 28 }),
|
||
text(104, 654, 'Заявки на выплату', { size: 24, weight: 800 }),
|
||
orderRows(104, 686, 1232, [
|
||
['WD-01A23F', 'Иван Петров · на проверке', '12 000 ₽'],
|
||
], { rowH: 68 }),
|
||
], { nav: ['Заказы', 'Бонусы', 'Настройки'], active: 'Бонусы' }),
|
||
};
|
||
|
||
mkdirSync(outDir, { recursive: true });
|
||
for (const [fileName, content] of Object.entries(pages)) {
|
||
writeFileSync(join(outDir, fileName), `${content}\n`, 'utf8');
|
||
}
|