sk-parket-site

Облачное подключение: рабочая сборка GitHub Pages создаётся командой npm run build:pages и использует общий API/YDB. npm run build:demo сохраняет старый локальный демонстрационный режим и не должен перезаписывать облачную публикацию. Приём реальных заявок пока выключен до замены заглушек политики и согласия. Подробнее: cloud-functions/ydb-stage1/FORM.md.

SK Паркет — сайт и конструктор калькулятора

Первая рабочая серверная версия. Не готовый публичный коммерческий запуск: цены и расход ещё требуют проверки, телефон и фотографии работ — заглушки, юридические документы не оформлены. По умолчанию приём реальных заявок выключен.

Что реализовано

1. Локальный запуск

Нужен Node.js 24 LTS. Сторонних серверных npm-зависимостей нет; SQLite входит в Node.

npm run setup
# Введите новый пароль в скрытом интерактивном запросе.
# Будет создан .env с scrypt-хешем. Сам пароль не записывается.
npm start

Открыть http://localhost:3000/, админка — http://localhost:3000/admin.

HTML нельзя просто открыть двойным щелчком: этому варианту нужен сервер.

Пароль по умолчанию отсутствует. Без настройки хеша сервер откажется запускаться. Хеш, .env, база данных и заявки не должны попадать в GitHub.

2. Как пользоваться админкой

  1. В разделе «Паркет» / «Лак» / «Работы и расходники» найдите строку.
  2. Слева опубликованная цена, справа поле новой цены. Пустое поле оставляет цену черновика; не означает ноль.
  3. «Изменить» — название, характеристики, расход, активность и состав варианта.
  4. «Копия» — удобный способ добавить похожий материал или основание. Замените название, цену и расход.
  5. Для нового основания отметьте операции и материалы, которые входят в расчёт. Для уже готового основания выберите соответствующий режим.
  6. Новый расходник нужно включить в состав основания или в «Правила расчёта → Общий состав».
  7. Выберите базовые варианты, если они должны быть выставлены посетителю по умолчанию.
  8. Нажмите «Сохранить черновик». Сайт не меняется.
  9. Нажмите «Проверить и опубликовать»: проверьте пример, подтвердите состав и опубликуйте.
  10. Если ошиблись, «История цен → Вернуть в черновик», проверьте и опубликуйте нужную версию.

Используемую позицию нельзя удалить, пока она входит в состав. Базовый вариант нельзя отключить/удалить до выбора замены. Скрытие элемента также требует убрать его из активных составов. Между несколькими вкладками действует защита от перезаписи старого черновика: при конфликте нужно обновить страницу; несохранённые изменения автоматически не объединяются.

Для лака нужно вручную согласовать число слоёв в его описании, расход на весь цикл и название/цену работы в общем составе. Конструктор намеренно не делает сложный технологический подбор и не проверяет химическую совместимость продуктов.

3. GitHub и размещение

GitHub — хранилище кода. GitHub Pages не подходит: здесь есть Node-сервер, авторизация и база данных.

Потребуется серверный хостинг с:

Архитектура рассчитана на один экземпляр Node-сервера, не на serverless и не на несколько реплик с отдельными дисками.

Публикуйте репозиторий как private. Ни секреты, ни SQLite не загружайте. Репозиторий может содержать только исходники, seed с демонстрационными ценами и документацию.

Настройки production

На сервере задайте переменные окружения:

Start: npm start, healthcheck: /health. Для Docker: docker build -t sk-parket .; передайте переменные через секреты хостинга и смонтируйте постоянный диск в /app/data (доступен пользователю node, UID 1000). Не копируйте .env внутрь образа.

Смена домена требует обновления PUBLIC_ORIGIN, сертификата и перезапуска. Только HTTPS в production: cookie сессии становится Secure. За proxy используйте HTTPS termination и закройте внешний доступ к порту Node.

4. До приёма реальных заявок

  1. Заменить телефон-заглушку и проверить все кнопки звонка.
  2. Утвердить цены, расход, запасы и состав; снять отметку о демонстрационном расчёте только после проверки.
  3. Заменить /public/privacy.html и /public/consent.html реальными согласованными документами с данными оператора и сроками хранения. Поставить собственную версию согласия в server.mjs вместо policy-v1 при юридически значимом изменении текста.
  4. Выбрать место хранения и провайдера, проверить применимые требования к персональным данным. Этот проект не является юридической экспертизой или гарантией соблюдения закона.
  5. Настроить HTTPS, секреты, резервное копирование, доступ владельца к хостингу с 2FA и срок хранения заявок.
  6. Включить LEADS_ENABLED=true и PRIVACY_OPERATOR_CONFIRMED=yes только после этих действий. Сервер потребует второе подтверждение; наличие флага само по себе не доказывает готовность документов.
  7. Отправить тестовую заявку, проверить её и фото в админке, затем удалить тестовые данные.

