diff --git a/SPRINTS.md b/SPRINTS.md index 2649792..d3c7607 100644 --- a/SPRINTS.md +++ b/SPRINTS.md @@ -9,6 +9,13 @@ - По завершении спринта — короткое резюме в блоке **Итоги** - Нерешённое переносится в следующий спринт +## Правила разработки + +- **Документация = часть прототипа.** При любом изменении экрана в `src/screens/*.jsx` — проверить и обновить соответствующую запись в `src/docs.js` (`goal` / `tasks` / `rationale` / `variants`). Эти описания показываются в плашке над телефоном и на экране «Документация», поэтому устаревшие формулировки мешают коллегам на ревью. +- Новый экран → добавить запись в `SCREEN_DOCS`, выбрать категорию из `ORDER` (в `getAllDocs`), для compound-маршрутов (`something:id`) прописать кейс в `resolveRouteForDoc`. +- Мелкие CSS-правки (цвета, отступы, иконки) документировать не нужно — в `docs.js` описывается intent, а не пиксели. +- Варианты экрана с ctx (home cards/list/feed) — отдельная запись на каждый вариант (`home:cards`, `home:list`, `home:feed`). + --- ## Спринт 1 · 19 апр 2026 @@ -92,5 +99,20 @@ _заполнить в конце спринта_ - [x] Добавить оба экрана в SCREEN_OPTIONS Tweaks - [x] Динамический рендер: при переключении палитры значения hex обновляются автоматически +--- + +## Спринт 5 · 20 апр 2026 + +**Цель:** документация прототипа внутри самого прототипа — чтобы на ревью с коллегами можно было сразу увидеть цель и design-решения по любому экрану. + +### План +- [x] Общий слой данных `src/docs.js` — словарь описаний по screen-id: title, category, goal, tasks[], rationale[], variants +- [x] Helper `getScreenDoc(screenId, ctx)` — резолвит варианты home (cards/list/feed) и compound-экраны +- [x] Toggle «Описания» в Tweaks (вкл/выкл) +- [x] Плашка-описание над телефоном в режиме single (только когда тоггл ON) — category + title + 1 строка goal +- [x] Тап по плашке → модал-оверлей поверх сцены с полным описанием (задачи, design-решения, варианты, CTA «Закрыть») +- [x] Отдельный экран `docs` (Вариант 4) — список всех экранов по категориям с collapsible-описаниями и кнопкой «Открыть экран» +- [x] Наполнить описаниями все ~30 экранов прототипа + ### Итоги _заполнить в конце спринта_ diff --git a/src/App.jsx b/src/App.jsx index 65beb2d..6962c62 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { IOSDevice } from './frames/IOSDevice.jsx'; import { AndroidDevice } from './frames/AndroidDevice.jsx'; import { PhoneApp } from './PhoneApp.jsx'; +import { getScreenDoc } from './docs.js'; const TWEAKS_DEFAULT = { homeVariant: 'cards', @@ -14,6 +15,7 @@ const TWEAKS_DEFAULT = { device: 'ios', scale: 'auto', showIntro: true, + docsEnabled: false, }; const SCALE_OPTIONS = [ @@ -85,6 +87,7 @@ const SCREEN_OPTIONS = [ { id: 'prices', lb: 'Цены' }, { id: 'dev-colors', lb: 'DEV · Палитра' }, { id: 'dev-examples', lb: 'DEV · Примеры' }, + { id: 'docs', lb: 'Документация' }, ]; function applyTheme(tw) { @@ -110,8 +113,8 @@ function applyTheme(tw) { document.body.style.fontFamily = f.base; } -function Phone({ device = 'ios', screen, ctx, label, sublabel }) { - const content = ; +function Phone({ device = 'ios', screen, ctx, label, sublabel, onCurrentChange }) { + const content = ; const frame = device === 'android' ? {content} : {content}; @@ -151,6 +154,10 @@ function TweaksPanel({ tw, setTw, onClose }) { )} {group('Устройство', opts([{id:'ios',lb:'iOS'},{id:'android',lb:'Android'}], 'device'))} + {group('Описания', [ + , + , + ])} {tw.layout === 'single' && group('Масштаб', opts(SCALE_OPTIONS, 'scale'))} {group('Компоновка', opts([ { id:'single', lb:'1 телефон' }, @@ -176,13 +183,111 @@ function TweaksPanel({ tw, setTw, onClose }) { ); } +function DocPlashka({ doc, onOpen }) { + if (!doc) return null; + return ( + + ); +} + +function DocModal({ doc, onClose }) { + if (!doc) return null; + return ( +
+
e.stopPropagation()} style={{ + background: '#fff', borderRadius: 20, padding: 22, + maxWidth: 480, width: '100%', maxHeight: '85vh', overflowY: 'auto', + boxShadow: '0 30px 80px rgba(15,30,40,0.35)', + position: 'relative', + }}> +
+
i
+
+
{doc.category}
+
{doc.title}
+
+ +
+ +
+ {doc.goal} +
+ +
Задачи пользователя
+
    + {doc.tasks.map((t, i) => ( +
  • + + {t} +
  • + ))} +
