From 81efc78029d4a28f4da29d51d3a4ff5e89bd09ac Mon Sep 17 00:00:00 2001
From: Ruslan Bakiev <572431+veikab@users.noreply.github.com>
Date: Tue, 5 May 2026 10:38:56 +0700
Subject: [PATCH] Align specification prototypes with screens
---
docs/public/prototypes/bonus-cabinet.svg | 2 +-
docs/public/prototypes/bonus-manager.svg | 2 +-
docs/public/prototypes/cart.svg | 2 +-
docs/public/prototypes/catalog-grid.svg | 2 +-
docs/public/prototypes/catalog-settings.svg | 2 +-
docs/public/prototypes/client-card.svg | 2 +-
docs/public/prototypes/client-list.svg | 2 +-
docs/public/prototypes/client-order.svg | 2 +-
docs/public/prototypes/dashboard.svg | 2 +-
docs/public/prototypes/login.svg | 2 +-
docs/public/prototypes/manager-order.svg | 2 +-
docs/public/prototypes/manager-orders.svg | 2 +-
docs/public/prototypes/product-card.svg | 2 +-
docs/public/prototypes/profile.svg | 2 +-
docs/public/prototypes/sync-settings.svg | 2 +-
docs/scripts/generate-prototypes.mjs | 491 ++++++++++++++++++++
docs/tz-fregat.typ | 46 +-
17 files changed, 525 insertions(+), 42 deletions(-)
create mode 100644 docs/scripts/generate-prototypes.mjs
diff --git a/docs/public/prototypes/bonus-cabinet.svg b/docs/public/prototypes/bonus-cabinet.svg
index 0d0abcf..af25443 100644
--- a/docs/public/prototypes/bonus-cabinet.svg
+++ b/docs/public/prototypes/bonus-cabinet.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/bonus-manager.svg b/docs/public/prototypes/bonus-manager.svg
index b008ea4..a7fde98 100644
--- a/docs/public/prototypes/bonus-manager.svg
+++ b/docs/public/prototypes/bonus-manager.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/cart.svg b/docs/public/prototypes/cart.svg
index 9bf0e23..e26a5e3 100644
--- a/docs/public/prototypes/cart.svg
+++ b/docs/public/prototypes/cart.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/catalog-grid.svg b/docs/public/prototypes/catalog-grid.svg
index 539295b..bb4a141 100644
--- a/docs/public/prototypes/catalog-grid.svg
+++ b/docs/public/prototypes/catalog-grid.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/catalog-settings.svg b/docs/public/prototypes/catalog-settings.svg
index 404d7d0..5309e12 100644
--- a/docs/public/prototypes/catalog-settings.svg
+++ b/docs/public/prototypes/catalog-settings.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/client-card.svg b/docs/public/prototypes/client-card.svg
index fcf3ca7..3d613c0 100644
--- a/docs/public/prototypes/client-card.svg
+++ b/docs/public/prototypes/client-card.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/client-list.svg b/docs/public/prototypes/client-list.svg
index c881762..cdb036a 100644
--- a/docs/public/prototypes/client-list.svg
+++ b/docs/public/prototypes/client-list.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/client-order.svg b/docs/public/prototypes/client-order.svg
index 1f52632..f9ece61 100644
--- a/docs/public/prototypes/client-order.svg
+++ b/docs/public/prototypes/client-order.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/dashboard.svg b/docs/public/prototypes/dashboard.svg
index eca19d6..cac3955 100644
--- a/docs/public/prototypes/dashboard.svg
+++ b/docs/public/prototypes/dashboard.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/login.svg b/docs/public/prototypes/login.svg
index 181056a..8fb27a2 100644
--- a/docs/public/prototypes/login.svg
+++ b/docs/public/prototypes/login.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/manager-order.svg b/docs/public/prototypes/manager-order.svg
index 348c273..37d355d 100644
--- a/docs/public/prototypes/manager-order.svg
+++ b/docs/public/prototypes/manager-order.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/manager-orders.svg b/docs/public/prototypes/manager-orders.svg
index acbe40b..66e4c8e 100644
--- a/docs/public/prototypes/manager-orders.svg
+++ b/docs/public/prototypes/manager-orders.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/product-card.svg b/docs/public/prototypes/product-card.svg
index ec704f1..086cbd3 100644
--- a/docs/public/prototypes/product-card.svg
+++ b/docs/public/prototypes/product-card.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/profile.svg b/docs/public/prototypes/profile.svg
index 5279d04..c52c683 100644
--- a/docs/public/prototypes/profile.svg
+++ b/docs/public/prototypes/profile.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/public/prototypes/sync-settings.svg b/docs/public/prototypes/sync-settings.svg
index 23ac3c9..367da89 100644
--- a/docs/public/prototypes/sync-settings.svg
+++ b/docs/public/prototypes/sync-settings.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/scripts/generate-prototypes.mjs b/docs/scripts/generate-prototypes.mjs
new file mode 100644
index 0000000..0bb8ef8
--- /dev/null
+++ b/docs/scripts/generate-prototypes.mjs
@@ -0,0 +1,491 @@
+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 ``;
+}
+
+function line(x1, y1, x2, y2, options = {}) {
+ return ``;
+}
+
+function text(x, y, value, options = {}) {
+ const {
+ size = 16,
+ weight = 500,
+ fill = C.dark,
+ anchor = 'start',
+ } = options;
+
+ return `${esc(value)}`;
+}
+
+function circle(cx, cy, r, options = {}) {
+ return ``;
+}
+
+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 ``;
+}
+
+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, [
+ ['catalog_snapshot', 'Каталог и остатки'],
+ ['balances_snapshot', 'Задолженность клиентов'],
+ ['orders_snapshot', 'Заказы клиентов'],
+ ], 3),
+ 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');
+}
diff --git a/docs/tz-fregat.typ b/docs/tz-fregat.typ
index a689f4a..4c8a8cb 100644
--- a/docs/tz-fregat.typ
+++ b/docs/tz-fregat.typ
@@ -1064,7 +1064,7 @@
[Главная страница],
[/],
[клиент],
- [стартовая точка, быстрые действия, актуальные события],
+ [стартовый экран каталога],
[Каталог],
[/products],
[клиент],
@@ -1330,17 +1330,16 @@
Назначение страницы:
-- вход в основные разделы личного кабинета
-- отображение актуальных действий и событий
+- отображение каталога товарных направлений
+- переход к карточке выбранного типа товара
Состав страницы:
- верхняя навигация
-- быстрые переходы по основным разделам
-- блок актуальных заказов и заявок
-- блок последних уведомлений
-- блок бонусной информации при наличии подключенного бонусного контура
-- индикатор статуса заполненности профиля клиента
+- заголовок раздела
+- поиск по типу товара
+- сетка карточек товарных направлений
+- переход в карточку выбранного товарного направления
Wireframe-прототип:
@@ -1393,7 +1392,6 @@ Wireframe-прототип:
- блок индивидуальных возможностей, если они разрешены
- блок добавления в корзину
- таблица доступных вариантов
-- блок навигации к соседним товарным направлениям
Маршрут страницы:
@@ -1485,15 +1483,14 @@ Wireframe-прототип:
Назначение страницы:
- запуск входа в систему
-- запуск самостоятельной заявки на подключение
- запуск сценариев входа через мессенджеры
Состав страницы:
- форма запроса кода входа
-- выбор канала входа
-- ссылка на самостоятельную заявку на подключение
-- блок пояснения по дальнейшему сценарию доступа
+- форма проверки кода из письма
+- кнопки входа через доступные мессенджеры
+- служебное сообщение о результате входа либо привязки канала
Wireframe-прототип:
@@ -1514,7 +1511,7 @@ Wireframe-прототип:
Состав страницы:
- фильтры
-- таблица заказов
+- карточки заказов
- переход в карточку конкретного заказа
=== Уведомления
@@ -1539,7 +1536,7 @@ Wireframe-прототип:
- история начислений и списаний
- связанные реферальные сведения
- форма подачи заявки на использование либо вывод бонусов
-- правила бонусной программы
+- магазин вознаграждений
Wireframe-прототип:
@@ -1563,8 +1560,7 @@ Wireframe-прототип:
Состав страницы:
- поиск и фильтры
-- таблица клиентов
-- индикаторы активности и количества заказов
+- сетка карточек клиентов
- действие приглашения нового клиента
Wireframe-прототип:
@@ -1582,15 +1578,12 @@ Wireframe-прототип:
- просмотр сведений о компании
- просмотр истории заявок и заказов
-- просмотр бонусных и реферальных данных
Состав страницы:
- карточка компании и контактных данных
- реквизиты контрагента
- список заказов клиента
-- список бонусных операций
-- связанные рефералы
Wireframe-прототип:
@@ -1653,7 +1646,6 @@ Wireframe-прототип:
- управление параметрами товарных направлений
- управление стандартными значениями параметров
- управление возможностями кастомизации
-- управление описаниями параметров
Состав страницы:
@@ -1671,20 +1663,20 @@ Wireframe-прототип:
)
-=== Настройки синхронизации и уведомлений
+=== Настройки синхронизации
Назначение страницы:
-- управление шаблонами уведомлений
-- управление параметрами интеграционного обмена
+- просмотр состояния интеграционного обмена
+- контроль последних загрузок данных
Состав страницы:
-- список шаблонов уведомлений
-- каналы отправки
- статусы последних синхронизаций
-- диагностическая информация по обмену
+- количество загруженных записей
+- дата последней загрузки
+- примечание по каждому потоку обмена
Wireframe-прототип: