я бы купил бы такой CMS тыс эдак за 5к руб
Спасибо большое за ваш интерес, за предложение и оценку

На данный момент проект еще делаю, нужно максимально закрыть все баг репорты.
Пока что лучше примите участие в тестировании проекта. Буду очень благодарен.
Что нового было добавлено и улучшено :
- Пошаговое и плавное увеличение (Zoom) :
- Кнопка «+» (Увеличить) для постепенного масштабирования (100% → 125% → 150% → 200% → 300% → 400% → 600% → 800%).
- Кнопка «-» (Уменьшить) для пошагового отдаления.
- Индикатор текущего масштаба в процентах.
- Просмотр в 1:1 оригинальном разрешении :
- Специальная кнопка «1:1 Оригинал» (автоматически рассчитывает реальное пиксельное разрешение снимка и отображает его пиксель-в-пиксель без сжатия).
- Кнопка «Сбросить масштаб» (вписывает изображение обратно в экран).
- Интерактивное управление и перемещение (Pan & Drag) :
- Перетаскивание зажатой мышью / пальцем: при приближении можно перетаскивать картинку в любую сторону.
- Колесико мыши: скролл вверх приближает, скролл вниз отдаляет.
- Двойной клик: переключает масштаб между обычным и оригинальным размером.
- Удобная галерея и навигация:
- Нижняя карусель миниатюр для быстрого переключения фото.
- Кнопки навигации «Вперед» / «Назад» и клавиатурные хоткеи (←, →, +, -, 0, Esc).
Проблема с кнопкой
«Удалить» на странице товара была исправлена.
Причина возникновения проблемы
В предварительном просмотре приложения, встроенном через iframe, вызовы браузерного Диалога window.confirm() блокируются политиками безопасности браузеров (Chrome/Webkit). Из-за этого window.confirm() мгновенно возвращал false без вывода окна, и запрос на удаление товара на сервер не отправлялся.
Что было сделано для исправления:
- Кастомное модальное окно подтверждения:
- Заменен вызов window.confirm() на встроенный модальный диалог React в MainLayout.tsx.
- При нажатии на кнопку «Удалить» на странице любого товара открывается интерактивное модальное окно с предупреждением и подтверждением действия («Вы уверены, что хотите удалить товар?»).
- Надежная обработка запроса на сервере:
- Обновлен роут DELETE /api/products/:id в server/routes/products.ts, с поддержкой передачи заголовка идентификации пользователя x-user-id.
- Сервер каскадно удаляет все связанные записи (избранное, сравнения, отзывы, товар из подборок, корзин и историю) и удаляет сам товар из БД.
- Обновление состояния и навигация:
- При успешном удалении товара пользователь автоматически перенаправляется на главную страницу каталога (/), обнуляется выбранный товар, обновляются данные с сервера и выводится уведомление: «Товар успешно удален!».
При удалении товара полностью очищаются все его связи и ассоциированные данные, исключая появление «мусорных» или осиротевших записей в базе данных:
1. Связи и таблицы, которые автоматически очищаются
- Избранное (favorites): удаляются все записи у всех пользователей, которые добавили этот товар в свои «Закладки».
- Сравнение товаров (comparisons): товар полностью удаляется из панелей сравнения.
- Отзывы и рейтинги (reviews): удаляются все оценки, текстовые отзывы покупателей и ответы продавцов/администратора к данному товару.
- Пользовательские коллекции (collection_products): товар удаляется из всех папок и подборок пользователей.
- Корзина покупателей (cart_items): товар очищается из корзин текущих пользователей и гостей.
- Значения характеристик (product_custom_values): удаляются значения всех настраиваемых полей и спецификаций данного товара.
- История изменения цен (product_price_history): очищается график и хронология изменения цен товара.
- История просмотров (viewing_history): удаляются записи из списков «Недавно просмотренные».
- Системные уведомления (notifications): очищается привязка товара из оповещений пользователей.
- Файловая система (/uploads/products/{id}/): с сервера удаляется отдельная папка со всеми загруженными изображениями и медиафайлами товара.
Реализован функционал фильтрации Дополнительных Характеристик (Пользовательских Полей) по выбранной Категории товара при добавлении и редактировании объявлений:
- Категориальная фильтрация пользовательских полей (useCategoryCustomFields):
- Создан специализированный хук useCategoryCustomFields согласно методологии Feature-Sliced Design (FSD) и принципу Single Responsibility Principle (SRP).
- При выборе или изменении Категории во вкладке «Общие параметры» список Дополнительных Характеристик автоматически фильтруется и загружает поля, привязанные непосредственно к выбранной категории, её родительским/дочерним категориям или универсальные (глобальные) характеристики.
- Интерактивный UI во вкладке «Дополнительные характеристики» (CustomFieldsTab):
- Добавлен информационный бейдж с иконкой и именем выбранной категории, отображающий количество доступных характеристик.
- На кнопке переключения вкладок формы появился динамо-счетчик доступных полей для выбранной категории.
- В случае отсутствия характеристик у категории или при невыбранной категории выводится информационное уведомление.
- Сохранение и фильтрация данных:
- При отправке формы сохраняются только те значения характеристик, которые принадлежат активной категории, что предотвращает появление устаревших мусорных записей в базе данных.
- Поддержка PHP REST API и миграций:
- В public/php_rest_api/config.php и ProductController.php обновлены схемы таблицы custom_fields (поля category_id и show_in_filter) с авто-миграциями ALTER TABLE для серверного хостинга.
- Мультиязычная локализация:
- Зарегистрированы и переведены все новые фразы на 5 языков (RU, EN, UK, DE, ES) в translations.ts.
Вся необходимая функциональность для связи
Один ко Многим (1:M) между Пользовательскими Полями и Категориями:
1. Архитектура БД и PHP API Backend
- База данных: Создана связующая таблица custom_field_categories (field_id, category_id), поддерживающая множественную привязку характеристических полей к различным категориям каталога.
- PHP REST API Controller: Методы createCustomField, updateCustomField, deleteCustomField и getDbStateData обновлены для синхронизации массива category_ids в таблице custom_field_categories.
2. Типизация и Хранилище Данных
- Интерфейс TypeScript (CustomField): Поле category_ids?: number[] добавлено в базовый тип CustomField.
- Слой данных (db-store.ts): Автоматически объединяет и агрегирует списки категорий при загрузке состояния БД.
3. Интерактивный Модальный Выбор Категорий (CategorySelectorModal)
- Модальное окно вызова: В Admin Console в разделе «Пользовательские и зависимые поля» при создании или редактировании поля кнопка выбора открывает удобный модальный селектор.
- Иерархическая структура: Поддерживает древовидное отображение родительских категорий и вложенных подкатегорий с раскрывающимися списками.
- Быстрый поиск и пакетные действия: Интегрированы строка поиска по названию категорий, а также кнопки быстрого выбора («Выбрать все», «Снять выбор»).
- Отображение бейджей: В таблице характеристик созданные поля наглядно отображают список всех привязанных категорий с помощью стильных цветовых индикаторов.
4. Динамическая Фильтрация в Форме Добавления Товара и Каталоге
- Форма создания товара: В блоке «Additional Specifications» / «Дополнительные Характеристики» отображаются пользовательские поля, привязанные как к самой выбранной категории, так и к её родительским или дочерним категориям, либо общие поля.
- Боковой фильтр каталога (SidebarFilters): Функция getCategoryCustomFields с поддержкой массива category_ids обеспечивает корректную фильтрацию полей в поиске каталога.
Форма управления переводами для раздела «Пользовательские и зависимые поля» визуально улучшена и оптимизирована:
- Языковые вкладки (Tabs): Вместо громоздких списков из 5 одновременно отображаемых текстовых полей, названия и варианты опций полей теперь переключаются с помощью удобных вкладок языков (RU
, EN
, UK
, DE
, ES
).
- Визуальные индикаторы заполнения: На каждой языковой кнопке отображается цветная точка состояния (зеленая — язык заполнен, янтарная/серые — ожидают заполнения) и отметки обязательных языков (*).
- Компактная таблица редактирования: В таблице существующих полей строка редактирования стала аккуратной и не растягивается по высоте.
- Интеграция с PHP REST API: Все измененные структуры данных полностью сохранены и прозрачно передаются в REST API.
Старая версия:
Новая версия:
Обновлен серверный стек
PHP REST API (public/php_rest_api), а также внесены улучшения в мобильный просмотрщик изображений товара:
1. Обновление PHP REST API (public/php_rest_api)
- Синхронизация состояния приложения (getDbStateData): В ProductController.php добавлены массивы countries и cities. Теперь при запросе /api/db-state из приложения на виртуальном хостинге страны и города передаются в клиентское состояние React.
- Автоматическая инициализация данных (config.php): В функцию ensureDatabaseSchema добавлена авто-загрузка стран и крупных городов, если таблица countries пуста.
- Дамп базы данных (database.sql): В файл database.sql добавлены начальные команды INSERT INTO countries и INSERT INTO cities.
- Эндпоинт внешнего списка стран (LocationController.php & index.php): Реализован эндпоинт /api/locations/external/rest-countries (с встроенным резервным справочником WORLD_COUNTRIES_FALLBACK).
- Справочник городов по умолчанию (MAJOR_CITIES_DICT): В метод импорта городов добавлены готовые наборы крупных городов для всех основных стран.
2. Оптимизация мобильного просмотрщика изображений (ProductImageLightbox.tsx)
- Уменьшены отступы элементов масштабирования: Компактные отступы и зазоры на мобильных экранах между кнопкой уменьшения масштаба, процентом (%) и кнопкой увеличения масштаба.
- Классический вывод счетчика: Счетчик изображений выводится в формате 1/3 без выделения текста или фоновых плашек (select-none / pointer-events-none).
Исправлена логика и взаимосвязь полей
Страна и
Город во всех модулях приложения (в фильтрах каталога, сайдбаре/мобильной версии, модальном окне добавления/редактирования товара и профиле пользователя:
- Зависимость и блокировка: Поле выбора города теперь заблокировано (disabled) и визуально отображается неактивным с подсказкой "Сначала выберите страну" до тех пор, пока не будет выбрана конкретная страна.
- Автоматический сброс: При смене страны или выборе значения "Все страны" (или "Выберите страну...") выбранный ранее город автоматически сбрасывается (null), а выпадающий список городов снова становится неактивным.
- Фильтрация по стране: При выборе страны список городов формируется строго для выбранного региона.
Новые улучшения и исправления:
- Ограничение видимости черновиков:
- Черновики теперь отображаются исключительно владельцу соответствующего товара или Администратору (в каталоге, профиле и предпросмотре).
- На страницах просмотра товара при попытке открыть чужой черновик выводятся блокирующие информационные уведомления.
- Запрет отзывов и оценок самому себе:
- Автор товара теперь не может отправлять отзыв или ставить рейтинг на свой собственный товар.
- В форме отзывов ProductReviews для автора показывается соответствующее информационное сообщение, а на сервере (Express и PHP REST API) добавлена проверка безопасности.
- Ссылка на страницу товара в боковой панели предпросмотра (details-panel):
- В заголовок панели p-6 border-b border-slate-100 dark:border-slate-800 flex items-center justify-between bg-slate-50/50 dark:bg-slate-950/20 боковой панели предпросмотра добавлена кнопка «Страница товара» с иконкой внешней ссылки для прямого перехода на отдельную страницу объявления.
- Исправления страницы профиля:
- Дата регистрации: Исправлено отображение «Registered on N/A» на корректную локализованную дату регистрации пользователя (обновлены сопоставления в UserProfile.tsx, db-store.ts и ProductController.php).
- Сохранение страницы при перезагрузке: Обновлен роутинг в AppContext.tsx и MainLayout.tsx — состояние simulatorView синхронизируется с URL (/profile), поэтому при обновлении страницы профиля пользователь остается на странице своего профиля, а не перенаправляется на главную.
Реализовал подсистему отслеживания активности пользователей и статуса присутствия для профилей в соответствии с принципами Feature-Sliced Design (FSD) и Single Responsibility Principle (SRP
Ключевые детали реализации:
Сервис активности и оптимизация базы данных (server/services/activityService.ts) :
- Буферизованный механизм присутствия: Создан сервис кэширования в памяти (activityService) для отслеживания активности пользователей в реальном времени с минимальной нагрузкой на MySQL.
- Низкочастотная запись в БД: Обновления полей last_activity_at и avg_daily_online_seconds в MySQL буферизируются и синхронизируются только при значимых действиях пользователя или через заданные интервалы.
- Порог присутствия: Настроено 5-минутное окно активности (OFFLINE_TIMEOUT_MS = 300 000 мс) для точного различения статусов «в сети» и «не в сети».
- Скользящее среднее за 7 дней: Автоматический расчёт среднего ежедневного времени присутствия в сети в часах и минутах за скользящий 7-дневный период.
Компонент активности пользователя (src/components/profile/UserActivityStatus.tsx) :
- Визуальные индикаторы: Отображаются пульсирующие изумрудные точки статуса для активных сессий и приглушённые серые индикаторы при офлайн-статусе.
- Форматированные строки присутствия: Отображается «В сети» при активности или «Был(а) в сети: дд.мм.гггг, чч:мм» при офлайн-статусе.
- Среднесуточная активность: Форматируется среднее время (например, «В среднем 2 ч 15 мин в день»).
- Интеграция в профиль: Встроен как в мобильные, так и в десктопные шапки обложки профиля, а также на вкладку «Обзор» панели управления в UserProfile.tsx.
Как работает статус «Онлайн / Оффлайн»
1. Спустя какое время пользователь становится Оффлайн?
Пользователь переходит в статус
«Оффлайн» спустя 5 минут после своего последнего действия или последнего отправленного пинга.
- Периодичность проверки (Heartbeat
Пока страница сайта открыта и пользователь авторизован, приложение автоматически каждые 60 секунд (1 минуту) отправляет фоновый сигнал («пинг активности») на сервер.
- Таймаут: Если от пользователя не поступает сигналов в течение 5 минут (300 секунд), система считывает время last_activity_at и переводит статус в «Не в сети» (Offline), а также отображает точную дату и время последнего визита.
2. Какие действия и поведение приводят к переходу в Оффлайн?
Пользователь становится Оффлайн при следующих событиях:
- Закрытие вкладки или браузера — прекращается отправка ежеминутного пинга. Через 5 минут статус меняется на Оффлайн.
- Переход ПК / смартфона в спящий режим — приостанавливается выполнение фоновых скриптов браузера, пинги перестают отправляться.
- Отключение интернета или потеря сети — запросы с сайта перестают доходить до сервера.
- Выход из аккаунта (Logout) — при нажатии кнопки «Выйти» удаляется текущая сессия и немедленно прекращается отправка сигналов активности.
3. Совместимость с PHP REST API (public/php_rest_api)
Все изменения полностью синхронизированы:
- В public/php_rest_api/controllers/UserController.php добавлен метод pingActivity().
- В public/php_rest_api/index.php зарегистрирован роут POST users/activity/ping.
- В public/php_rest_api/config.php и database.sql обновлена структура таблицы users (last_activity_at и avg_daily_online_seconds INT DEFAULT 0).
При развертывании на виртуальном PHP-хостинге подсчет активности и динамика статусов будут работать точно так же, как и на Node.js сервере.
Архитектура очереди пингов и фонового воркера
Да, именно такой архитектурный паттерн с буферизацией в памяти и асинхронным фоновым воркером внедрён в проект:
- Мгновенный ответ (0 мс
При каждом HTTP-пинге /ping сервер не делает мгновенный тяжёлый запись-запрос в MySQL, а за доли миллисекунды обновляет состояние в быстрой оперативной памяти (In-Memory Map) и добавляет userId в Set-очередь изменённых пользователей (dirtyUsersQueue).
- Асинхронный воркер (каждые 10 секунд
Специальный фоновый процесс каждые 10 секунд считывает накопленные user_id из очереди и выполняет эффективную пакетную запись в таблицу users
Почему фоновый воркер с очередью лучше:
- Мгновенный отклик (0 ms задержки для пользователя)
При выполнении HTTP-пинга клиент получает ответ моментально из оперативной памяти (RAM), не дожидаясь ответа от диска MySQL.
- Колоссальное сокращение операций записи (IOPS)
- Без очереди: 1 000 активных пользователей генерируют 1 000 отдельных UPDATE запросов к диску в минуту.
- С очередью и воркером (10 сек
Все пинги накапливаются в памяти (Set очередь) и сбрасываются пакетно раз в 10 секунд. Дублирующие пинги от одного и того же пользователя объединяются в 1 запись. Нагрузка на диск снижается на 80–90%.
- Защита от блокировок таблиц (Lock Contentions)
Частые постоянные записи в таблицу users приводят к блокировкам строк (Row Locking) в InnoDB. Накопление через очередь сглаживает пики нагрузки.
Логика работы статуса Онлайн / Оффлайн:
- Когда пользователь «В сети»:
Клиент каждые 60 секунд отправляет легкий фоновый пинг. Пока разница с последним пингом не превышает 5 минут, статус пользователя отображается как «В сети».
- Когда и почему статус меняется на «Не в сети»:
- Закрыл вкладку или браузер — пинги прекращают отправляться. Спустя 5 минут после последнего пинга статус автоматически меняется на «Не в сети / Был в сети Х минут назад».
- Потеря соединения / пропал интернет — клиент не может достучаться до сервера > 5 минут.
- Неактивность (ушел от компьютера) — если нет действий, статус становится оффлайн через 5 минут.