Files
portal/app/resources/js/plugins/vuetify.ts
T
Дмитрий 4a439ebb80 merge: правка телеграм-смены от 01:59 сведена — на бою снова обе работы
Вторая смена выложила на боевой свою свежую правку в 01:55–02:00, поверх выката
СМС-модуля. Их выкладка заменила сборку экранов целиком, а СМС-модуля в их ветке
нет — в результате экраны клиентских СМС на бою пропали из сборки, хотя код,
маршруты и таблицы остались целы. Их телеграм-правка при этом работала.

Сверка байт в байт показала: расходились ровно 5 файлов, все из их записи
f2ac3f4c7. Остальные 2432 файла кода совпадали.

Сведено, разрешено две склейки:
- phpstan-baseline.neon: взята наша вычищенная сторона, 15 строк заметания не
  возвращены — статанализ после сведения даёт 0 без них;
- vuetify.ts свёлся сам, но обе смены завели одно и то же имя значка
  mdi-file-download-outline. Оставлена одна запись — FileDown вместо простой
  стрелки: их комментарий гласил, что точного значка в Lucide нет, а он есть.
  Запись обслуживает обе кнопки, в комментарии это записано.

Проверено после сведения: экраны 251 файл, 2033 зелёных, 3 пропущено, 0 падений;
PHP по обоим модулям 714 тестов, 714 зелёных; статанализ 0; проверка типов 0.

Выложено на боевой: код и пересобранные экраны. После выкладки сверка байт в байт
всех 2626 файлов кода на бою против этой ветки — ноль расхождений. В сборке
присутствуют и экраны СМС, и экран телеграм-рекламы. Порталы 200, службы активны.

🪤 Урок, который стоил ночи: две смены выкатывают на один сервер из разных деревьев,
и выкладка всегда несёт ВЕСЬ фронт одним куском. Перед любой выкладкой обязательно
сверять слепками, чей код сейчас на бою, и забирать чужое к себе — иначе выкат
молча откатывает соседа.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 02:40:42 +03:00

446 lines
15 KiB
TypeScript

