From 6c5b571884bbaeef101ccf94f425c29e368730b5 Mon Sep 17 00:00:00 2001 From: AR 15 M4 Date: Sun, 22 Mar 2026 23:09:22 +0500 Subject: [PATCH] =?UTF-8?q?feat(sprint4):=20add=20cards=20page=20=E2=80=94?= =?UTF-8?q?=20DoctorCard,=20NewsCard,=20ReviewCard,=20PriceCard,=20Service?= =?UTF-8?q?Card,=20badges/tags/alerts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /components/cards — new page with 5 card types + badges/tags/alerts + LLM block v1.0 - DoctorCard: photo 110×160px placeholder, name, specialty, experience, outline button - NewsCard: hover bg #eef4d1 + box-shadow (matches real site CSS), CSS class .bb-news-card - ReviewCard: star rating 1-5 (SVG), text 4-line clamp, bg #eef4d1 - PriceCard: service + price + description + button, highlighted variant (blue border/bg) - ServiceCard: emoji icon 48×48px, title, description, link - Badges: 6 color variants (primary/success/warning/danger/neutral/outline) - Tags: default/active state filters - Alerts: 4 types (info/success/warning/error) with icons - globals.css: .bb-news-card:hover, .bb-service-card:hover CSS rules (Sprint 4) Co-Authored-By: Claude Sonnet 4.6 --- apps/web/app/components/cards/page.tsx | 629 +++++++++++++++++++++++++ apps/web/app/globals.css | 7 + 2 files changed, 636 insertions(+) create mode 100644 apps/web/app/components/cards/page.tsx diff --git a/apps/web/app/components/cards/page.tsx b/apps/web/app/components/cards/page.tsx new file mode 100644 index 0000000..26ca497 --- /dev/null +++ b/apps/web/app/components/cards/page.tsx @@ -0,0 +1,629 @@ +import type { Metadata } from "next"; +import { CodeCopy } from "@/components/ui/CodeCopy"; +import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock"; + +export const metadata: Metadata = { + title: "Карточки. Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой", +}; + +function Section({ + id, title, subtitle, children, +}: { + id?: string; title: string; subtitle?: string; children: React.ReactNode; +}) { + return ( +
+
+

{title}

