From 72829b5d4669f5b33f2916953164b95007a7b8f4 Mon Sep 17 00:00:00 2001 From: AR 15 M4 Date: Mon, 23 Mar 2026 16:21:40 +0500 Subject: [PATCH] feat(sprint-5.5): add page preview section with Create button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New route /pages/preview with empty state ("Создать") and assembled preview - Preview assembles real site blocks in order (Hero + Doctors ready, rest placeholders) - localStorage persists created state; "Пересобрать" resets it - Extracted HeroBlock and DoctorsBlock as reusable components - Refactored hero and doctors pages to import from components/blocks/ - Sidebar: added "Просмотр страницы" link, bumped to Sprint 5.5 · v0.5.5 - SPRINTS.md: added Sprint 5.5 plan with summary table row Co-Authored-By: Claude Sonnet 4.6 --- apps/web/app/blocks/doctors/page.tsx | 118 ++--------- apps/web/app/blocks/hero/page.tsx | 127 +----------- apps/web/app/pages/preview/PreviewClient.tsx | 200 +++++++++++++++++++ apps/web/app/pages/preview/page.tsx | 11 + apps/web/components/blocks/DoctorsBlock.tsx | 95 +++++++++ apps/web/components/blocks/HeroBlock.tsx | 101 ++++++++++ apps/web/components/layout/Sidebar.tsx | 3 +- docs/SPRINTS.md | 63 ++++++ 8 files changed, 496 insertions(+), 222 deletions(-) create mode 100644 apps/web/app/pages/preview/PreviewClient.tsx create mode 100644 apps/web/app/pages/preview/page.tsx create mode 100644 apps/web/components/blocks/DoctorsBlock.tsx create mode 100644 apps/web/components/blocks/HeroBlock.tsx diff --git a/apps/web/app/blocks/doctors/page.tsx b/apps/web/app/blocks/doctors/page.tsx index beb7de8..5f8156a 100644 --- a/apps/web/app/blocks/doctors/page.tsx +++ b/apps/web/app/blocks/doctors/page.tsx @@ -1,57 +1,20 @@ import type { Metadata } from "next"; import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock"; +import { DoctorsBlock, STATS, DOCTORS } from "@/components/blocks/DoctorsBlock"; export const metadata: Metadata = { title: "Блок «Наши врачи». Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой", }; -const STATS = [ - { num: "27", label: "ЛОР врачей работает в клинике", prefix: "Ежедневно" }, - { num: "6", label: "кандидатов медицинских наук", prefix: "В том числе" }, - { num: "12 000+", label: "успешно проведённых операций", prefix: "Свыше" }, -]; - -const DOCTORS = [ - { - name: "Макарова Людмила Германовна", - spec: "ЛОР врач, сурдолог", - photo: "/doctors/makarova.jpg", - }, - { - name: "Семерикова Наталия Александровна", - spec: "ЛОР врач, сурдолог, хирург. К.М.Н. Завед. Центром сурдологии", - photo: "/doctors/semerikova.png", - }, - { - name: "Ворончихина Наталия Валерьевна", - spec: "Отоневролог, хирург. К.М.Н., доцент кафедры ПГМУ", - photo: "/doctors/voronchikhina.png", - }, - { - name: "Лобанова Ирина Юрьевна", - spec: "ЛОР врач, сурдолог", - photo: "/doctors/lobanova.jpg", - }, - { - name: "Торсунова Наталья Сергеевна", - spec: "Специалист по слухопротезированию (сурдоакустик)", - photo: "/doctors/torsunova.jpg", - }, - { - name: "Суворова Светлана Викторовна", - spec: "ЛОР врач, сурдолог", - photo: "/doctors/suvorova.jpg", - }, -]; - const LLM_DOCTORS_TEXT = ` БЛОК: Наши врачи Источник: perm.oclinica.ru/lor — блок под CEO-текстом -Версия: v1.0 +Версия: v1.1 СТРУКТУРА БЛОКА: 1. ЗАГОЛОВОК H2: «Приём ведут опытные ЛОР врачи» Подзаголовок: описание принципа работы врачей клиники + Размер: ~30px (text-3xl), font-bold, #111827 2. БЛОК СТАТИСТИКИ (3 показателя в ряд): — «Ежедневно 27 ЛОР врачей работают в клинике» @@ -75,8 +38,8 @@ const LLM_DOCTORS_TEXT = ` ПРАВИЛА: ✓ Заголовок H2 + описание обязательны -✓ 3 stat-блока в ряд -✓ Сетка 3 колонки, 2 ряда (6 карточек) +✓ 3 stat-блока в ряд, без фоновых блоков +✓ Сетка 6 колонок (6 карточек в ряд) ✕ Не отображать более 6 врачей в основном блоке ✕ Не убирать статистику `.trim(); @@ -106,69 +69,14 @@ export default function DoctorsBlockPage() { Живой пример
- {/* Заголовок */} -
-