// @ts-expect-error vuetify/styles — CSS-импорт без d.ts
import 'vuetify/styles';
import { h, type Component } from 'vue';
import { createVuetify } from 'vuetify';
import { ru } from 'vuetify/locale';
import type { ThemeDefinition, IconSet, IconProps } from 'vuetify';
import {
Activity,
AlertCircle,
AlertTriangle,
Archive,
ArrowDown,
ArrowLeft,
ArrowRightLeft,
ArrowUp,
Ban,
BanknoteX,
Bell,
BellOff,
Bot,
Calendar,
CalendarClock,
CalendarDays,
CalendarRange,
Camera,
Check,
CheckCheck,
CheckCircle,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
ChevronUp,
Circle,
CircleDot,
CirclePause,
CirclePlay,
CircleStop,
CircleX,
Clock,
Code,
Columns3,
Copy,
CreditCard,
Crosshair,
Download,
ExternalLink,
Eye,
EyeOff,
FilterX,
FileCheck,
FileDown,
FileSignature,
FileSpreadsheet,
FileText,
FileUp,
FlaskConical,
Folder,
Folders,
Globe,
HelpCircle,
History,
Hourglass,
IdCard,
Image as ImageIcon,
Info,
Key,
KeyRound,
LayoutDashboard,
LifeBuoy,
LineChart,
Link2,
List,
ListFilter,
LogOut,
Mail,
MapPinned,
Megaphone,
Menu,
MessageSquare,
MessageSquareText,
Minus,
MoreVertical,
Paperclip,
Pause,
Pencil,
Phone,
Play,
Plus,
PlusCircle,
Puzzle,
Radar,
ReceiptText,
RefreshCw,
Repeat,
Repeat2,
Rocket,
RotateCcw,
RotateCw,
RussianRuble,
Save,
Search,
Send,
SendHorizontal,
Settings,
Share2,
Shield,
ShieldCheck,
ShieldOff,
Square,
SquareCheck,
SquareMinus,
Star,
StarHalf,
Tag,
Target,
Trash2,
Undo2,
Upload,
User,
UserCheck,
UserCog,
UserPlus,
UserRoundSearch,
Users,
UserSearch,
UserX,
Wallet,
Webhook,
X,
XCircle,
} from 'lucide-vue-next';
/**
* Палитра Forest extended (Iteration 1 — Quiet Luxury redesign).
* Spec: docs/superpowers/specs/2026-05-12-portal-redesign-quiet-luxury-design.md §3
* CSS-токены: app/resources/css/tokens.css (single source of truth)
*
* 14 OKLCH-статусов воронки маппятся на slugs из db/schema.sql:2076 (lead_statuses)
* через `useStatusPill` composable, НЕ через Vuetify theme.
*/
const liderraForest: ThemeDefinition = {
dark: false,
colors: {
background: '#F6F3EC',
surface: '#FFFFFF',
primary: '#0F6E56',
'on-primary': '#FFFFFF',
secondary: '#012019',
'on-secondary': '#F6F3EC',
success: '#2E8B57',
warning: '#D9A441',
// Текстовые двойники success/warning. Сами брендовые цвета не трогаем —
// они остаются для заливок, иконок и рамок, где 4.5:1 не требуется. Но
// ТЕКСТОМ они нечитаемы: success даёт 4.25:1 на белой карточке и 3.83:1
// на ивори #F6F3EC, warning — 2.25/2.03 при норме WCAG 2.1 AA 4.5:1.
// Затемнённые двойники: 6.10/5.51 и 5.37/4.85 — тон сохранён.
// Использовать как text-success-strong / text-warning-strong.
'success-strong': '#256F46',
'warning-strong': '#8A6410',
error: '#B83A3A',
info: '#3F7C95',
// Расширения — для data viz и semantic uses
'liderra-plum': '#7A5BA3',
'liderra-salmon': '#CC6E50',
'liderra-teal-deep': '#0A5A47',
'liderra-muted': '#6B6356',
},
};
/**
* Liderra Lucide IconSet (CTO-19 closure, v1.83).
* Spec: docs/superpowers/specs/2026-05-13-cto-19-lucide-icon-migration-design.md
*
* Maps 80 mdi-* semantic-ID strings (used across 52 Vue/TS files in resources/js/)
* to Lucide Vue components for rendering. Views НЕ touched — mdi-* строки остаются
* как semantic identifiers, рендерятся через Lucide stroke-based SVG icons.
*
* CLAUDE.md §2 «Иконки: Lucide» — бренд-spec compliance.
*
* Экспортируется наружу РАДИ СТОРОЖА: имя, которого тут нет, молча подменяется
* на HelpCircle (см. ниже), и на экране вместо значка появляется вопрос в кружке.
* Замер 02.08.2026: так молча ломались 43 имени из 162. Сторож —
* tests/Frontend/znachki-est-v-karte.spec.ts.
*/
export const lucideMap: Record<string, Component> = {
'mdi-account-arrow-right-outline': UserCheck,
'mdi-account-group-outline': Users,
'mdi-account-outline': User,
'mdi-account-plus-outline': UserPlus,
'mdi-account-switch': UserCog,
'mdi-alert-circle': AlertCircle,
'mdi-alert-circle-outline': AlertCircle,
'mdi-alert-outline': AlertTriangle,
'mdi-api': Webhook,
'mdi-archive': Archive,
'mdi-arrow-down': ArrowDown,
'mdi-arrow-left': ArrowLeft,
'mdi-arrow-up': ArrowUp,
'mdi-autorenew': RotateCw,
'mdi-bell-off-outline': BellOff,
'mdi-bell-outline': Bell,
'mdi-bullhorn-outline': Megaphone,
'mdi-camera': Camera,
'mdi-cash-plus': PlusCircle,
'mdi-chart-box-outline': LayoutDashboard,
'mdi-check': Check,
'mdi-check-circle': CheckCircle,
'mdi-check-circle-outline': CheckCircle,
'mdi-chevron-right': ChevronRight,
'mdi-clock-check-outline': Clock,
'mdi-clock-outline': Clock,
'mdi-close': X,
'mdi-close-circle': XCircle,
'mdi-cog-outline': Settings,
'mdi-comment-outline': MessageSquare,
'mdi-content-copy': Copy,
'mdi-content-save-outline': Save,
'mdi-credit-card-outline': CreditCard,
'mdi-currency-rub': RussianRuble,
'mdi-delete': Trash2,
'mdi-delete-outline': Trash2,
'mdi-dots-vertical': MoreVertical,
'mdi-download': Download,
'mdi-email-outline': Mail,
'mdi-eye': Eye,
'mdi-eye-off': EyeOff,
'mdi-eye-outline': Eye,
'mdi-file-pdf-box': FileText,
'mdi-filter-off': FilterX,
'mdi-folder-multiple-outline': Folders,
'mdi-folder-outline': Folder,
'mdi-format-list-bulleted': List,
'mdi-key': Key,
'mdi-lock-reset': KeyRound,
'mdi-logout': LogOut,
'mdi-magnify': Search,
'mdi-message-text': MessageSquareText,
'mdi-pause': Pause,
'mdi-pencil': Pencil,
'mdi-pencil-outline': Pencil,
'mdi-phone': Phone,
'mdi-play': Play,
'mdi-plus': Plus,
'mdi-plus-circle-outline': PlusCircle,
'mdi-progress-clock': Clock,
'mdi-pulse': Activity,
'mdi-puzzle-outline': Puzzle,
'mdi-receipt-text-check-outline': ReceiptText,
'mdi-refresh': RefreshCw,
'mdi-restore': RotateCcw,
'mdi-shield-account-outline': ShieldCheck,
'mdi-shield-key': Shield,
'mdi-shield-lock-outline': ShieldCheck,
'mdi-shield-off': ShieldOff,
'mdi-stop-circle-outline': CircleStop,
'mdi-swap-horizontal': ArrowRightLeft,
'mdi-tag-arrow-right': Tag,
'mdi-test-tube': FlaskConical,
'mdi-trash-can-outline': Trash2,
'mdi-view-column-outline': Columns3,
'mdi-view-dashboard-outline': LayoutDashboard,
'mdi-wallet-outline': Wallet,
'mdi-web': Globe,
'mdi-xml': Code,
// UI-аудит 21.06.2026 — иконки, ранее падавшие в HelpCircle-fallback («?»):
// bulk-бар проектов, импорт, экспорт отчётов/сделок, помощь, действия админки.
'mdi-account-cancel': UserX,
'mdi-cash-edit': Pencil,
'mdi-cash-refund': RotateCcw,
'mdi-database-import-outline': Upload,
'mdi-delete-forever': Trash2,
'mdi-earth': Globe,
'mdi-file-delimited-outline': FileText,
'mdi-file-document-outline': FileText,
'mdi-file-excel-outline': FileSpreadsheet,
'mdi-format-list-checks': List,
'mdi-information-outline': Info,
'mdi-lifebuoy': LifeBuoy,
'mdi-target': Target,
// Раздел «Рекламные возможности» (витрина будущих каналов).
'mdi-robot-outline': Bot,
'mdi-send-outline': Send,
// Vuetify-internal default mdi-* aliases (CTO-19 closure extension)
'mdi-chevron-down': ChevronDown,
'mdi-chevron-left': ChevronLeft,
'mdi-menu-down': ChevronDown,
'mdi-menu-right': ChevronRight,
'mdi-menu-up': ChevronUp,
'mdi-menu': Menu,
'mdi-page-first': ChevronsLeft,
'mdi-page-last': ChevronsRight,
'mdi-checkbox-marked': SquareCheck,
'mdi-checkbox-blank-outline': Square,
'mdi-minus-box': SquareMinus,
'mdi-radiobox-marked': CircleDot,
'mdi-radiobox-blank': Circle,
'mdi-circle': Circle,
'mdi-information': Info,
'mdi-minus': Minus,
'mdi-calendar': Calendar,
'mdi-calendar-month': CalendarDays,
'mdi-paperclip': Paperclip,
'mdi-unfold-more-horizontal': ChevronsUpDown,
'mdi-window-close': X,
'mdi-cached': RefreshCw,
'mdi-star': Star,
'mdi-star-outline': Star,
'mdi-star-half-full': StarHalf,
// ——— Рекламные экраны (приёмка глазами 02.08.2026) ———
// Этих имён в карте не было, и на экране вместо значка стоял вопрос в
// кружке: у ярлыков «Проверяем вручную» и «Запущена в кабинете МТС», у поля
// «Картинка или видео», у заголовка телеграм-экрана. Ярлык состояния с
// вопросом вместо значка читается как «портал сам не знает, что происходит».
'mdi-account-search-outline': UserSearch,
'mdi-calendar-clock': CalendarClock,
'mdi-calendar-range': CalendarRange,
'mdi-cancel': Ban,
'mdi-check-all': CheckCheck,
'mdi-file-check-outline': FileCheck,
'mdi-file-outline': FileText,
'mdi-file-upload-outline': FileUp,
// ——— Экраны СМС-рассылок клиента, 03.08.2026 ———
// Пойманы сторожем значков при сведении веток: три имени с экранов СМС
// (админский и клиентский) в карте отсутствовали и рисовались вопросом
// в кружке. Имена подобраны по смыслу: подписанный документ — согласие
// клиента, удостоверение — карточка имени отправителя, файл со стрелкой
// вниз — скачивание образца базы номеров.
// 🪤 Последнее имя параллельно завела и телеграм-смена (для кнопки «Скачать
// образец» в «Моей базе номеров»), взяв простую стрелку `Download` — «точного
// значка в Lucide нет». Он есть, это `FileDown`; при сведении оставлена одна
// запись, она обслуживает обе кнопки.
'mdi-file-sign': FileSignature,
'mdi-account-badge-outline': IdCard,
'mdi-file-download-outline': FileDown,
'mdi-help-circle-outline': HelpCircle,
'mdi-image-outline': ImageIcon,
'mdi-link-variant': Link2,
'mdi-pause-circle': CirclePause,
'mdi-play-circle': CirclePlay,
'mdi-repeat': Repeat,
'mdi-repeat-variant': Repeat2,
'mdi-rocket-launch-outline': Rocket,
'mdi-send-check-outline': SendHorizontal,
'mdi-send-circle-outline': Send,
'mdi-tag-outline': Tag,
'mdi-upload': Upload,
// ——— Остальные экраны портала: закрытие долга 02.08.2026 ———
// Те же 43 сломанных имени, но вне рекламы: личный кабинет, отдел продаж,
// проекты, счета, админка. Раньше держались списком «известного долга» в
// стороже — список опустошён, значит на этих экранах вопросов в кружке
// больше нет.
//
// 🪤 Букв в Lucide нет: имена вида `mdi-alpha-*` подобраны ПО СМЫСЛУ канала —
// Яндекс глобусом, ВК значком сети, Телеграм самолётиком. Точной буквы «В»
// или «Я» не будет, и искать её бесполезно.
'mdi-account-check-outline': UserCheck,
'mdi-account-clock-outline': Hourglass,
'mdi-account-eye-outline': UserRoundSearch,
'mdi-account-multiple-outline': Users,
'mdi-account-plus': UserPlus,
'mdi-alpha-v-box-outline': Share2,
'mdi-alpha-v-circle': Share2,
'mdi-alpha-y-circle': Globe,
'mdi-bullseye-arrow': Crosshair,
'mdi-cash-remove': BanknoteX,
'mdi-chart-line': LineChart,
'mdi-clock-edit-outline': CalendarClock,
'mdi-close-circle-outline': CircleX,
'mdi-file-document-check-outline': FileCheck,
'mdi-filter-variant': ListFilter,
'mdi-history': History,
'mdi-map-search-outline': MapPinned,
'mdi-message-text-outline': MessageSquareText,
'mdi-open-in-new': ExternalLink,
'mdi-pause-circle-outline': CirclePause,
'mdi-radar': Radar,
'mdi-send-circle': Send,
'mdi-undo': Undo2,
};
const liderraLucideSet: IconSet = {
component: (props: IconProps) => {
const Icon = lucideMap[String(props.icon)] || HelpCircle;
return h(Icon, { size: 20, strokeWidth: 1.75 });
},
};
export const vuetify = createVuetify({
// Русская локаль Vuetify — иначе дефолтные строки таблиц/пагинации на
// английском («No data available», «Items per page», «0-0 of 0») на всех экранах.
locale: {
locale: 'ru',
fallback: 'en',
messages: { ru },
},
theme: {
defaultTheme: 'liderraForest',
themes: { liderraForest },
},
icons: {
defaultSet: 'liderra',
sets: { liderra: liderraLucideSet },
},
defaults: {
VBtn: {
variant: 'flat',
rounded: 'lg',
},
VCard: {
rounded: 'lg',
variant: 'flat',
border: true,
},
VTextField: {
variant: 'outlined',
density: 'comfortable',
color: 'primary',
},
VTextarea: {
variant: 'outlined',
density: 'comfortable',
color: 'primary',
},
VSelect: {
variant: 'outlined',
density: 'comfortable',
},
VChip: {
rounded: 'pill',
size: 'small',
},
VDataTable: {
density: 'comfortable',
},
VDialog: {
scrim: 'rgba(1, 32, 25, 0.32)',
},
},
});