+ {subtitle &&

{subtitle}

} +
+ {children} +
+ ); +} + +/* ─── Карточка врача ────────────────────────────────────────────────── */ +function DoctorCard({ + name, specialty, experience, photo, +}: { + name: string; specialty: string; experience: string; photo?: string; +}) { + return ( +
+ {/* Фото */} +
+ {photo ? ( + {name} + ) : ( +
+ + + + + фото +
+ )} +
+ + {/* Информация */} +
+
+

+ {name} +

+

{specialty}

+

{experience}

+
+ +
+
+ ); +} + +/* ─── Карточка новости ──────────────────────────────────────────────── */ +function NewsCard({ + date, title, snippet, category, +}: { + date: string; title: string; snippet: string; category?: string; +}) { + return ( +
+ {/* Превью */} +
+ {category && ( + + {category} + + )} +
+ + {/* Контент */} +
+

{date}

+

+ {title} +

+

+ {snippet} +

+ + Читать далее → + +
+
+ ); +} + +/* ─── Карточка отзыва ───────────────────────────────────────────────── */ +function ReviewCard({ + author, date, text, rating, doctor, +}: { + author: string; date: string; text: string; rating: number; doctor?: string; +}) { + return ( +
+ {/* Звёзды */} +
+ {Array.from({ length: 5 }, (_, i) => ( + + + + ))} + {rating}/5 +
+ + {/* Текст */} +

+ {text} +

+ + {/* Автор */} +
+
+

{author}

+ {doctor &&

Врач: {doctor}

} +
+

{date}

+
+
+ ); +} + +/* ─── Карточка цены ─────────────────────────────────────────────────── */ +function PriceCard({ + service, price, description, highlighted, +}: { + service: string; price: string; description?: string; highlighted?: boolean; +}) { + return ( +
+
+

{service}

+

+ {price} +

+
+ {description && ( +

{description}

+ )} + +
+ ); +} + +/* ─── Карточка услуги ───────────────────────────────────────────────── */ +function ServiceCard({ + title, description, icon, +}: { + title: string; description: string; icon: string; +}) { + return ( +
+
+ {icon} +
+
+

{title}

+

{description}

+
+ + Подробнее → + +
+ ); +} + +/* ─── LLM текст ─────────────────────────────────────────────────────── */ +const LLM_CARDS_TEXT = `КАРТОЧКИ — LLM-спецификация +Версия: v1.0 · /components/cards + +КАРТОЧКА ВРАЧА (DoctorCard) +Структура: фото (110×160px) + имя + специализация + опыт + кнопка «Записаться» +Фото: 110px × 160px, object-fit: cover, border-radius: 8px, фон-placeholder: #dff0fa +Кнопка: .bb-btn-outline .bb-btn-sm +Источник: .doctor на perm.oclinica.ru + +КАРТОЧКА НОВОСТИ (NewsCard) +Структура: превью (h=144px) + дата + заголовок + анонс (4 строки) + «Читать далее →» +Hover: background #eef4d1 + box-shadow 0 0 16px #9e9e9a +Источник: #block-views-last-news-block-1 .views-column на сайте (200×200px) +Без hover: background #fff, border 1px + +КАРТОЧКА ОТЗЫВА (ReviewCard) +Структура: рейтинг (звёзды 1–5) + текст (4 строки, overflow hidden) + автор + дата + врач +Фон: #eef4d1 (светло-жёлтый) — из реального CSS сайта +Звёзды: SVG polygon, filled #f59e0b + +КАРТОЧКА ЦЕНЫ (PriceCard) +Структура: услуга + цена (bold) + описание + кнопка +highlighted-вариант: border var(--brand-053m), bg #f0f9ff, цена тоже --brand-053m + +КАРТОЧКА УСЛУГИ (ServiceCard) +Структура: иконка (emoji, 48×48px, bg #dff0fa) + заголовок + описание + «Подробнее →» +Hover: box-shadow (0.5rem smth) + +БЕЙДЖИ (Badge) +Варианты: primary (#0089c3), success (#059669), warning (#d97706), danger (#dc2626), neutral (#6b7280) +Размер: text-xs, px-2.5 py-0.5, border-radius: full +CSS: inline-flex, font-weight: 600 + +ТЕГИ (Tag) +Варианты: default (border, text), filled (bg), removable (с кнопкой ×) +Цвет: --brand-053m или нейтральный (#e5e7eb bg) +Размер: text-xs, px-2 py-1, border-radius: 4px + +АЛЕРТЫ (Alert) +Варианты: info (#dff0fa фон, #075985 текст), success (#d1fae5, #065f46), warning (#fef3c7, #92400e), error (#fee2e2, #991b1b) +Структура: иконка (16×16px) + заголовок + описание +Без кнопки закрыть в базовом варианте + +ПРАВИЛА +✓ DoctorCard: всегда показывать фото-placeholder если нет фото (не ломать layout) +✓ ReviewCard: обрезать текст после 4 строк (WebkitLineClamp) +✓ PriceCard: highlighted = акционная или рекомендуемая позиция +✓ NewsCard: hover-эффект обязателен (#eef4d1 + box-shadow) +✓ Badge: не более 2–3 бейджей рядом +✓ Alert: одновременно не более 1 alert одного типа на экране +✕ Не смешивать типы карточек в одной сетке без заголовка секции +✕ Не использовать ReviewCard без рейтинга`.trim(); + +/* ─── Данные примеров ───────────────────────────────────────────────── */ +const DOCTORS = [ + { + name: "Иванова Анна Сергеевна", + specialty: "Оториноларинголог (ЛОР), высшая категория", + experience: "Стаж 18 лет", + }, + { + name: "Петров Дмитрий Александрович", + specialty: "Сурдолог, аудиолог", + experience: "Стаж 12 лет", + }, + { + name: "Соколова Мария Ивановна", + specialty: "Детский ЛОР, ринолог", + experience: "Стаж 9 лет", + }, +]; + +const REVIEWS = [ + { + author: "Елена К.", + date: "15 марта 2026", + rating: 5, + doctor: "Иванова А.С.", + text: "Очень довольна визитом. Доктор внимательно выслушала все жалобы, провела осмотр и объяснила причину заболевания. Назначила лечение, которое помогло уже через 3 дня. Рекомендую!", + }, + { + author: "Михаил Р.", + date: "10 марта 2026", + rating: 4, + doctor: "Петров Д.А.", + text: "Хороший специалист, всё объяснил понятно. Подождал немного дольше, чем ожидал, но качество приёма на высоте. Буду обращаться снова.", + }, +]; + +const PRICES = [ + { service: "Первичный приём ЛОР-врача", price: "1 500 ₽", description: "Включает осмотр и консультацию" }, + { service: "Повторный приём", price: "1 000 ₽", description: "До 14 дней после первичного" }, + { service: "Промывание миндалин", price: "800 ₽", highlighted: true, description: "Аппаратное — аккустический вакуум" }, + { service: "Аудиометрия", price: "1 200 ₽", description: "Исследование слуха" }, +]; + +const SERVICES = [ + { title: "Лечение ангины и тонзиллита", description: "Консервативное и хирургическое лечение заболеваний миндалин", icon: "🦷" }, + { title: "Аудиология и сурдология", description: "Диагностика нарушений слуха, подбор слуховых аппаратов", icon: "👂" }, + { title: "Детский ЛОР", description: "Специализация на лечении ЛОР-заболеваний у детей от 0 лет", icon: "👶" }, + { title: "Ринология", description: "Лечение заболеваний носа и придаточных пазух", icon: "👃" }, +]; + +/* ─── Коды примеров ─────────────────────────────────────────────────── */ +const codeDoctorCard = ` +
+ ФИО +
+

Иванова Анна Сергеевна

+

Оториноларинголог, высшая категория

+

Стаж 18 лет

+ +
+
+ +/* CSS с сайта oclinica.ru */ +.doctor .image { float:left; margin-right:20px; width:110px; height:160px; } +.doctor .item { float:left; width:170px; } +.doctor h3 { margin-top:0; height:32px; }`; + +const codeNewsCard = ` +
+
...
+
+ +

Заголовок новости

+

Краткий анонс...

+ Читать далее → +
+
+ +/* CSS с сайта */ +#block-views-last-news-block-1 .views-column { + background: #fff; width: 200px; height: 200px; + margin: 15px 8px; padding: 15px; +} +#block-views-last-news-block-1 .views-column:hover { + background: #eef4d1; + box-shadow: 0px 0px 16px 0px #9e9e9a; +}`; + +const codeReviewCard = ` +
+
★★★★★
+

Текст отзыва (4 строки, overflow: hidden)...

+
+ Елена К. + +
+
+ +/* Стиль брендбука (фон из CSS сайта) */ +.review-card { background: #eef4d1; border-radius: 12px; padding: 20px; }`; + +const codePriceCard = ` +
+
+ Первичный приём ЛОР-врача + 1 500 ₽ +
+

Включает осмотр и консультацию

+ +
`; + +const codeBadges = ` +ЛОР +Принимает +Ожидает +Не принимает +Высшая категория + + + + + + + +
+ +
+ Запись открыта +

Вы можете записаться онлайн или по телефону.

+
+
`; + +export default function CardsPage() { + return ( +
+ + {/* Заголовок */} +
+

+ Компоненты → 3.3 +

+

+ Карточки +

+

+ Карточки врача, новости, отзыва, цены и услуги — основные блоки контента сайта. + Бейджи, теги и алерты — вспомогательные элементы. +

+
+ + {/* 1. Карточки врачей */} +
+
+ {DOCTORS.map(d => )} +
+
+ + {/* 2. Карточки новостей */} +
+
+ + + +
+

+ * Наведите на карточку чтобы увидеть hover-эффект +

+
+ + {/* 3. Карточки отзывов */} +
+
+ {REVIEWS.map(r => )} +
+
+ + {/* 4. Карточки цен */} +
+
+ {PRICES.map(p => )} +
+
+ + {/* 5. Карточки услуг */} +
+
+ {SERVICES.map(s => )} +
+
+ + {/* 6. Бейджи и теги */} +
+ {/* Бейджи */} +
+

Бейджи (статус)

+
+ {[ + { label: "Основной", bg: "var(--brand-053m)", color: "#fff" }, + { label: "Принимает", bg: "#059669", color: "#fff" }, + { label: "Высшая категория", bg: "#d97706", color: "#fff" }, + { label: "Не принимает", bg: "#dc2626", color: "#fff" }, + { label: "Нейтральный", bg: "#6b7280", color: "#fff" }, + { label: "Новинка", bg: "#dff0fa", color: "var(--brand-053m)" }, + ].map(b => ( + + {b.label} + + ))} +
+
+ + {/* Теги */} +
+

Теги (категории)

+
+ {["Ухо", "Горло", "Нос", "Аудиология", "Детский ЛОР", "Хирургия"].map((tag, i) => ( + + ))} +
+
+ + {/* Алерты */} +
+

Алерты

+
+ {[ + { type: "info", bg: "#dff0fa", color: "#075985", icon: "ℹ", title: "Информация", text: "Запись открыта. Вы можете записаться онлайн или по телефону." }, + { type: "success", bg: "#d1fae5", color: "#065f46", icon: "✓", title: "Успешно", text: "Ваша запись подтверждена. Ждём вас 20 марта в 10:00." }, + { type: "warning", bg: "#fef3c7", color: "#92400e", icon: "⚠", title: "Внимание", text: "Не забудьте взять паспорт и полис ОМС на приём." }, + { type: "error", bg: "#fee2e2", color: "#991b1b", icon: "✕", title: "Ошибка", text: "К сожалению, это время уже занято. Выберите другое." }, + ].map(a => ( +
+ {a.icon} +
+

{a.title}

+

{a.text}

+
+
+ ))} +
+
+
+ + {/* 7. Код */} +
+
+ + + + + +
+
+ + {/* LLM-блок */} + + + + + + + + + +
+ ); +} diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 7d46e0d..bc26fe4 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -162,6 +162,13 @@ body { flex-shrink: 0; } +/* ─── Карточки (Sprint 4) ────────────────────────────────────── */ +.bb-news-card:hover { + background: #eef4d1 !important; + box-shadow: 0 0 16px 0 #9e9e9a; +} +.bb-service-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } + /* ─── Тумблер (Sprint 3) ──────────────────────────────────────── */ .bb-toggle-track { display: inline-flex;