/
pizzadox
/
remsmeta
Обзор
Документация
Войти
/
pizzadox
/
remsmeta
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/lib/types.ts
507 строк
17 KB
Z User
26bb6201-76fd-41d9-979e-ac7c5da504c8
22 июл 2026, 18:09
22 июл 2026, 18:09
de0f376
Код
Авторство
О чём код?
// Общие типы приложения «Калькулятор ремонта» export type SurfaceKind = | 'floor' | 'ceiling' | 'wallNorth' | 'wallSouth' | 'wallEast' | 'wallWest' export const WALL_KINDS: SurfaceKind[] = [ 'wallNorth', 'wallSouth', 'wallEast', 'wallWest', ] export const SURFACE_LABELS: Record<SurfaceKind, string> = { floor: 'Пол', ceiling: 'Потолок', wallNorth: 'Стена Север', wallSouth: 'Стена Юг', wallEast: 'Стена Восток', wallWest: 'Стена Запад', } export const SURFACE_SHORT: Record<SurfaceKind, string> = { floor: 'Пол', ceiling: 'Потолок', wallNorth: 'Север', wallSouth: 'Юг', wallEast: 'Восток', wallWest: 'Запад', } export type FinishType = | 'laminate' | 'parquet' | 'vinyl' | 'linoleum' | 'tile' | 'porcelain' | 'paint' | 'wallpaper' | 'wallpaperPaint' | 'plaster' | 'putty' | 'panels' | 'stretch' | 'drywall' | 'none' export const FINISH_LABELS: Record<FinishType, string> = { laminate: 'Ламинат', parquet: 'Паркет', vinyl: 'Кварцвинил / SPC', linoleum: 'Линолеум', tile: 'Плитка керамическая', porcelain: 'Керамогранит', paint: 'Краска', wallpaper: 'Обои', wallpaperPaint: 'Обои под покраску', plaster: 'Штукатурка', putty: 'Шпатлёвка', panels: 'Стеновые панели', stretch: 'Натяжной потолок', drywall: 'ГКЛ / гипсокартон', none: 'Без отделки', } // К каким поверхностям применим тип отделки export const FINISH_APPLICABLE: Record<FinishType, SurfaceKind[]> = { laminate: ['floor', 'wallNorth', 'wallSouth', 'wallEast', 'wallWest'], parquet: ['floor', 'wallNorth', 'wallSouth', 'wallEast', 'wallWest'], vinyl: ['floor', 'wallNorth', 'wallSouth', 'wallEast', 'wallWest'], linoleum: ['floor'], tile: ['floor', 'wallNorth', 'wallSouth', 'wallEast', 'wallWest'], porcelain: ['floor', 'wallNorth', 'wallSouth', 'wallEast', 'wallWest'], paint: ['ceiling', 'wallNorth', 'wallSouth', 'wallEast', 'wallWest'], wallpaper: ['wallNorth', 'wallSouth', 'wallEast', 'wallWest'], wallpaperPaint: ['wallNorth', 'wallSouth', 'wallEast', 'wallWest'], plaster: ['wallNorth', 'wallSouth', 'wallEast', 'wallWest', 'ceiling'], putty: ['wallNorth', 'wallSouth', 'wallEast', 'wallWest', 'ceiling'], panels: ['wallNorth', 'wallSouth', 'wallEast', 'wallWest', 'ceiling'], stretch: ['ceiling'], drywall: ['wallNorth', 'wallSouth', 'wallEast', 'wallWest', 'ceiling'], none: ['floor', 'ceiling', 'wallNorth', 'wallSouth', 'wallEast', 'wallWest'], } export type OpeningKind = 'door' | 'window' | 'arch' export interface Opening { id: string surface: SurfaceKind kind: OpeningKind x: number // м от левого края стены y: number // м от пола width: number // м height: number // м } export interface Surface { id: string kind: SurfaceKind finishType: FinishType materialId?: string | null color?: string | null } export interface Room { id: string name: string width: number // м (X) length: number // м (Z) height: number // м (Y) surfaces: Surface[] openings: Opening[] } export interface Project { id: string name: string notes?: string | null currency: string rooms: Room[] createdAt?: string updatedAt?: string } // ----- AI-режимы (РемСоветник) ----- export type AIMode = 'universal' | 'designer' | 'estimator' | 'builder' export const AI_MODES: AIMode[] = ['universal', 'designer', 'estimator', 'builder'] export const AI_MODE_LABELS: Record<AIMode, string> = { universal: 'Универсал', designer: 'Дизайнер', estimator: 'Сметчик', builder: 'Прораб', } // ----- Геометрия ----- export interface SurfaceGeometry { kind: SurfaceKind width: number // м height: number // м area: number // м² (чистая, без проёмов) grossArea: number // м² (с проёмами) perimeter: number // м (для пола/потолока — периметр помещения, для стены — 2*(w+h)) openingsArea: number // м² corners?: number } // ----- Результаты калькуляторов ----- export interface LaminateResult { boardCount: number // шт досок packCount: number // упаковок boardsPerPack: number totalAreaM2: number // площадь покрытия wastePct: number // % wasteAreaM2: number cost: number layout: LaminateBoard[] // карта кроя rows: number rollWidthMm: number // рабочая ширина доски boardLenMm: number gapMm: number // зазор у стен pattern: 'straight' | 'diagonal' | 'herringbone' offcuts: { lengthMm: number; count: number }[] } /** * Результат расчёта кроя ламината на стену с проёмами. * Доски укладываются горизонтальными рядами; в каждом ряду зоны пересечения * с проёмами исключаются (доска разрезается на сегменты вокруг проёмов). */ export interface WallLaminateResult { /** Кол-во целых досок, необходимых для укладки (с учётом остатков) */ boardCount: number /** Кол-во упаковок */ packCount: number /** Досок в упаковке */ boardsPerPack: number /** Чистая площадь покрытия (стена минус проёмы) */ totalAreaM2: number /** Площадь закупленных досок (брутто) */ grossAreaM2: number /** Процент отходов */ wastePct: number /** Площадь отходов в м² */ wasteAreaM2: number /** Стоимость */ cost: number /** Сегменты досок (видимые части) */ segments: LaminateBoardSegment[] /** Кол-во рядов */ rows: number /** Рабочая ширина доски в мм */ rollWidthMm: number /** Длина доски в мм */ boardLenMm: number /** Зазор у стен в мм */ gapMm: number /** Шаг разбежки в мм */ staggerMm: number /** Ширина стены в мм */ wallWidthMm: number /** Высота стены в мм */ wallHeightMm: number /** Проёмы на стене (в мм) */ openings: WallOpeningRect[] /** Остатки для повторного использования */ offcuts: { lengthMm: number; count: number }[] /** Доски, которые целиком попадают в проём (не нужны) */ skippedInOpenings: number } export interface LaminateBoard { id: number row: number col: number xMm: number // позиция по длине комнаты yMm: number // позиция по ширине комнаты (центр полосы) lenMm: number isOffcutReuse: boolean // из остатка cutFromFull: boolean // рез из целой } /** * Сегмент доски ламината на стене с проёмами. * Одна доска в ряду может быть разрезана проёмом на несколько видимых сегментов. * Например, доска над дверью и доска рядом с дверью — это два сегмента одной "логической" доски. */ export interface LaminateBoardSegment { /** Глобальный id сегмента (уникален в пределах результата) */ id: number /** Номер ряда (по вертикали, сверху вниз или снизу вверх) */ row: number /** Порядковый номер сегмента в ряду (слева направо) */ col: number /** X-координата левого края сегмента в мм (по горизонтали стены) */ xMm: number /** Y-координата верхнего края сегмента в мм (по вертикали стены, от пола) */ yMm: number /** Длина сегмента в мм (по горизонтали) */ lenMm: number /** Высота сегмента в мм = рабочая ширина доски */ heightMm: number /** Длина исходной доски, из которой вырезан сегмент (для учёта распила) */ sourceBoardLenMm: number /** Сегмент из остатка от предыдущего ряда */ isOffcutReuse: boolean /** Сегмент требует реза из целой доски */ cutFromFull: boolean /** Сегмент находится над/под/рядом с проёмом (true) или в чистой зоне (false) */ isAroundOpening: boolean } /** * Прямоугольник проёма на стене в мм (для визуализации и расчёта). */ export interface WallOpeningRect { id: string kind: 'door' | 'window' | 'arch' /** X левого края в мм */ xMm: number /** Y нижнего края в мм (от пола) */ yMm: number /** Ширина в мм */ widthMm: number /** Высота в мм */ heightMm: number /** Подпись для отображения */ label: string } export interface WallpaperResult { rollCount: number rollsNeeded: number stripsPerRoll: number stripsTotal: number stripLenM: number patternRepeatM: number wallHeightM: number wallWidthM: number openingsAreaM2: number usableAreaM2: number cost: number wastePct: number } export interface PaintResult { liters: number coats: number coverageM2PerL: number areaM2: number cans: number canSizeL: number cost: number } export interface DryMixResult { kg: number bags: number bagKg: number layerMm: number areaM2: number consumptionKgPerM2: number cost: number volumeM3: number } export interface TileResult { tilesCount: number boxes: number tilesPerBox: number tileWmm: number tileHmm: number jointMm: number areaM2: number groutKg: number crossCount: number cost: number wastePct: number } export interface BaseboardResult { lengthM: number pieces: number pieceLenM: number corners: number connectors: number cost: number } export interface CalcSummary { label: string qty: string cost: number category: string } /** * Строка детализации калькулятора (для тёплого пола, стяжки и т.д.). * Совместима с CalcSummary по структуре, но без category — это просто line item. */ export interface CalcLineItem { label: string qty: string cost: number } // ----- Тёплый пол (электрический) ----- export interface WarmFloorResult { areaM2: number // общая площадь комнаты usefulAreaM2: number // площадь без мебели (под укладку) perimeterM: number // периметр под демпферную ленту stepCm: number // шаг укладки кабеля cableLengthM: number // длина кабеля, м cablePowerPerM: number // погонная мощность кабеля, Вт/м totalPowerW: number // общая мощность, Вт voltageV: number // напряжение сети currentA: number // сила тока, А wireSectionMm2: number // рекомендованное сечение провода thermostats: number // кол-во терморегуляторов insulationM2: number // утеплитель, м² dampingTapeM: number // демпферная лента, м mountingTapeM: number // монтажная лента для крепления кабеля, м sensorTubeM: number // гофра для датчика, м cost: number items: CalcLineItem[] } // ----- Стяжка / самовыравнивающаяся смесь ----- export interface ScreedResult { areaM2: number // площадь под стяжку layerMm: number // толщина слоя mixType: 'cps' | 'selflevel' // ЦПС 1:3 или наливной пол volumeM3: number // объём раствора dryMixKg: number // масса сухой смеси bags: number // кол-во мешков bagKg: number // вес мешка waterL: number // объём воды, л meshM2: number // армосетка dampingTapeM: number // демпферная лента filmM2: number // полиэтиленовая плёнка perimeterM: number cost: number items: CalcLineItem[] } // ----- Подложка под ламинат ----- export interface UnderlaymentResult { areaM2: number // площадь под подложку coverAreaM2: number // площадь с запасом 7% type: 'foam' | 'cork' | 'fiber' | 'foil' // вспененная/пробка/хвойная/с фольгой thicknessMm: number rolls: number // кол-во рулонов (10 м²/рулон) rollAreaM2: number // площадь рулона vaporBarrierM2: number // пароизоляция tapeM: number // скотч для стыков cost: number items: CalcLineItem[] } // ----- Демонтаж ----- export type DemolitionType = 'laminate' | 'tile' | 'linoleum' | 'screed' export interface DemolitionResult { areaM2: number type: DemolitionType typeLabel: string debrisVolumeM3: number // объём мусора debrisWeightKg: number // вес мусора bags: number // кол-во мешков (120 л) workHours: number // время работы (часы) workCost: number // стоимость работ disposalCost: number // вывоз мусора cost: number // итого items: CalcLineItem[] } // ----- Материал (пресет) ----- export interface MaterialPreset { id: string name: string finishType: FinishType /** Бренд/производитель (например, Knauf, Tarkett, Quick-Step). */ brand?: string /** Короткое описание/подзаголовок (декор, класс, серия). */ description?: string /** Теги для поиска/фильтрации (например, «влагостойкий», «под дерево», «премиум»). */ tags?: string[] /** Популярность 0..100 — влияет на сортировку по умолчанию. */ popularity?: number /** Имя магазина-источника эталонной цены (Leroy Merlin, Petrovich, OBI, ВсеИнструменты, Castorama, Стройпарк…). */ shop?: string /** URL карточки товара у магазина-источника (если известен). */ shopUrl?: string /** ISO-дата последней проверки цены онлайн. */ lastVerified?: string // размерные boardLenMm?: number boardWmm?: number boardsPerPack?: number tileWmm?: number tileHmm?: number tilesPerBox?: number rollWmm?: number // обои ширина rollLenM?: number // обои длина patternRepeatMm?: number // расход coverageM2PerL?: number // краска consumptionKgPerM2?: number // сухие смеси при слое 1 мм defaultLayerMm?: number canSizeL?: number bagKg?: number pieceLenM?: number // плинтус // цена pricePerPack?: number pricePerBox?: number pricePerRoll?: number pricePerLiter?: number pricePerBag?: number pricePerPiece?: number pricePerM2?: number unit?: string color?: string } /** Результат одного онлайн-предложения цены на материал. */ export interface PriceOffer { /** Название магазина. */ shop: string /** Домен магазина. */ host: string /** URL карточки товара. */ url: string /** Заголовок товара из выдачи. */ title: string /** Описание/сниппет из выдачи. */ snippet?: string /** Распознанная цена в рублях (если удалось извлечь). */ price?: number /** Дата публикации (если есть в выдаче). */ date?: string /** Позиция в выдаче поиска. */ rank?: number } /** Полный ответ ручья /api/price-check. */ export interface PriceCheckResult { /** ID/имя запрошенного материала. */ materialId: string /** Поисковый запрос, который был отправлен. */ query: string /** Когда был выполнен запрос (ISO). */ fetchedAt: string /** Список найденных предложений. */ offers: PriceOffer[] /** Самое дешёвое предложение (для подсветки). */ cheapest?: PriceOffer /** Текущая базовая цена пресета для сравнения (если materialId известен). */ basePrice?: number /** Подсказка/рекомендация пользователю. */ hint?: string }