До включения сбора форма блокируется, API отвечает отказом. Встроенные проверки не заменяют независимый аудит безопасности перед коммерческим запуском.

5. Безопасность и ограничения

Бэкапы и пароль

Согласованный бэкап SQLite: остановите сервер и копируйте всю DATA_DIR, включая возможные -wal/-shm; либо используйте штатный SQLite online backup. Не копируйте только основной файл во время записи. Храните бэкап отдельно и ограничьте доступ. Проверьте восстановление до запуска.

Для смены пароля повторите npm run setup в безопасном терминале, обновите хеш в секретах хостинга и перезапустите сервер. Для принудительного выхода всех сессий перед перезапуском удалите записи из таблицы sessions через закрытый доступ к базе. Сама смена хеша не удаляет ранее выданные сессии.

Проверки

npm test

Временная база, случайный тестовый пароль, проверка расчёта, сценариев, авторизации, Origin/CSRF, черновика и публикации, конфликта версий, сохранения заявок и снимков сметы, приватности фото, удаления и сохранности после перезапуска. Реальные данные не нужны.

Опционально node test/ui.cjs с установленными Playwright и Chromium (путь к Chromium при необходимости заменить): проверка сайта и админки на разных ширинах, публикации, добавления паркета, переименования основания и отправки тестовой заявки. Выходные изображения пишутся в /data/output, адаптируйте путь на своём компьютере. Browser-test инструменты не нужны для запуска самого сайта.

Файлы

Демонстрация на GitHub Pages

Отдельная статическая сборка находится в docs/. Сборка: npm run build:demo. Pages: Settings → Pages → Deploy from a branch → main → /docs. GitHub Pages может требовать платный тариф для private-репозитория; не меняйте видимость репозитория без согласия владельца.

Это не серверная версия: нет настоящего входа, общей базы, отправки заявок или синхронизации между устройствами. Данные сохраняются в localStorage текущего браузера, включая тестовые фото. Не вводите реальные персональные данные. Очищение хранилища/смена устройства удалит доступ к локальным изменениям. Каждый посетитель видит свой независимый набор цен, если менял их в демо-админке. Главный экран админки — заявки; технические кнопки экспорта, импорта и сброса скрыты. История демо хранит последние 20 публикаций. Если место в localStorage закончилось, сохранение выдаст ошибку — удалите тестовые заявки/фото.

Настоящая версия в public/ и server.mjs сохранена отдельно и не использует демонстрационный обход API. Перед рекламным запуском используйте серверную архитектуру, пройдите проверки и оформите документы.

Экспорт заявок

В карточке и подробностях заявки есть «Скачать Excel». XLSX содержит контакты, сохранённую при обращении смету, единицы и количество. Жёлтые ячейки количества и цен редактируются; суммы и итог — формулы. Файл можно импортировать в Google Таблицы. CSV — дополнительная копия со статическими суммами. Экспорт не меняет цены на сайте.

В сборке GitHub Pages номер телефона доступен для повторного ввода, но заявка сохраняется только в текущем браузере и не передаётся мастеру. Уведомление об этом находится возле формы. Это не заменяет серверную базу и защищённую админку.

Цена материалов и расход

Клей, шпаклёвочный состав и лак отображаются в админке с ценой за м². Ввод новой цены в таблице переключает расчёт на площадь × цену за м². В редакторе можно вместо этого выбрать расчёт по упаковкам: цена упаковки ÷ её объём × расход на м²; закупка округляется до целых упаковок. Расход лака задаётся на все три слоя суммарно. Старые данные совместимы, суммы сохранённых заявок не пересчитываются.

Excel: сверху только дата заявки и телефон; в смете одна средняя цена (арифметическая середина сохранённого диапазона) и одна сумма. Количество и цена редактируемы, суммы и итог пересчитываются формулами. CSV содержит те же колонки без формул.

Интерфейс заявок

При входе в раздел открываются актуальные заявки; поиск работает при вводе и кнопкой «Найти». Excel/CSV доступны внутри заявки. Вверху подробностей выделены телефон, дата/время, стоимость, площадь, выбранные материалы и комментарий. Логотип перезагружает админку. После успешной заявки форма заменяется одним сообщением и восстанавливается при повторном открытии. Серверная версия подтверждает отправку только после успешного API-ответа. Статическая версия честно сообщает о локальном сохранении без отправки мастеру.