- Приём ведут опытные ЛОР врачи -

-

- Фундаментальная теоретическая подготовка и большой практический опыт в сочетании - с внимательным индивидуальным подходом являются причиной успеха лечения тысяч наших пациентов -

-
- - {/* Статистика — текст тёмно-бирюзовым, без фоновых блоков */} -
- {STATS.map((s) => ( -
-

- {s.prefix} {s.num} {s.label} -

-
- ))} -
- - {/* Сетка врачей — плотнее, имена тёмно-бирюзовым */} -
- {DOCTORS.map((doc) => ( -
- {/* eslint-disable-next-line @next/next/no-img-element */} - {doc.name} -
-

- {doc.name} -

-

- {doc.spec} -

-
-
- ))} -
+
- {/* Стат-блоки */} + {/* Стат-блоки — разбор */}

Блок статистики @@ -217,17 +125,17 @@ export default function DoctorsBlockPage() { @@ -78,7 +64,8 @@ export default function HeroPage() { Hero-баннер

- Главный баннер страницы раздела ЛОР — perm.oclinica.ru/lor. Двухколоночный блок, единый светло-кремовый фон #f9f4e7. + Главный баннер страницы раздела ЛОР — perm.oclinica.ru/lor. Двухколоночный блок, единый светло-кремовый фон{" "} + #f9f4e7.

@@ -87,99 +74,7 @@ export default function HeroPage() {

Живой пример

- - {/* H1 страницы */} -

- ЛОР Клиника ухо, горло, нос – медицинский центр лечения ЛОР заболеваний у детей и взрослых -

- - {/* Баннер — единый светло-кремовый фон */} -
- {/* Левая часть — контент на кремовом фоне */} -
-

- Эндоскопическое хирургическое лечение ЛОР органов -

-
    - {HERO_CHECKS.map((c) => ( -
  • - - ✓ - - - - {c.key} - {" "} - – {c.desc} - -
  • - ))} -
-
- -
-
- - {/* Правая часть — фото врача */} -
- {/* eslint-disable-next-line @next/next/no-img-element */} - Врач на приёме с пациентом -
-
- - {/* Под баннером: соцсети */} -
- - Поделиться: - - {["VK", "FB", "TW"].map((s) => ( - - ))} - - 👁 98 573 просмотра - -
+
{/* Анатомия */} @@ -210,7 +105,7 @@ export default function HeroPage() { - {/* Пункты с галочками */} + {/* Три пункта с галочками */}

Три пункта баннера @@ -222,7 +117,7 @@ export default function HeroPage() { className="flex items-start gap-3 p-3 rounded-lg" style={{ background: "var(--bb-sidebar-bg)", border: "1px solid var(--bb-border)" }} > - +
@@ -237,7 +132,7 @@ export default function HeroPage() { ))}

- Ключевое слово: uppercase + bold. Описание: обычный текст. Галочка: #22c55e. + Ключевое слово: uppercase + bold. Описание: обычный текст. Галочка: #bf9975 (бежевый).

@@ -265,7 +160,7 @@ export default function HeroPage() { ["Фон баннера (единый)", "#f9f4e7", "Светло-кремовый фон"], ["Кнопка CTA", "outline-стиль", "bb-btn-outline"], ["Заголовок блока", "#111827", "—"], - ["Галочка ✓", "#bf9975", "Бежевый (--brand-081m approx.)"], + ["Галочка ✓", "#bf9975", "Бежевый"], ]} /> diff --git a/apps/web/app/pages/preview/PreviewClient.tsx b/apps/web/app/pages/preview/PreviewClient.tsx new file mode 100644 index 0000000..db367a3 --- /dev/null +++ b/apps/web/app/pages/preview/PreviewClient.tsx @@ -0,0 +1,200 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { HeroBlock } from "@/components/blocks/HeroBlock"; +import { DoctorsBlock } from "@/components/blocks/DoctorsBlock"; + +const STORAGE_KEY = "bb-preview-created"; + +function BlockPlaceholder({ name, href }: { name: string; href: string }) { + return ( +
+

+ {name} +

+ + Открыть в брендбуке → + +
+ ); +} + +const BLOCKS: Array<{ + id: string; + name: string; + href: string; + ready: boolean; + component?: React.ReactNode; +}> = [ + { + id: "hero", + name: "Hero-баннер", + href: "/blocks/hero", + ready: true, + component: , + }, + { + id: "ceo", + name: "Вводный текст (CEO-блок)", + href: "/blocks/ceo", + ready: false, + }, + { + id: "doctors", + name: "Наши врачи", + href: "/blocks/doctors", + ready: true, + component: , + }, + { + id: "reviews", + name: "Отзывы", + href: "/blocks/reviews", + ready: false, + }, + { + id: "contact-forms", + name: "Формы записи", + href: "/blocks/contact-forms", + ready: false, + }, + { + id: "news", + name: "Новости", + href: "/blocks/news", + ready: false, + }, + { + id: "footer", + name: "Подвал / Контакт", + href: "/blocks/contact", + ready: false, + }, +]; + +const READY_COUNT = BLOCKS.filter((b) => b.ready).length; + +export function PreviewClient() { + const [created, setCreated] = useState(false); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + if (localStorage.getItem(STORAGE_KEY) === "true") { + setCreated(true); + } + }, []); + + function handleCreate() { + localStorage.setItem(STORAGE_KEY, "true"); + setCreated(true); + } + + function handleRebuild() { + localStorage.removeItem(STORAGE_KEY); + setCreated(false); + } + + // Avoid hydration mismatch — render nothing until mounted + if (!mounted) return null; + + /* ── ПУСТОЕ СОСТОЯНИЕ ── */ + if (!created) { + return ( +
+
+

+ Страницы +

+

+ Просмотр текущей страницы +

+

+ Нажмите «Создать», чтобы собрать главную страницу{" "} + perm.oclinica.ru/lor из блоков, + задокументированных в брендбуке. +

+
+ + + Готово блоков: {READY_COUNT} из {BLOCKS.length} + + · + + {BLOCKS.length - READY_COUNT} плейсхолдеров + +
+
+ +
+
+
+ ); + } + + /* ── СОЗДАННОЕ СОСТОЯНИЕ ── */ + return ( +
+ {/* Топ-бар */} +
+
+

+ Просмотр текущей страницы +

+

+ perm.oclinica.ru/lor · {READY_COUNT}/{BLOCKS.length} блоков готово +

+
+ +
+ + {/* Собранная страница */} +
+ {BLOCKS.map((block) => + block.ready && block.component ? ( +
{block.component}
+ ) : ( +
+ +
+ ) + )} +
+
+ ); +} diff --git a/apps/web/app/pages/preview/page.tsx b/apps/web/app/pages/preview/page.tsx new file mode 100644 index 0000000..f899b52 --- /dev/null +++ b/apps/web/app/pages/preview/page.tsx @@ -0,0 +1,11 @@ +import type { Metadata } from "next"; +import { PreviewClient } from "./PreviewClient"; + +export const metadata: Metadata = { + title: + "Просмотр текущей страницы. Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой", +}; + +export default function PreviewPage() { + return ; +} diff --git a/apps/web/components/blocks/DoctorsBlock.tsx b/apps/web/components/blocks/DoctorsBlock.tsx new file mode 100644 index 0000000..9be73c9 --- /dev/null +++ b/apps/web/components/blocks/DoctorsBlock.tsx @@ -0,0 +1,95 @@ +export const STATS = [ + { num: "27", label: "ЛОР врачей работает в клинике", prefix: "Ежедневно" }, + { num: "6", label: "кандидатов медицинских наук", prefix: "В том числе" }, + { num: "12 000+", label: "успешно проведённых операций", prefix: "Свыше" }, +]; + +export const DOCTORS = [ + { + name: "Макарова Людмила Германовна", + spec: "ЛОР врач, сурдолог", + photo: "/doctors/makarova.jpg", + }, + { + name: "Семерикова Наталия Александровна", + spec: "ЛОР врач, сурдолог, хирург. К.М.Н. Завед. Центром сурдологии", + photo: "/doctors/semerikova.png", + }, + { + name: "Ворончихина Наталия Валерьевна", + spec: "Отоневролог, хирург. К.М.Н., доцент кафедры ПГМУ", + photo: "/doctors/voronchikhina.png", + }, + { + name: "Лобанова Ирина Юрьевна", + spec: "ЛОР врач, сурдолог", + photo: "/doctors/lobanova.jpg", + }, + { + name: "Торсунова Наталья Сергеевна", + spec: "Специалист по слухопротезированию (сурдоакустик)", + photo: "/doctors/torsunova.jpg", + }, + { + name: "Суворова Светлана Викторовна", + spec: "ЛОР врач, сурдолог", + photo: "/doctors/suvorova.jpg", + }, +]; + +export function DoctorsBlock() { + return ( +
+ {/* Заголовок + описание */} +
+

+ Приём ведут опытные ЛОР врачи +

+

+ Фундаментальная теоретическая подготовка и большой практический опыт в сочетании + с внимательным индивидуальным подходом являются причиной успеха лечения тысяч наших пациентов +

+
+ + {/* Статистика — без фона, border-bottom #60959c */} +
+ {STATS.map((s) => ( +
+

+ {s.prefix} {s.num} {s.label} +

+
+ ))} +
+ + {/* Сетка врачей — 6 колонок */} +
+ {DOCTORS.map((doc) => ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {doc.name} +
+

+ {doc.name} +

+

+ {doc.spec} +

+
+
+ ))} +
+
+ ); +} diff --git a/apps/web/components/blocks/HeroBlock.tsx b/apps/web/components/blocks/HeroBlock.tsx new file mode 100644 index 0000000..718e4ed --- /dev/null +++ b/apps/web/components/blocks/HeroBlock.tsx @@ -0,0 +1,101 @@ +export const HERO_CHECKS = [ + { key: "БЕЗОПАСНО", desc: "оперируют хирурги с 15-летним опытом работы" }, + { key: "БЕЗ ВНЕШНИХ РАЗРЕЗОВ", desc: "хирургия сверхмалых размеров" }, + { key: "БЫСТРО", desc: "под наблюдением врача пациент находится 1 сутки" }, +]; + +export function HeroBlock() { + return ( +
+ {/* H1 страницы */} +

+ ЛОР Клиника ухо, горло, нос – медицинский центр лечения ЛОР заболеваний у детей и взрослых +

+ + {/* Баннер — единый светло-кремовый фон */} +
+ {/* Левая часть — контент */} +
+

+ Эндоскопическое хирургическое лечение ЛОР органов +

+
    + {HERO_CHECKS.map((c) => ( +
  • + + ✓ + + + + {c.key} + {" "} + – {c.desc} + +
  • + ))} +
+
+ +
+
+ + {/* Правая часть — фото врача */} +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + Врач на приёме с пациентом +
+
+ + {/* Под баннером: соцсети + просмотры */} +
+ + Поделиться: + + {["VK", "FB", "TW"].map((s) => ( + + ))} + + 👁 98 573 просмотра + +
+
+ ); +} diff --git a/apps/web/components/layout/Sidebar.tsx b/apps/web/components/layout/Sidebar.tsx index 78b60ee..5f99fb3 100644 --- a/apps/web/components/layout/Sidebar.tsx +++ b/apps/web/components/layout/Sidebar.tsx @@ -48,6 +48,7 @@ const NAV: NavSection[] = [ { title: "Страницы", items: [ + { label: "Просмотр страницы", href: "/pages/preview" }, { label: "Главная", href: "/pages/home", soon: true }, { label: "Заболевание", href: "/pages/disease", soon: true }, { label: "Все врачи", href: "/pages/doctors", soon: true }, @@ -166,7 +167,7 @@ export function Sidebar() { color: "var(--bb-sidebar-text-muted)", }} > - Sprint 5 · v0.5.1 + Sprint 5.5 · v0.5.5 ); diff --git a/docs/SPRINTS.md b/docs/SPRINTS.md index c3f351f..cfe9a9a 100644 --- a/docs/SPRINTS.md +++ b/docs/SPRINTS.md @@ -229,6 +229,68 @@ --- +## Sprint 5.5 — «Просмотр текущей страницы» (внеочередной) + +**Цель:** Добавить интерактивный раздел брендбука, который собирает главную страницу из уже задокументированных блоков. +Показывает живой превью того, как выглядит сайт на основе данных брендбука. + +### Концепция UX + +**Маршрут:** `/pages/preview` +**Сайдбар:** добавить в раздел «Страницы» с пометкой (если ещё нет блоков — показывает заглушку с кнопкой) + +**Два состояния страницы:** + +1. **Пустое состояние** (первый вход, или если превью не создавалось): + - Заголовок «Просмотр текущей страницы» + - Описание: «Здесь будет собрана главная страница из задокументированных блоков» + - Активная кнопка «Создать» (`.bb-btn bb-btn-primary`) + - После нажатия → переход в «созданное» состояние + +2. **Созданное состояние** (после нажатия «Создать»): + - Превью главной страницы из всех доступных блоков в порядке сверху вниз, как на perm.oclinica.ru/lor + - Кнопка «Пересобрать» в шапке (сбрасывает до исходного состояния) + - Сборка только из блоков, у которых есть готовый компонент (не mock-заглушки) + - Блоки рендерятся как реальные React-компоненты внутри `
` + +**Порядок блоков в превью** (по perm.oclinica.ru/lor, только готовые): +1. Hero-баннер (`/blocks/hero` → компонент HeroBlock) +2. Блок врачей (`/blocks/doctors` → компонент DoctorsBlock) +3. Блок отзывов (`/blocks/reviews` → когда будет готов) +4. Форма записи (`/blocks/contact-forms` → когда будет готова) +5. Блок новостей (`/blocks/news` → когда будет готов) +6. Footer (`/blocks/contact` → когда будет готов) + +**Техническая реализация (FE only, без бэкенда):** +- Состояние сохраняется в `localStorage` (`preview-created: true/false`) +- Каждый задокументированный блок выносится в переиспользуемый React-компонент +- Страница `/pages/preview` импортирует компоненты и рендерит их в нужном порядке +- Блоки, которых ещё нет → показывается placeholder с текстом «Блок в разработке» + +### Задачи + +- [ ] FE: Страница `/pages/preview` — пустое состояние с кнопкой «Создать» +- [ ] FE: Логика `localStorage` — сохранение/сброс состояния превью +- [ ] FE: Рефактор `/blocks/hero/page.tsx` — вынести баннер в компонент `HeroBlock` (переиспользуемый) +- [ ] FE: Рефактор `/blocks/doctors/page.tsx` — вынести в компонент `DoctorsBlock` +- [ ] FE: Placeholder-компонент для блоков, которые ещё не готовы (серая рамка с названием блока) +- [ ] FE: Сборка превью: рендер всех доступных компонентов в порядке реального сайта +- [ ] FE: Sidebar — добавить «Просмотр страницы» в раздел «Страницы» +- [ ] FE: Кнопка «Пересобрать» в созданном состоянии +- [ ] Docs: Добавить `/pages/preview` v1.0 в LLM_CONTEXT.md + +### Зависимости +- Зависит от: Sprint 5 (блоки hero и doctors уже готовы — ✅) +- По мере добавления новых блоков в Sprint 5 — они автоматически подключаются к превью + +### Ожидаемый результат +- Раздел «Просмотр текущей страницы» работает в браузере +- Кнопка «Создать» собирает главную страницу из задокументированных блоков +- Отсутствующие блоки отображаются как плейсхолдеры +- Кнопка «Пересобрать» позволяет сбросить и пересоздать + +--- + ## Sprint 6 — Страницы (сборки из блоков) **Цель:** Задокументировать полные страницы как сборки уже готовых блоков. @@ -355,6 +417,7 @@ | 3 | Кнопки и форм-контролы | FE | CSS реального сайта | | 4 | Карточки, бейджи, алерты | FE | CSS реального сайта | | 5 | ВСЕ блоки сайта | FE | Все блоки /lor, mock-данные | +| 5.5 | Просмотр текущей страницы | FE | Кнопка «Создать», сборка из блоков, localStorage | | 6 | Все страницы (сборки) | FE | Сборки из блоков, mock-данные | | 7 | Авторизация (viewer / editor) | BE + FE | JWT, роли, login-страница, шапка с именем | | 8 | Реальные данные | BE + FE | NestJS прокси → oclinica.ru, кэш 15 мин |