diff --git a/DOC/ШАГИ/ШАГ_2026-04-27_002.md b/DOC/ШАГИ/ШАГ_2026-04-27_002.md new file mode 100644 index 0000000..137ec6e --- /dev/null +++ b/DOC/ШАГИ/ШАГ_2026-04-27_002.md @@ -0,0 +1,5 @@ +# Шаг 2026-04-27 — спринты мобильного UI и правки + +- Документ спринтов: [`docs/СПРИНТЫ_МОБИЛЬНЫЙ_ДИЗАЙН.md`](../../docs/СПРИНТЫ_МОБИЛЬНЫЙ_ДИЗАЙН.md) (спринт 1 выполнен в коде). +- Стили: `actions-bar`, `version-card-list`, `list-row__meta-tail`, `inline-actions--block-mobile`, safe-area у `.cabinet-main`, `.btn--sm` / `.btn-ghost`, `assign-list` без пустой «коробки`. +- Страницы: `TestDetail.jsx` (карточки версий, панель команд, назначение), `TestsList.jsx` (мета-строка). diff --git a/docs/СПРИНТЫ_МОБИЛЬНЫЙ_ДИЗАЙН.md b/docs/СПРИНТЫ_МОБИЛЬНЫЙ_ДИЗАЙН.md new file mode 100644 index 0000000..13827e9 --- /dev/null +++ b/docs/СПРИНТЫ_МОБИЛЬНЫЙ_ДИЗАЙН.md @@ -0,0 +1,40 @@ +# Спринты: мобильный UI кабинета тестов + +Рядом с: [`ПРЕДЛОЖЕНИЕ_ДИЗАЙН_СОЗДАНИЕ_ТЕСТА.md`](./ПРЕДЛОЖЕНИЕ_ДИЗАЙН_СОЗДАНИЕ_ТЕСТА.md). + +--- + +## Спринт 1 — быстрые исправления (текущий) + +**Цель:** выровнять кнопки, мета-строку списка, историю версий, назначение и safe-area; без смены контентной модели страниц. + +| # | Задача | Статус | +|---|--------|--------| +| 1.1 | Панель «Сохранить черновик / К списку»: убрать конфликт `inline-actions .btn { width: auto }` с `btn-primary` — колонка на всю ширину (`.actions-bar`) | done | +| 1.2 | Touch: `min-height` у `.btn--sm` (убрать, удалить вопрос, сделать активной…) | done | +| 1.3 | Список тестов: не разбивать «· v1» — хвост в `list-row__meta-tail` + `white-space: nowrap` | done | +| 1.4 | «История версий»: вместо `` — карточки (`surface-card` + flex) | done | +| 1.5 | «Назначение»: не рендерить пустой `.assign-list` (убрать «коробку» без людей) | done | +| 1.6 | Сильнее рамка `.btn-ghost` (согласование с полями) | done | +| 1.7 | `padding-bottom` у `.cabinet-main` + `env(safe-area-inset-bottom)` | done | +| 1.8 | «Публикация»: на узком экране — кнопка на всю ширину (`.inline-actions--block-mobile`) | done | + +**Файлы:** `frontend/src/styles/cabinet-theme.css`, `frontend/src/pages/TestDetail.jsx`, `frontend/src/pages/TestsList.jsx`. + +--- + +## Спринт 2 — бэклог (следующий) + +| # | Задача | +|---|--------| +| 2.1 | «Прогоны и разбор»: на мобилке заменить таблицу на карточки или гориз. скролл с фиксированными колонками | +| 2.2 | «Импорт из файла»: кастомная кнопка (скрытый `input` + стилизованный `label` под `.btn`) | +| 2.3 | «Вопрос 1» + «Сгенерировать вопрос (ИИ)» — не в одной строке на узком экране; явная иерархия primary/secondary | +| 2.4 | Радио vs чекбокс у вариантов ответа при «несколько верных» — визуальная метафора (квадраты vs круги) | +| 2.5 | Закреплённый футер с действиями «Сохранить» (опционально) | + +--- + +## Спринт 3 — дизайн-токены (по желанию) + +Единая шкала: `--control-height`, `--control-padding-x`, `--button-gap` — рефакторинг всех `inline-actions` и форм. diff --git a/frontend/src/pages/TestDetail.jsx b/frontend/src/pages/TestDetail.jsx index cb0ba00..a8c8db3 100644 --- a/frontend/src/pages/TestDetail.jsx +++ b/frontend/src/pages/TestDetail.jsx @@ -938,8 +938,8 @@ export default function TestDetail() {
-
-
- - - - - - - - - {versions.map((r) => ( - - - - - - - ))} - -
ВерсияАктивнаСоздана -
- v{r.version} - {r.is_active && ( - - текущая +
    + {versions.map((r) => ( +
  • +
    +
    +
    + + v{r.version} - )} -
{r.is_active ? 'да' : 'нет'}{fmtDt(r.created_at)} - {!r.is_active && ( - - )} -
- + {r.is_active && ( + + текущая + + )} + +

+ {fmtDt(r.created_at)} +

+

+ Активна: {r.is_active ? 'да' : 'нет'} +

+ + {!r.is_active && ( + + )} + + + ))} + {attemptsList != null && attemptsList.length > 0 && ( @@ -1065,7 +1058,10 @@ export default function TestDetail() { )} -
+
{test?.chainActive !== false ? (
-
- {assignPeople.length === 0 && !assignLoadBusy && ( + {assignPeople.length > 0 ? ( +
+ {assignPeople.map((p) => { + const k = assignPersonKey(p); + const picked = assignSelected.has(k); + return ( + + ); + })} +
+ ) : ( + !assignLoadBusy && (

Нет подходящих записей. Смените фильтры или поиск.

- )} - {assignPeople.map((p) => { - const k = assignPersonKey(p); - const picked = assignSelected.has(k); - return ( - - ); - })} -
+ ) + )}
@@ -151,11 +147,9 @@ export default function TestsList() { {t.title} {formatTestAuthorLabel(user, t.created_by, t.author_full_name)} - - {' '} - ·{' '} + + {' · '}v{t.version} · скрыт - v{t.version} · скрыт
diff --git a/frontend/src/styles/cabinet-theme.css b/frontend/src/styles/cabinet-theme.css index 49d11ce..03e4239 100644 --- a/frontend/src/styles/cabinet-theme.css +++ b/frontend/src/styles/cabinet-theme.css @@ -248,7 +248,7 @@ code, .btn-ghost { background: transparent; color: var(--primary); - border-color: color-mix(in srgb, var(--outline-variant) 50%, transparent); + border-color: color-mix(in srgb, var(--outline-variant) 70%, transparent); } .btn-ghost:hover { @@ -264,8 +264,11 @@ code, .btn--sm { font-size: 0.8rem; - padding: 0.35rem 0.6rem; + padding: 0.5rem 0.75rem; border-radius: 0.5rem; + min-height: 2.75rem; + min-width: 2.75rem; + box-sizing: border-box; } /* --- App shell (cabinet/base) --- */ @@ -378,7 +381,7 @@ code, max-width: var(--max-content); width: 100%; margin: 0 auto; - padding: 1.25rem 1.25rem 2.5rem; + padding: 1.25rem 1.25rem calc(2.5rem + env(safe-area-inset-bottom, 0px)); } /* Cards & lists */ @@ -431,6 +434,11 @@ code, margin-top: 0.25rem; } +/* «· v1» и хвост мета — не рвём посередине (мобайл) */ +.list-row__meta-tail { + white-space: nowrap; +} + /* Вся плитка — одна ссылка */ .list-row--action { padding: 0; @@ -609,7 +617,8 @@ code, } .assign-list { - max-height: min(50vh, 22rem); + max-height: min(40vh, 18rem); + min-height: 0; overflow: auto; border: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent); border-radius: 0.75rem; @@ -755,6 +764,57 @@ code, color: var(--on-surface-variant); } +/* История версий: карточки вместо таблицы (мобайл) */ +.version-card-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.version-card-list__item { + margin: 0; +} + +.version-card-list__row { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + justify-content: space-between; + gap: 0.75rem; +} + +.version-card-list__main { + min-width: 0; + flex: 1 1 12rem; +} + +.version-card-list__title-line { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; +} + +.version-card-list__action { + flex: 0 0 auto; + align-self: center; +} + +@media (max-width: 520px) { + .version-card-list__row { + flex-direction: column; + align-items: stretch; + } + + .version-card-list__action { + width: 100%; + align-self: stretch; + } +} + .draft-block { margin-top: 1.25rem; padding: 1rem; @@ -783,3 +843,60 @@ code, .inline-actions .btn { width: auto; } + +/* Нижняя панель: полноширинные primary + secondary (без перебития .inline-actions .btn) */ +.actions-bar { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 0.5rem; + width: 100%; + box-sizing: border-box; +} + +.actions-bar .btn-primary { + width: 100%; + margin-top: 0; + box-sizing: border-box; +} + +.actions-bar a.btn, +.actions-bar .btn.btn-ghost { + display: block; + width: 100%; + text-align: center; + box-sizing: border-box; +} + +@media (min-width: 480px) { + .actions-bar { + flex-direction: row; + flex-wrap: wrap; + align-items: center; + } + + .actions-bar .btn-primary { + width: auto; + min-width: 12rem; + flex: 1 1 auto; + } + + .actions-bar a.btn, + .actions-bar .btn.btn-ghost { + display: inline-block; + width: auto; + flex: 0 0 auto; + } +} + +@media (max-width: 520px) { + .inline-actions--block-mobile { + flex-direction: column; + align-items: stretch; + } + + .inline-actions--block-mobile .btn { + width: 100%; + box-sizing: border-box; + } +}