Имя, заметки и разделы сметы

Имя необязательно, сохраняется в заявке и доступно в списке, подробностях и выгрузке; старые заявки совместимы. SQLite автоматически добавляет колонку имени в старую базу без удаления данных. Заметка мастера видна на плитке и участвует в поиске. Цена черновика находится в самом поле, без дублирующей подписи. Excel/CSV разделены на «Материалы» и «Работы», неизвестные позиции старых заявок — «Прочие позиции». Промежуточные итоги не удваивают общий итог; цены — середина сохранённого диапазона.

Уведомления

Браузер: кнопка «Включить уведомления» на странице заявок. Разрешение запрашивается только после нажатия. Админка проверяет новые ID каждые 30 секунд; исторические заявки при открытии не вызывают уведомления. Контакты в системное уведомление не выводятся. Для уведомлений админка должна оставаться открытой, в фоне браузер может замедлять таймер. Это не Web Push: после закрытия страницы уведомлений нет. GitHub Pages уведомляет только о локальных заявках своего браузера.

Telegram: поддержан только серверной версией. На сервере задайте TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID через секреты окружения (НЕ в GitHub и не в .env.example). Параметры по умолчанию пустые, интеграция выключена. При создании заявки задача попадает в SQLite-очередь в одной транзакции с заявкой; сервер отправляет контакт, имя, комментарий и краткий расчёт. До 5 попыток с задержкой; ошибки доставки не отменяют сохранение заявки, токен в журнал не выводится. Очередь переживает перезапуск сервера. Для личного чата сначала откройте бота и нажмите Start; нужен числовой ID чата получателя. Перед реальными заявками учитывайте передачу персональных данных в Telegram в документах и согласиях.

Исправление синхронизации заявок

Админка слушает изменения локальных данных из других вкладок, события текущей вкладки, возврат фокуса и видимости. Карточки и счётчики обновляются без перезагрузки страницы; фокус в поиске не блокирует получение заявок, строка поиска сохраняется. Опрос сервера остаётся каждые 30 секунд. Excel использует числовой денежный формат рублей с локалью ru-RU (419), включая цены, суммы и итог, без преобразования значений в текст.

Интерфейс и гарантия

Первый экран содержит единые краткие преимущества; подробные условия трёхлетней гарантии на качество работ находятся в нижнем блоке согласований. Обычный износ покрытия отделён от дефектов нанесения. Галерея объектов — ручная карусель со свайпом и открытием пары фотографий нажатием; до получения реальных снимков сохранены явные заглушки. В смете укладка предшествует шлифовке, шпаклеванию и лакировке. Вступление формы без локального предупреждения; на GitHub Pages сообщение после сохранения по-прежнему честно сообщает об отсутствии отправки мастеру.

Упрощение интерфейса

Пункт «Порядок заказа» заменён на «Как мы работаем»; остальные пункты и заголовок первого раздела сохранены. Убрана дублирующая кнопка звонка в контактах. Общие обещания и FAQ согласованы со сметой и гарантией. Суммы остаются точными до рубля; при закупке целыми упаковками показывается пояснение. В админке: сгруппированная навигация, поиск при вводе без отдельной кнопки, короткие инструкции, удалено неиспользуемое поле пояснения о ценах. Быстрое изменение цены за м² сохраняет текущий способ расчёта: в режиме упаковок пересчитывается цена упаковки по прежнему расходу, а не включается режим расчёта по площади.

Навигация и руководитель

Меню начинается с калькулятора и содержит отдельные якоря гарантии и частых вопросов; блок «Что входит» сохранён на странице, но исключён из меню. Рядом с фотографией указан Сергей Королёв — руководитель бригады; его роль отражена и в FAQ. Телефон в шапке уменьшен, на мобильных меню доступно с первого экрана. Категории админки визуально отделены от кнопок, активный раздел выделен контрастно и помечен aria-current.

Уведомления

Браузерные уведомления используют Service Worker (notifications-sw.js) с запасным оконным API. Кнопка проверки позволяет отдельно проверить разрешения браузера и устройства. Оповещения о заявках требуют открытой админки; закрытая страница фоновые push-уведомления не получает. Ключ дедупликации ограничен путём приложения и сбрасывается при обнаружении нового набора данных с меньшими ID. GitHub Pages по-прежнему хранит заявки отдельно в каждом браузере: Service Worker не создаёт общей базы и не отправляет их в Telegram. Реальная доставка в Telegram выполняется только размещённой серверной частью через защищённые переменные окружения; токен и получатель не добавляются в публичный код.