+ +
Design-решения
+
    + {doc.rationale.map((t, i) => ( +
  • + + {t} +
  • + ))} +
+ + {doc.variants && ( +
+ Варианты. {doc.variants} +
+ )} +
+
+ ); +} + function FitWrap({ children, w = 402, h = 874, userScale = 'auto' }) { const outerRef = useRef(null); const [autoScale, setAutoScale] = useState(1); useEffect(() => { const outer = outerRef.current; if (!outer) return; - const stage = outer.parentElement; + const stage = outer.closest('.stage') || outer.parentElement; if (!stage) return; const measure = () => { const padding = 48; @@ -212,11 +317,17 @@ export default function App() { const [tw, setTw] = useState(TWEAKS_DEFAULT); const [panelOpen, setPanelOpen] = useState(true); const [introVisible, setIntroVisible] = useState(tw.showIntro !== false); + const [docModal, setDocModal] = useState(null); + const [innerScreen, setInnerScreen] = useState(tw.screen); useEffect(() => { applyTheme(tw); }, [tw.accent, tw.font]); + // Sync inner screen when Tweaks changes the initial screen + useEffect(() => { setInnerScreen(tw.screen); }, [tw.screen]); + const palette = ACCENT_OPTIONS.find(a => a.id === tw.accent) || ACCENT_OPTIONS[0]; const ctx = { homeVariant: tw.homeVariant, docVariant: tw.docVariant, density: tw.density, palette }; + const currentDoc = tw.docsEnabled ? getScreenDoc(innerScreen, ctx) : null; const content = useMemo(() => { if (tw.layout === 'home3') { @@ -262,11 +373,14 @@ export default function App() { ); } return ( - - - +
+ {currentDoc && setDocModal(currentDoc)} />} + + + +
); - }, [tw, ctx.homeVariant, ctx.docVariant, ctx.density]); + }, [tw, ctx.homeVariant, ctx.docVariant, ctx.density, currentDoc]); const stageClass = tw.layout === 'single' ? 'stage' : 'stage grid-mode'; @@ -287,6 +401,8 @@ export default function App() { {!panelOpen && ( )} + + {docModal && setDocModal(null)} />} ); } diff --git a/src/PhoneApp.jsx b/src/PhoneApp.jsx index d69e392..53a36e4 100644 --- a/src/PhoneApp.jsx +++ b/src/PhoneApp.jsx @@ -17,6 +17,7 @@ import { ChatsListScreen, ChatConversationScreen } from './screens/screens-chats import { ArticlesScreen, ArticleDetailScreen } from './screens/screens-articles.jsx'; import { HomeV2Screen, SearchScreen, ContactsScreen, PricesScreen } from './screens/screens-v2.jsx'; import { DevColorsScreen, DevExamplesScreen } from './screens/screens-dev.jsx'; +import { DocsScreen } from './screens/screens-docs.jsx'; function renderScreen(screenId, nav, ctx) { const parts = screenId.split(':'); @@ -54,17 +55,24 @@ function renderScreen(screenId, nav, ctx) { case 'prices': return ; case 'dev-colors': return ; case 'dev-examples': return ; + case 'docs': return ; default: return
Экран не найден: {screenId}
; } } const TAB_IDS = ['home', 'appts', 'doctors', 'chat', 'profile']; -export function PhoneApp({ initialScreen, ctx }) { +export function PhoneApp({ initialScreen, ctx, onCurrentChange }) { const [stack, setStack] = useState([initialScreen]); useEffect(() => { setStack([initialScreen]); }, [initialScreen]); + const current = stack[stack.length - 1]; + + useEffect(() => { + if (onCurrentChange) onCurrentChange(current); + }, [current, onCurrentChange]); + const nav = useMemo(() => ({ push: (id) => setStack(s => [...s, id]), pop: () => setStack(s => s.length > 1 ? s.slice(0, -1) : s), @@ -72,7 +80,6 @@ export function PhoneApp({ initialScreen, ctx }) { reset:() => setStack(['home']), }), []); - const current = stack[stack.length - 1]; const rootId = current.split(':')[0]; const hasSubId = current.includes(':'); const tabId = hasSubId ? null : (rootId === 'home-v2' ? 'home' : (TAB_IDS.includes(rootId) ? rootId : null)); diff --git a/src/docs.js b/src/docs.js new file mode 100644 index 0000000..d227735 --- /dev/null +++ b/src/docs.js @@ -0,0 +1,630 @@ +// Описания экранов прототипа — для коллаборативного ревью. +// Читается двумя UI-слоями: +// 1. Плашка над телефоном в Tweaks-режиме «Описания» +// 2. Экран `docs` (список всех экранов с разворачивающимися описаниями) + +export const SCREEN_DOCS = { + 'home:cards': { + title: 'Главная 1 · Карточки', + category: 'Главная', + goal: 'Быстрый старт: записаться или увидеть ближайший приём. Приветствие + один доминирующий CTA + фасетная сетка входов.', + tasks: [ + 'Нажать «Записаться» и попасть в 4-шаговый флоу', + 'Увидеть ближайший приём и открыть его детали', + 'Выбрать специализацию (browse) или быстрое действие (телемед, тест слуха)', + 'Прочитать статью врача', + ], + rationale: [ + 'Бежевый градиент шапки — тёплый контакт вместо стерильного белого', + 'Единственная доминирующая CTA тёмный teal + тень — видна сразу', + 'Сетка 3×2 специализаций — browse-режим для пользователя, не знающего врача', + 'Статьи горизонтально — низкий приоритет, не давят вертикально', + ], + variants: 'В Tweaks «Главный экран»: Карточки / Лента / Таймлайн — три подхода к приоритизации контента', + }, + 'home:list': { + title: 'Главная 1 · Лента', + category: 'Главная', + goal: 'Утилитарная навигация: одна компактная карточка со всеми разделами как в iOS Settings. Для частых пользователей, которым не нужно browse.', + tasks: [ + 'Быстро открыть Медкарту / Анализы / Чат с одного тапа', + 'Увидеть ближайший приём (компактно сверху)', + 'Попасть на запись через первый выделенный пункт', + ], + rationale: [ + 'Одна плотная карточка-список — минимум визуального шума', + 'CTA «Записаться» выделена фоном primary-50 среди остальных', + 'Бейджи справа для счётчиков (непрочитанные в чате)', + 'Без градиентов и крупных CTA — подчёркнуто утилитарно', + ], + }, + 'home:feed': { + title: 'Главная 1 · Таймлайн', + category: 'Главная', + goal: 'Пациент-центричная лента: восстановление, лекарства, ближайший приём — всё, что влияет на самочувствие сегодня. Для послеоперационных.', + tasks: [ + 'Увидеть прогресс восстановления с процентом и «что сегодня»', + 'Отметить приём лекарства (one-tap)', + 'Перейти к ближайшему приёму', + 'Прочитать релевантные статьи', + ], + rationale: [ + 'Первый блок — прогресс операции, а не CTA: для послеоперационных это важнее', + 'Вопрос «Как Ваше самочувствие?» вместо нейтрального приветствия — эмпатия', + 'Лекарство с кнопкой «Принял» — one-tap action', + 'Accent-CTA (красная) для записи — выделяется на фоне тёплых карточек', + ], + }, + 'home-v2': { + title: 'Главная 2', + category: 'Главная', + goal: 'Search-first layout: универсальная поисковая строка вверху — врач, симптом, услуга, дата. Для пользователя, который точно знает, что ищет.', + tasks: [ + 'Ввести симптом или ФИО врача → получить релевантный результат', + 'Попасть в Контакты или Цены через тайлы', + 'Увидеть статистику клиники (формирование доверия)', + ], + rationale: [ + 'Поисковая строка вверху + AI-бейдж — сигнал «умный поиск»', + 'Тайлы Контакты/Цены/Анализы/Восстановление — частые разделы в один тап', + 'Градиентная карточка статистики — эмоциональный контакт с клиникой', + 'Бордер primary-300 на stats-карточке — демонстрация «живого» цвета в палитре Бриз', + ], + }, + 'doctors': { + title: 'Врачи · список', + category: 'Врачи и запись', + goal: 'Список всех врачей клиники с поиском, фильтрами и тремя вариантами представления карточек.', + tasks: [ + 'Найти врача по ФИО или специализации', + 'Отфильтровать по «свободно сегодня», «кандидаты наук», «детские»', + 'Открыть карточку врача → записаться', + ], + rationale: [ + 'Пиллы-фильтры горизонтально скроллятся — помещаются все на узких экранах', + 'Три варианта карточек (rich/list/photo) — под разные привычки сканирования', + 'Пиллы вынесены из карточки — общий фильтр для всего списка', + ], + variants: 'В Tweaks «Карточки врачей»: Карточки+ / Список / Плитка', + }, + 'doctor': { + title: 'Карточка врача', + category: 'Врачи и запись', + goal: 'Подробная карточка врача: регалии, рейтинг, цены, расписание, отзывы. Всё для решения «записаться — не записаться».', + tasks: [ + 'Увидеть аватар, имя, специализацию, рейтинг, стоимость', + 'Прочитать образование и специализацию', + 'Посмотреть расписание (таб)', + 'Прочитать отзывы (таб)', + 'Нажать «Записаться» внизу → сразу на выбор времени', + ], + rationale: [ + 'Три равных stat-карточки: опыт, рейтинг, цена — ключевая инфа сразу', + 'Сегментед-контрол для вкладок — компактно, iOS-паттерн', + 'Фиксированная CTA внизу с backdrop-blur — не теряется при прокрутке', + 'Чип «К.м.н.» warm-цветом — выделяет научную степень', + ], + }, + 'booking-specs': { + title: 'Запись: специализация', + category: 'Флоу записи', + goal: 'Шаг 1 из 4: выбор направления. Для пользователей, которые не знают конкретного врача.', + tasks: [ + 'Переключиться между «Взрослому / Ребёнку / Онлайн»', + 'Выбрать специализацию из сетки 2×2', + 'Перейти к списку врачей специализации', + ], + rationale: [ + 'Сегментед 3 варианта сразу — быстрый split аудитории', + 'Крупные карточки специализаций с иконкой, названием, кол-вом врачей, минимальной ценой', + '«Шаг 1 из 4» в шапке — прогресс флоу виден', + ], + }, + 'booking-doctor': { + title: 'Запись: выбор врача', + category: 'Флоу записи', + goal: 'Шаг 2 из 4: список врачей выбранной специализации с поиском и фильтрами.', + tasks: [ + 'Найти конкретного врача по ФИО', + 'Отфильтровать по «свободно сегодня», «детские»', + 'Выбрать врача → перейти к выбору времени', + ], + rationale: [ + 'Та же логика, что и в tab «Врачи», но filter по специализации уже применён', + 'Rich-карточка с ближайшим временем приёма — дополнительный фильтр «когда»', + ], + }, + 'booking-time': { + title: 'Запись: дата и время', + category: 'Флоу записи', + goal: 'Шаг 3 из 4: выбор даты и времени. Ключевой шаг флоу — тут пользователь принимает финальное решение.', + tasks: [ + 'Увидеть выбранного врача (повтор сверху)', + 'Выбрать дату из горизонтального скролла (7 дней)', + 'Выбрать время из трёх групп (Утро/День/Вечер)', + 'Нажать «Выбрать · [дата], [время]» → подтверждение', + ], + rationale: [ + 'Дата как горизонтальные тайлы 54px — mobile-friendly, помещаются 5-6 на экране', + 'Слоты в сетке 4 колонки — легко сканировать', + 'Занятые слоты disabled с зачёркиванием — чтобы пользователь не пробовал', + 'CTA закреплена снизу с текущим выбором — пользователь всегда знает, что выберет', + ], + }, + 'booking-confirm': { + title: 'Запись: подтверждение', + category: 'Флоу записи', + goal: 'Шаг 4 из 4: финальный обзор перед подтверждением. Выбор формата (очно/онлайн) и комментарий для врача.', + tasks: [ + 'Проверить врача, дату, время, адрес', + 'Выбрать очно или онлайн', + 'Написать комментарий для врача (симптомы)', + 'Увидеть стоимость', + 'Подтвердить запись', + ], + rationale: [ + 'Вся инфа в одной карточке-таблице — одним взглядом', + 'Формат приёма — 2 крупных варианта, выбор чёткий', + 'Комментарий опциональный, но есть — врач лучше готовится', + 'Warm-плашка про отмену за 3 часа — ожидание поставлено сразу', + ], + }, + 'booking-success': { + title: 'Запись: успех', + category: 'Флоу записи', + goal: 'Визуальное подтверждение успешной записи с ключевой информацией для встречи.', + tasks: [ + 'Убедиться, что запись состоялась', + 'Запомнить адрес и кабинет', + 'Перейти к «Моим приёмам» или на главную', + ], + rationale: [ + 'Полноэкранный modal (таббар скрыт) — фокус на событии', + 'Большая primary-галочка с ореолом — эмоциональный момент', + 'Адрес выделен отдельно — пользователь часто возвращается смотреть', + 'Два выхода: appts (для чекинга) и главная (для продолжения)', + ], + }, + 'appts': { + title: 'Мои приёмы', + category: 'Приёмы и результаты', + goal: 'Предстоящие и прошедшие приёмы. Основная точка контроля для активных пациентов.', + tasks: [ + 'Переключиться между предстоящими и прошедшими', + 'Открыть детали приёма', + 'Записаться на новый приём (CTA внизу)', + ], + rationale: [ + 'Сегментед с счётчиком предстоящих — важно знать, сколько запланировано', + 'Разный фон карточек: предстоящие — градиент primary-100, прошедшие — белый', + 'Чип «Заключение» на прошедших приёмах с готовым документом', + ], + }, + 'appt': { + title: 'Детали приёма', + category: 'Приёмы и результаты', + goal: 'Полная карточка приёма: дата/время, врач, адрес, контакты, заключение (для прошедших).', + tasks: [ + 'Увидеть дату, время, тип приёма', + 'Открыть карточку врача', + 'Посмотреть адрес на карте', + 'Позвонить в клинику', + 'Отменить или перенести (для предстоящих)', + 'Открыть заключение PDF (для прошедших)', + ], + rationale: [ + 'Крупное время 42px monospace-narrow — главное, что пациент ищет', + 'Адрес отдельной секцией с кнопкой карты — частый re-check', + 'Кнопка «Отменить» приглушённым danger — чтобы случайно не нажать', + 'Перенос primary — предполагаемое действие', + ], + }, + 'results': { + title: 'Анализы и обследования', + category: 'Приёмы и результаты', + goal: 'Список всех анализов и обследований. Часть медкарты, но с фокусом на конкретные документы.', + tasks: [ + 'Увидеть все результаты с датой и врачом', + 'Отфильтровать по типу', + 'Открыть результат (аудио/эндоскопия/лаб)', + ], + rationale: [ + 'Статус «Готово» / «В работе» — чип цветом справа', + 'Разные иконки для типов результата (audio/image/lab) — быстрая семантика', + 'Pending-результаты приглушены opacity: .7 — нельзя открыть', + ], + }, + 'result-audio': { + title: 'Аудиограмма', + category: 'Приёмы и результаты', + goal: 'Экран просмотра аудиограммы: график, заключение сурдолога, рекомендация.', + tasks: [ + 'Увидеть график слуха для двух ушей', + 'Прочитать заключение', + 'Получить рекомендацию по контролю', + 'Скачать PDF', + ], + rationale: [ + 'SVG-график с сеткой, легендой (правое/левое), зелёной зоной «норма» — стандарт аудиологии', + 'Круги + сплошная линия для правого уха, крестики + пунктир для левого — международная нотация', + 'Рекомендация выделена primary-50 фоном — подсказка действия', + ], + }, + 'result': { + title: 'Эндоскопия носоглотки', + category: 'Приёмы и результаты', + goal: 'Просмотр результата эндоскопического обследования: снимки, диагноз, рекомендации.', + tasks: [ + 'Увидеть кто и когда провёл исследование', + 'Просмотреть 4 снимка (сетка 2×2)', + 'Открыть любой снимок в полноэкранном режиме', + 'Прочитать диагноз и заключение', + 'Выполнить рекомендации (нумерованный список)', + 'Скачать PDF или обсудить с врачом', + ], + rationale: [ + 'CSS-мокапы эндоскопических снимков: радиальный градиент + блик — имитация медицинского изображения', + 'Диагноз отдельной карточкой с чипом warm — самое важное', + 'Рекомендации нумерованы — чёткий порядок действий', + 'Полноэкранный просмотр с точками-навигацией — удобный UX для галерей', + ], + }, + 'recovery': { + title: 'Восстановление', + category: 'Здоровье', + goal: 'Трекер восстановления после операции: прогресс по дням, лекарства, контрольные осмотры.', + tasks: [ + 'Увидеть процент восстановления', + 'Отметить приём лекарства', + 'Увидеть план восстановления по дням (таймлайн)', + 'Связаться с хирургом в чате (chat:doctor-syndaev)', + ], + rationale: [ + 'Тёмная primary-карточка вверху — «важно», вне обычной иерархии', + 'Прогресс-бар на белом фоне контрастно на тёмном', + 'Лекарства с счётчиком доз (2/4) — пользователь видит прогресс', + 'Таймлайн с линией между шагами, галочки для сделанных, активный день с кольцом', + ], + }, + 'audiotest': { + title: 'Тест слуха', + category: 'Здоровье', + goal: 'Трёхминутный тест слуха в приложении. Скрининг, а не диагноз.', + tasks: [ + 'Прочитать инструкции (наушники, тихое место, не торопиться)', + 'Пройти тест (кнопка «Слышу» при каждом тоне)', + 'Увидеть результат по каждому уху', + 'Записаться к сурдологу, если нужно', + ], + rationale: [ + 'Три стадии: intro → test → done — чёткая структура', + 'Крупная анимированная волна (pulse) — объект фокуса внимания', + 'Большая кнопка «Слышу» — one-tap действие, выбор быстрый', + 'В результате разные цвета по ушам (success/warning) — сразу видно где норма', + 'CTA «Записаться к сурдологу» — естественное продолжение', + ], + }, + 'chat': { + title: 'Чаты · список', + category: 'Коммуникации', + goal: 'Центр всех коммуникаций с клиникой: AI-помощник, врач, администратор.', + tasks: [ + 'Открыть чат с AI-помощником для быстрого вопроса', + 'Продолжить диалог с врачом', + 'Написать в регистратуру', + 'Начать новый чат с другим врачом', + ], + rationale: [ + 'AI выделен featured-карточкой с градиентом и AI-бейджем — показать как новая возможность', + 'Остальные в обычном списке с аватаром, online-точкой, бейджем непрочитанных', + 'Непрочитанные выделены fg-1 текстом и accent-бейджем — сразу видно', + 'Плашка снизу: чат с врачом ограничен 14 днями — ожидания пользователя правильные', + ], + }, + 'chat:ai': { + title: 'Чат: AI-помощник', + category: 'Коммуникации', + goal: 'Приватный диалог с AI-помощником клиники: напоминания, помощь с записью, объяснение результатов.', + tasks: [ + 'Задать вопрос о расписании или операции', + 'Получать напоминания о лекарствах', + 'Подтверждать приём препарата', + 'Использовать suggested-reply чипсы для быстрых ответов', + ], + rationale: [ + 'Сообщения от AI — gradient primary-50 → primary-100 + бордер primary-200 — субтильное отличие от человека', + 'Suggested-replies над инпутом — снижает барьер начала диалога', + 'Плейсхолдер «Спросите что-нибудь» вместо «Сообщение» — AI-контекст', + ], + }, + 'chat:doctor-syndaev': { + title: 'Чат: врач', + category: 'Коммуникации', + goal: 'Приватный чат с лечащим врачом. Продолжение консультации после приёма.', + tasks: [ + 'Рассказать о самочувствии между приёмами', + 'Уточнить режим лечения', + 'Согласовать перенос приёма', + 'Нажать кнопку видеозвонка для срочной консультации', + ], + rationale: [ + 'Статус «Онлайн · отвечает 5 мин» — ожидания пациента поставлены', + 'Кнопка video в хедере только у врача — эскалация в видеозвонок', + 'Обычные iOS-bubbles — привычный паттерн', + ], + }, + 'chat:operator': { + title: 'Чат: администратор', + category: 'Коммуникации', + goal: 'Общение с регистратурой: справки, переносы, оплата, документы.', + tasks: [ + 'Заказать медицинскую справку', + 'Перенести приём', + 'Уточнить счёт к оплате', + 'Позвонить по кнопке в хедере', + ], + rationale: [ + 'Часы работы в подписи — пользователь знает, когда ждать ответ', + 'Кнопка phone в хедере — альтернатива чату', + 'Иконка 📞 на аватаре — тип коммуникации визуально закодирован', + ], + }, + 'profile': { + title: 'Профиль', + category: 'Сервис', + goal: 'Профиль пациента с группировкой по темам: здоровье, оплата, клиника, настройки.', + tasks: [ + 'Увидеть имя, возраст, телефон', + 'Перейти к медкарте, анализам, лекарствам', + 'Проверить способы оплаты и бонусы', + 'Показать QR пациента', + ], + rationale: [ + 'Градиентная карточка профиля с аватаром и QR-чипом — личное пространство', + 'Секции-группы как в iOS Settings — знакомый паттерн', + 'Бейджи на некоторых пунктах (Серебро у бонусов) — достижения', + ], + }, + 'qr': { + title: 'QR пациента', + category: 'Сервис', + goal: 'QR-код пациента для быстрой идентификации на ресепшене. Полноэкранный modal.', + tasks: [ + 'Показать QR сотруднику', + 'Увидеть номер пациента', + ], + rationale: [ + 'Тёмный primary-gradient фон — пользователь воспринимает как важный билет', + 'Большой QR на белой карточке — максимальный контраст для сканера', + 'Таббар скрыт — фокус на QR', + 'Плашка внизу: код обновляется каждые 60 сек — сигнал безопасности', + ], + }, + 'telemed': { + title: 'Видеозвонок', + category: 'Коммуникации', + goal: 'Видеозвонок с врачом. Full-screen экран «в разговоре».', + tasks: [ + 'Вести разговор с врачом', + 'Свернуть (кнопка вниз)', + 'Завершить звонок (красная кнопка)', + 'Переключить микрофон / видео / чат', + ], + rationale: [ + 'Тёмный фон — стандартная метафора видеозвонка', + 'Self-preview окошко в углу — пользователь видит себя', + 'Красный таймер с мигающей точкой — идёт запись/звонок', + 'Крупные контрольные кнопки снизу — критичные действия большими хит-зонами', + ], + }, + 'medcard': { + title: 'Медицинская карта', + category: 'Здоровье', + goal: 'Медкарта: основное, аллергии, история диагнозов.', + tasks: [ + 'Увидеть пол, возраст, рост/вес, группу крови', + 'Проверить аллергии', + 'Просмотреть историю диагнозов с датами', + 'Добавить аллергию', + ], + rationale: [ + 'Основные данные в label/value списке — табличная структура', + 'Аллергии как красные чипы — критическая инфа', + 'История — плоская лента с датой, диагнозом, врачом', + ], + }, + 'notifications': { + title: 'Уведомления', + category: 'Сервис', + goal: 'Лента уведомлений: напоминания, готовые заключения, сообщения, акции.', + tasks: [ + 'Просмотреть последние уведомления', + 'Увидеть время каждого', + ], + rationale: [ + 'Разные tint (primary/warning/warm) по типу уведомления — семантика цветом', + 'Первая строка с временем справа — стандарт ленты', + ], + }, + 'articles': { + title: 'Статьи врачей · список', + category: 'Информация', + goal: 'Список всех статей врачей. Образовательный контент, формирует доверие к клинике.', + tasks: [ + 'Отфильтровать по тегу (Дети / Операции / Беременность / Слух)', + 'Открыть статью', + ], + rationale: [ + 'Крупные hero-карточки с emoji и лидом — стимулируют нажатие', + 'Теги как pill-фильтры сверху — выбор по интересу', + ], + }, + 'article': { + title: 'Статья врача', + category: 'Информация', + goal: 'Детальная статья с разметкой: лид, подзаголовки, списки, callout-плашки, карточка автора.', + tasks: [ + 'Прочитать статью', + 'Увидеть автора с контактом', + 'Записаться к автору', + 'Открыть связанные статьи', + ], + rationale: [ + 'Hero с заголовком занимает 72% ширины, emoji 82px справа — журнальный ритм', + 'Плавающие back/bookmark кнопки поверх hero — не мешают картинке', + 'Callout разных tone (danger/warn/info) — визуально маркированные предупреждения', + 'Author card + CTA записи к автору — конверсия из чтения в действие', + ], + }, + 'search': { + title: 'Поиск', + category: 'Информация', + goal: 'Универсальный поиск по врачам, услугам, симптомам, статьям, приёмам.', + tasks: [ + 'Ввести ФИО / симптом / услугу / дату', + 'Получить релевантные результаты по типам', + 'Использовать предложенные чипы-запросы или симптомы', + 'Перейти на выбранный результат', + ], + rationale: [ + 'Autofocus на инпуте — сразу можно печатать', + 'Пустое состояние с популярными запросами — помогает начать', + 'Группировка результатов по типам (Врачи, Услуги, Симптомы…) — лёгкий скан', + 'Date-detection (сегодня, завтра, апр) показывает приёмы — smart-поведение', + ], + }, + 'contacts': { + title: 'Контакты', + category: 'Информация', + goal: 'Адреса клиник, телефон, часы работы, маршруты.', + tasks: [ + 'Позвонить в клинику', + 'Написать в чат', + 'Увидеть адреса с мокапом здания и карты', + 'Построить маршрут', + ], + rationale: [ + 'Primary-darker карточка сверху с телефоном 26px narrow — главный контакт', + 'CSS-мокапы здания и карты для каждого адреса — прототипное решение без реальных изображений', + 'Кнопки «Маршрут / позвонить / видео» в каждой карточке — действия по локации', + ], + }, + 'prices': { + title: 'Цены', + category: 'Информация', + goal: 'Прайс-лист всех услуг с поиском, категориями, группировкой, диапазоном цен.', + tasks: [ + 'Найти услугу по названию', + 'Отфильтровать по категории', + 'Увидеть сгруппированные цены', + 'Записаться прямо с услуги', + ], + rationale: [ + 'Сводка найдено / от-до — сразу понятен разброс цен', + 'Категории как pill-фильтры, группировка в карточках', + 'Цены narrow-шрифтом — акцент на числе', + ], + }, + 'dev-colors': { + title: 'DEV · Палитра', + category: 'DEV', + goal: 'Служебный экран для разработчиков. Все цвета дизайн-системы с hex, ролями, применением.', + tasks: [ + 'Увидеть hex любой CSS-переменной', + 'Переключать палитру в Tweaks и наблюдать изменения', + 'Понять, где используется конкретный цвет', + 'Перейти к примерам применения', + ], + rationale: [ + 'Ключевая полоса из 8 цветов сверху — палитра видна сразу', + 'Группировка по ролям (primary / warm / accent / status / text / surfaces)', + 'Каждая строка: свотч + имя + css-var + hex + описание — полная информация', + 'Подпись «динамически» у групп, меняющихся при переключении палитры', + ], + }, + 'dev-examples': { + title: 'DEV · Примеры', + category: 'DEV', + goal: 'Готовые компоненты дизайн-системы с указанием CSS-переменных, которые они используют.', + tasks: [ + 'Посмотреть, как выглядит любая кнопка/чип/карточка', + 'Прочитать, какие vars внутри', + 'Сверить с экраном DEV · Палитра', + ], + rationale: [ + 'Каждый пример помечен VarTag-монокодами', + 'Секции по типам: кнопки / чипы / поверхности / статусы / текст / аватары / формы / тени', + ], + }, + 'docs': { + title: 'Документация', + category: 'DEV', + goal: 'Единый дизайн-гайд прототипа. Список всех экранов с описаниями — цели, задачи, design-решения.', + tasks: [ + 'Просмотреть все экраны по категориям', + 'Развернуть описание экрана inline', + 'Перейти к реальному экрану для интерактивного просмотра', + ], + rationale: [ + 'Группировка по категориям облегчает навигацию для ревью', + 'Описания collapsed-by-default, чтобы список оставался обозримым', + 'Кнопка «Открыть экран» на каждой карточке — быстрый переход в демо-режим', + 'Работает в паре с toggle «Описания» в Tweaks (плашка над телефоном)', + ], + }, +}; + +// Резолвер: по текущему screenId и ctx отдаёт правильное описание. +// Учитывает: варианты главной (homeVariant), compound-маршруты (doctor:id, +// appt:id, result:id, article:id, chat:id). +export function getScreenDoc(screenId, ctx) { + if (!screenId) return null; + const parts = screenId.split(':'); + const base = parts[0]; + + // home с вариантом: home:cards / home:list / home:feed + if (base === 'home') { + const v = ctx?.homeVariant || 'cards'; + return SCREEN_DOCS[`home:${v}`] || SCREEN_DOCS['home:cards']; + } + // chat с конкретным id (ai / doctor-syndaev / operator) — отдельные описания + if (base === 'chat' && parts[1]) { + return SCREEN_DOCS[screenId] || SCREEN_DOCS['chat']; + } + // Generic compound: doctor:xxx → doctor, article:xxx → article и т.д. + const compounds = ['doctor', 'appt', 'result', 'article', 'booking-doctor', 'booking-time', 'booking-confirm']; + if (compounds.includes(base)) { + return SCREEN_DOCS[base] || null; + } + return SCREEN_DOCS[screenId] || SCREEN_DOCS[base] || null; +} + +// Для экрана «Документация»: список всех уникальных описаний, сгруппированных +// по категориям. Возвращает упорядоченный массив для рендера. +export function getAllDocs() { + const ORDER = ['Главная', 'Врачи и запись', 'Флоу записи', 'Приёмы и результаты', 'Здоровье', 'Коммуникации', 'Информация', 'Сервис', 'DEV']; + const groups = new Map(); + for (const [key, doc] of Object.entries(SCREEN_DOCS)) { + if (!groups.has(doc.category)) groups.set(doc.category, []); + groups.get(doc.category).push({ key, ...doc }); + } + return ORDER.filter(c => groups.has(c)).map(c => ({ category: c, items: groups.get(c) })); +} + +// screenId для навигации из docs screen. Учитывает compound-маршруты. +export function resolveRouteForDoc(key) { + // home:cards → переводим в home + homeVariant cards (но через простой nav.set + // мы не можем поменять homeVariant — это в Tweaks). Поэтому home:* → 'home'. + if (key.startsWith('home:')) return 'home'; + // chat:ai / chat:doctor-syndaev / chat:operator — полноценные маршруты + if (key.startsWith('chat:')) return key; + // generic compounds: нужно подставить пример id + if (key === 'doctor') return 'doctor:syndaev'; + if (key === 'appt') return 'appt:a1'; + if (key === 'result') return 'result:r2'; + if (key === 'article') return 'article:otitis-kids'; + if (key === 'booking-doctor') return 'booking-doctor:ent'; + if (key === 'booking-time') return 'booking-time:syndaev'; + if (key === 'booking-confirm') return 'booking-confirm:syndaev:1:16:00'; + return key; +} diff --git a/src/screens/screens-docs.jsx b/src/screens/screens-docs.jsx new file mode 100644 index 0000000..da345fe --- /dev/null +++ b/src/screens/screens-docs.jsx @@ -0,0 +1,106 @@ +import React, { useState } from 'react'; +import { I } from '../icons.jsx'; +import { ScreenHeader } from '../components.jsx'; +import { getAllDocs, resolveRouteForDoc } from '../docs.js'; + +function DocRow({ doc, route, onOpen }) { + const [open, setOpen] = useState(false); + return ( +
+ + + {open && ( +
+
Задачи пользователя
+
    + {doc.tasks.map((t, i) => ( +
  • + + {t} +
  • + ))} +
+ +
Design-решения
+
    + {doc.rationale.map((t, i) => ( +
  • + + {t} +
  • + ))} +
+ + {doc.variants && ( +
+ Варианты: {doc.variants} +
+ )} + + +
+ )} +
+ ); +} + +export function DocsScreen({ nav }) { + const groups = getAllDocs(); + + return ( +
+ nav.pop()} /> + +
+
+
+ +
+ Список всех экранов прототипа с целями и design-решениями. Разверните любой пункт, чтобы прочитать задачи пользователя и обоснование. Кнопка «Открыть экран» ведёт в демо. +
+
+
+
+ +
+ {groups.map(g => ( +
+
+ {g.category} + {g.items.length} +
+
+ {g.items.map((item, i) => ( + + nav.set(r)} /> + {i < g.items.length - 1 &&
} + + ))} +
+
+ ))} +
+
+ ); +}