From 9511fcb555a2eb18e44c426894749f8999b40560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D1=81=D1=82=D0=B0=D0=BD=D1=82=D0=B8?= =?UTF-8?q?=D0=BD=20=D0=9B=D0=B5=D0=B1=D0=B5=D0=B4=D0=B8=D0=BD=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=D0=B9?= Date: Thu, 30 Apr 2026 20:54:37 +0500 Subject: [PATCH] fix test editor UI and test completition UI --- flask_app/app/services/test_attempt.py | 21 +- flask_app/app/static/css/app.css | 549 +++++++++++++++++- flask_app/app/static/js/attempt.js | 76 ++- flask_app/app/static/js/editor.js | 19 +- .../app/templates/tests/attempt_review.html | 68 ++- flask_app/app/templates/tests/editor.html | 27 +- 6 files changed, 680 insertions(+), 80 deletions(-) diff --git a/flask_app/app/services/test_attempt.py b/flask_app/app/services/test_attempt.py index e0202b0..6ff94e0 100644 --- a/flask_app/app/services/test_attempt.py +++ b/flask_app/app/services/test_attempt.py @@ -46,7 +46,13 @@ def _to_uuid(val) -> _uuid.UUID | None: # ─── load questions (shared) ───────────────────────────────────────────────── -def load_questions_for_version(session: Session, test_version_id, *, include_correct: bool) -> list[dict]: +def load_questions_for_version( + session: Session, + test_version_id, + *, + include_correct: bool, + include_hints: bool = False, +) -> list[dict]: vid = _to_uuid(test_version_id) if vid is None: return [] @@ -65,13 +71,17 @@ def load_questions_for_version(session: Session, test_version_id, *, include_cor if include_correct: base['isCorrect'] = bool(o.is_correct) options.append(base) - out.append({ + item = { 'id': str(q.id), 'text': q.text, 'questionOrder': q.question_order, 'hasMultipleAnswers': bool(q.has_multiple_answers), 'options': options, - }) + } + if include_hints: + hint = (q.ai_hint or '').strip() + item['aiHint'] = hint or None + out.append(item) return out @@ -275,7 +285,9 @@ def build_review_from_db(session: Session, attempt_id: str) -> dict: raise HttpError(400, 'Попытка не завершена.') test = attempt.test_version.test - questions = load_questions_for_version(session, attempt.test_version_id, include_correct=True) + questions = load_questions_for_version( + session, attempt.test_version_id, include_correct=True, include_hints=True + ) sel_by_q: dict[str, list[str]] = { str(ua.question_id): [str(x) for x in (ua.selected_options or [])] @@ -294,6 +306,7 @@ def build_review_from_db(session: Session, attempt_id: str) -> dict: 'id': q['id'], 'text': q['text'], 'hasMultipleAnswers': q['hasMultipleAnswers'], + 'aiHint': q.get('aiHint'), 'isUserCorrect': _same_selection(selected, correct), 'options': [ { diff --git a/flask_app/app/static/css/app.css b/flask_app/app/static/css/app.css index eee1ea9..6e1f815 100644 --- a/flask_app/app/static/css/app.css +++ b/flask_app/app/static/css/app.css @@ -736,9 +736,19 @@ body.ui-legacy .test-detail-subsection__title { margin-bottom: 0.6rem; color: var(--ink-900, #111827); } -body.ui-legacy .test-detail-ai-panel { - padding: 1rem 1.1rem 1.1rem; - margin-bottom: 1.25rem; + +/* Панель «Инструменты генерации»: одинаковые отступы по краям и снизу */ +.editor-generation-panel { + box-sizing: border-box; + padding: 1rem; + margin-bottom: 0; +} +.editor-generation-panel__status { + margin-top: 0.75rem; + margin-bottom: 0; +} +.editor-generation-panel__status:empty { + display: none; } /* ─── Option row alignment ───────────────────────────────────────── */ @@ -895,6 +905,10 @@ body.ui-legacy .test-detail-ai-panel { border-radius: 0.85rem; box-shadow: none; } +body.ui-legacy .test-detail-ai-panel.editor-generation-panel { + padding: 1rem; + margin-bottom: 0; +} body.ui-legacy .assign-toolbar { display: flex; @@ -948,7 +962,6 @@ body.ui-legacy .assign-row__fio { font-weight: 600; font-size: 0.95rem; } body.ui-legacy .assign-row__login { font-size: 0.8rem; color: #506965; font-family: ui-monospace, Menlo, monospace; } body.ui-legacy .assign-row__meta { font-size: 0.8rem; color: #506965; line-height: 1.35; } -body.ui-legacy .version-card-list, body.ui-legacy .attempts-card-list { list-style: none; margin: 0; @@ -958,43 +971,85 @@ body.ui-legacy .attempts-card-list { gap: 0.5rem; } -/* ─── Version items (compact row in top section) ─────────────────── */ -body.ui-legacy .version-item { +/* ─── Версии теста (редактор): одна высота строки, «активная» справа ─ */ +.version-card-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.version-item { display: flex; align-items: center; + justify-content: space-between; gap: 0.75rem; + min-height: 3.35rem; padding: 0.5rem 0.75rem; + box-sizing: border-box; border-radius: 0.6rem; background: var(--surface-container-low, #f5f5f5); border: 1px solid var(--outline-variant, #e0e0e0); } -body.ui-legacy .version-item[data-active="1"] { - background: color-mix(in srgb, var(--primary, #007168) 8%, white); - border-color: color-mix(in srgb, var(--primary, #007168) 25%, transparent); + +.version-item__main { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.12rem; + min-width: 0; + flex: 1 1 auto; } -body.ui-legacy .version-item__label { + +.version-item__label { font-weight: 600; font-size: 0.875rem; + line-height: 1.2; +} + +.version-item__date { + font-size: 0.78rem; + line-height: 1.25; + color: var(--ink-500, #6b7280); +} + +.version-item__actions { + flex: 0 0 auto; display: flex; align-items: center; - gap: 0.4rem; + justify-content: flex-end; + min-width: 8.5rem; } -body.ui-legacy .version-item__badge { + +.version-item__badge { font-size: 0.65rem; - font-weight: 500; - padding: 0.1rem 0.4rem; + font-weight: 600; + padding: 0.22rem 0.5rem; border-radius: 999px; background: var(--primary, #007168); color: #fff; text-transform: uppercase; letter-spacing: 0.03em; + white-space: nowrap; } -body.ui-legacy .version-item__date { - font-size: 0.78rem; - flex: 1; + +.version-item[data-active="1"] { + background: color-mix(in srgb, var(--primary, #007168) 8%, white); + border-color: color-mix(in srgb, var(--primary, #007168) 25%, transparent); +} + +body.ui-modern .version-item { + background: #fff; + border-color: rgba(15, 23, 42, 0.08); } -body.ui-legacy .version-item__spacer { - width: 1px; +body.ui-modern .version-item[data-active="1"] { + background: color-mix(in srgb, var(--brand-600, #6366f1) 6%, #fff); + border-color: color-mix(in srgb, var(--brand-600, #6366f1) 28%, transparent); +} +body.ui-modern .version-item__badge { + background: var(--brand-600, #6366f1); } body.ui-legacy #versions-section { padding: 0.75rem 1rem; @@ -1024,26 +1079,278 @@ body.ui-legacy .attempts-card-list__action { flex-shrink: 0; } +/* ─── Разбор попытки (attempt_review.html) ───────────────────────── */ +.attempt-review-page { + max-width: 42rem; + margin: 0 auto; + padding: 0 0.25rem 2rem; + width: 100%; +} + +.attempt-review-page__header { + margin-bottom: 1.5rem; +} + +.attempt-review-page__back { + margin: 0 0 0.75rem; +} + +.attempt-review-page__title { + font-size: clamp(1.15rem, 2.5vw, 1.45rem); + line-height: 1.25; + margin: 0 0 0.5rem; + color: var(--ink-900, #111827); +} + +.attempt-review-page__params { + margin: 0 0 1rem; + line-height: 1.5; + color: var(--ink-500, #6b7280); + font-size: 0.8125rem; +} + +.attempt-review-score { + display: flex; + flex-wrap: wrap; + align-items: stretch; + justify-content: space-between; + gap: 0.75rem 1rem; + padding: 1rem 1.1rem; + border-radius: 0.85rem; + border: 1px solid color-mix(in srgb, var(--outline-variant) 45%, transparent); + background: var(--surface, #fff); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); +} + +.attempt-review-score--pass { + border-color: color-mix(in srgb, var(--primary, #007168) 35%, transparent); + background: color-mix(in srgb, var(--primary, #007168) 6%, var(--surface)); +} + +.attempt-review-score--fail { + border-color: color-mix(in srgb, #b42318 22%, transparent); + background: color-mix(in srgb, #fef2f2 85%, var(--surface)); +} + +.attempt-review-score__label { + display: block; + font-size: 0.68rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--on-surface-variant); + margin-bottom: 0.35rem; +} + +.attempt-review-score__value { + margin: 0; + font-size: 1rem; + line-height: 1.4; + color: var(--ink-900, #111827); +} + +.attempt-review-score__percent { + font-weight: 500; + color: var(--ink-600, #4b5563); +} + +.attempt-review-score__threshold { + margin: 0.35rem 0 0; + font-size: 0.8125rem; + color: var(--ink-500, #6b7280); +} + +.attempt-review-score__verdict { + align-self: center; + flex-shrink: 0; + padding: 0.45rem 0.85rem; + border-radius: 999px; + font-size: 0.8125rem; + font-weight: 700; + letter-spacing: 0.02em; +} + +.attempt-review-score--pass .attempt-review-score__verdict { + background: color-mix(in srgb, var(--primary, #007168) 18%, transparent); + color: var(--primary, #007168); +} + +.attempt-review-score--fail .attempt-review-score__verdict { + background: #fee2e2; + color: #b42318; +} + +.attempt-review-page__list { + display: flex; + flex-direction: column; + gap: 1rem; + list-style: none; + margin: 0; + padding: 0; +} + +.attempt-review-card { + margin: 0; + padding: 1rem 1.1rem 1.15rem; + border-radius: 0.85rem; + border: 1px solid color-mix(in srgb, var(--outline-variant) 40%, transparent); + background: var(--surface, #fff); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.attempt-review-card__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + margin-bottom: 0.65rem; +} + +.attempt-review-card__num { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.75rem; + height: 1.75rem; + padding: 0 0.4rem; + border-radius: 0.45rem; + font-size: 0.75rem; + font-weight: 700; + background: color-mix(in srgb, var(--outline-variant) 35%, transparent); + color: var(--ink-700, #374151); +} + +.attempt-review-card__badge { + font-size: 0.75rem; + font-weight: 700; + padding: 0.25rem 0.55rem; + border-radius: 999px; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.attempt-review-card__badge--ok { + background: color-mix(in srgb, var(--primary, #007168) 14%, transparent); + color: var(--primary, #0a6b5c); +} + +.attempt-review-card__badge--bad { + background: #fee2e2; + color: #b42318; +} + +.attempt-review-card__question { + margin: 0 0 0.75rem; + font-size: 1.02rem; + line-height: 1.45; + color: var(--ink-900, #111827); +} + +.attempt-review-hint { + margin: 0 0 1rem; + padding: 0.75rem 0.95rem; + border-radius: 0.65rem; + border: 1px solid color-mix(in srgb, var(--brand-500, #6366f1) 22%, transparent); + background: color-mix(in srgb, var(--brand-50, #eef2ff) 88%, var(--surface)); +} + +.attempt-review-hint__label { + display: block; + font-size: 0.68rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--on-surface-variant); + margin-bottom: 0.4rem; +} + +.attempt-review-hint__text { + margin: 0; + font-size: 0.9rem; + line-height: 1.5; + color: var(--on-surface); + white-space: pre-wrap; +} + .attempt-review-options { list-style: none; - padding-left: 0; + padding: 0; margin: 0; + display: flex; + flex-direction: column; + gap: 0.45rem; } .attempt-review-option { - margin: 0.25rem 0; - padding: 0.3rem 0.5rem; - border-radius: 0.55rem; + margin: 0; + padding: 0.65rem 0.85rem; + border-radius: 0.65rem; + border: 1px solid color-mix(in srgb, var(--outline-variant) 55%, transparent); + background: color-mix(in srgb, var(--surface-container-low) 55%, var(--surface)); + transition: border-color 0.12s ease; +} + +.attempt-review-option__text { + display: flex; + align-items: flex-start; + gap: 0.55rem; + font-size: 0.94rem; + line-height: 1.45; +} + +.attempt-review-option__mark { + flex-shrink: 0; + font-size: 1rem; + line-height: 1.35; + opacity: 0.85; +} + +.attempt-review-option__body { + flex: 1 1 auto; + min-width: 0; +} + +.attempt-review-option__tag { + display: inline-block; + margin-left: 0.35rem; + padding: 0.1rem 0.4rem; + border-radius: 0.35rem; + font-size: 0.68rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.03em; + vertical-align: middle; + background: #ecfdf3; + color: #067647; } .attempt-review-option--wrong { + border-color: color-mix(in srgb, #b42318 28%, transparent); background: #fef2f2; - color: #b42318; + color: #7f1d1d; +} + +.attempt-review-option--wrong .attempt-review-option__tag { + display: none; } .attempt-review-option--correct { + border-color: color-mix(in srgb, #067647 35%, transparent); background: #ecfdf3; - color: #067647; + color: #14532d; +} + +body.ui-modern .attempt-review-page { + padding-bottom: max(2rem, env(safe-area-inset-bottom, 0px)); +} + +body.ui-modern .attempt-review-card { + border-color: rgba(15, 23, 42, 0.08); +} + +body.ui-legacy .attempt-review-page { + padding-left: 0; + padding-right: 0; } /* ─── Прохождение теста: один вопрос, прогресс сверху, удобно с телефона ─── */ @@ -1327,11 +1634,192 @@ body.ui-legacy .attempts-card-list__action { } .attempt-result-card { - padding: 1.25rem 1.35rem; + padding: 1.35rem 1.35rem 1.5rem; + max-width: 22rem; + margin-left: auto; + margin-right: auto; + text-align: center; + /* Трек и центр — белые; цвет только на дугах результата / порога */ + --attempt-result-track: #fff; + --attempt-result-disk: #fff; + --attempt-result-fail-score: var(--primary, #007168); + --attempt-result-gap: color-mix(in srgb, #f87171 22%, #fecaca 78%); + --attempt-result-pass: color-mix(in srgb, #22c55e 92%, #15803d 8%); + --attempt-result-ring-width: 9px; +} + +.attempt-result-card__inner { + display: flex; + flex-direction: column; + align-items: center; + gap: 0; } .attempt-result-title { - margin: 0 0 0.75rem; + margin: 0 0 1rem; + font-size: 1.125rem; + width: 100%; +} + +.attempt-result-visual { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + margin-bottom: 1.1rem; + animation: attempt-result-enter 0.5s cubic-bezier(0.22, 1, 0.36, 1) both; +} + +@keyframes attempt-result-enter { + from { + opacity: 0; + transform: translateY(0.35rem) scale(0.96); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.attempt-result-donut { + position: relative; + width: 10.75rem; + height: 10.75rem; + flex-shrink: 0; +} + +.attempt-result-donut__svg { + position: absolute; + inset: 0; + z-index: 0; + width: 100%; + height: 100%; + display: block; + overflow: visible; + filter: drop-shadow(0 1px 2px rgba(15, 23, 42, 0.08)) drop-shadow(0 2px 6px rgba(15, 23, 42, 0.06)); +} + +.attempt-result-svg__track { + stroke: var(--attempt-result-track); + stroke-width: var(--attempt-result-ring-width); + stroke-linecap: round; +} + +.attempt-result-svg__pass, +.attempt-result-svg__fail-score, +.attempt-result-svg__fail-gap { + fill: none; + stroke-width: var(--attempt-result-ring-width); + stroke-linecap: round; + stroke-linejoin: round; +} + +.attempt-result-svg__pass { + stroke: var(--attempt-result-pass); +} + +.attempt-result-svg__fail-score { + stroke: var(--attempt-result-fail-score); +} + +.attempt-result-svg__fail-gap { + stroke: var(--attempt-result-gap); +} + +.attempt-result-donut__disk { + position: absolute; + left: 50%; + top: 50%; + z-index: 1; + width: 71%; + height: 71%; + transform: translate(-50%, -50%); + border-radius: 50%; + background: var(--attempt-result-disk); + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 2px 8px rgba(15, 23, 42, 0.05); +} + +.attempt-result-donut__center { + position: absolute; + inset: 0; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; +} + +.attempt-result-donut__icon { + font-size: 2.75rem !important; + font-variation-settings: 'FILL' 1, 'wght' 600, 'GRAD' 0, 'opsz' 24; + line-height: 1; +} + +.attempt-result-donut__center[data-passed="1"] .attempt-result-donut__icon { + color: var(--attempt-result-pass); +} + +.attempt-result-donut__center[data-passed="0"] .attempt-result-donut__icon { + color: #dc2626; +} + +.attempt-result-verdict { + margin: 0.5rem 0 0; + padding: 0; +} + +.attempt-result-verdict__label { + font-size: 0.8125rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.attempt-result-verdict[data-passed="1"] .attempt-result-verdict__label { + color: var(--attempt-result-pass); +} + +.attempt-result-verdict[data-passed="0"] .attempt-result-verdict__label { + color: #b42318; +} + +.attempt-result-stats { + list-style: none; + margin: 0 0 1.1rem; + padding: 0.65rem 0.85rem; + width: 100%; + max-width: 18rem; + text-align: left; + font-size: 0.875rem; + line-height: 1.45; + color: var(--ink-700, #3d5357); + background: color-mix(in srgb, var(--surface-container-low, #f5f5f5) 88%, transparent); + border-radius: 0.75rem; + border: 1px solid color-mix(in srgb, var(--outline-variant, #e0e0e0) 45%, transparent); +} + +.attempt-result-stats li + li { + margin-top: 0.35rem; +} + +.attempt-result-stats__k { + color: var(--on-surface-variant, #506965); + font-weight: 500; + margin-right: 0.25rem; +} + +.attempt-result-actions { + margin: 0; + width: 100%; +} + +.attempt-result-review-link { + display: inline-flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 2.75rem; + text-decoration: none; } .attempt-passed { @@ -1344,6 +1832,13 @@ body.ui-legacy .attempts-card-list__action { color: #b42318; } +body.ui-modern .attempt-result-card { + --attempt-result-fail-score: var(--brand-600, #6366f1); +} +body.ui-modern .attempt-result-verdict[data-passed="1"] .attempt-result-verdict__label { + color: var(--attempt-result-pass); +} + body.ui-modern .attempt-flow { min-height: min(75dvh, 880px); } diff --git a/flask_app/app/static/js/attempt.js b/flask_app/app/static/js/attempt.js index 78404b6..a2cf8c1 100644 --- a/flask_app/app/static/js/attempt.js +++ b/flask_app/app/static/js/attempt.js @@ -35,6 +35,24 @@ }[m])); } + /** Доля круга 0..1 от верха по часовой; путь для stroke с round caps (viewBox 120). */ + function attemptResultArcD(cx, cy, r, t0, t1) { + if (t1 <= t0 + 1e-9) return ''; + if (t1 - t0 > 0.5 + 1e-6) { + const a = attemptResultArcD(cx, cy, r, t0, t0 + 0.5); + const b = attemptResultArcD(cx, cy, r, t0 + 0.5, t1); + return `${a} ${b.replace(/^M [\d.]+\s+[\d.]+\s+/, '')}`; + } + const angle = (t) => (-Math.PI / 2) + 2 * Math.PI * t; + const x0 = cx + r * Math.cos(angle(t0)); + const y0 = cy + r * Math.sin(angle(t0)); + const x1 = cx + r * Math.cos(angle(t1)); + const y1 = cy + r * Math.sin(angle(t1)); + const spanDeg = (t1 - t0) * 360; + const largeArc = spanDeg > 180 ? 1 : 0; + return `M ${x0.toFixed(2)} ${y0.toFixed(2)} A ${r} ${r} 0 ${largeArc} 1 ${x1.toFixed(2)} ${y1.toFixed(2)}`; + } + function setErr(msg) { errEl.textContent = msg || 'Ошибка.'; errEl.style.display = msg ? 'block' : 'none'; @@ -321,12 +339,58 @@ timerEl.style.display = 'none'; if (flowEl) flowEl.style.display = 'none'; resultEl.style.display = ''; - resultEl.innerHTML = - `

Результат

` - + `

Правильно: ${data.correctCount} из ${data.totalQuestions}` - + ` (${data.percent}%). Порог: ${data.passingThreshold}%.

` - + `

${data.passed ? 'Зачёт' : 'Незачёт'}

` - + `

Разбор попытки

`; + const pct = Math.min(100, Math.max(0, Number(data.percent) || 0)); + const thr = Math.min(100, Math.max(0, Number(data.passingThreshold) || 0)); + const passed = !!data.passed; + const thrArc = passed ? pct : Math.min(100, Math.max(pct, thr)); + const centerIcon = passed ? 'check' : 'close'; + const verdictLabel = passed ? 'Зачёт' : 'Незачёт'; + const ariaRing = passed + ? `Зачёт: ${pct} процентов верных ответов` + : `Незачёт: ${pct} процентов, порог ${thr} процентов`; + const CX = 60; + const CY = 60; + const RAD = 48; + const tScore = pct / 100; + const tThr = thrArc / 100; + const trackCircle = ``; + let arcPaths = ''; + if (passed) { + const dPass = attemptResultArcD(CX, CY, RAD, 0, tScore); + if (dPass) { + arcPaths += ``; + } + } else { + const dGap = tThr > tScore + 1e-9 ? attemptResultArcD(CX, CY, RAD, tScore, tThr) : ''; + const dScore = tScore > 1e-9 ? attemptResultArcD(CX, CY, RAD, 0, tScore) : ''; + if (dGap) arcPaths += ``; + if (dScore) arcPaths += ``; + } + const donutSvg = ``; + resultEl.innerHTML = ` +
+

Результат

+
+
+ ${donutSvg} + +
+ +
+
+

+ ${verdictLabel} +

+
+
    +
  • Верно ${data.correctCount} из ${data.totalQuestions} (${pct}%)
  • +
  • Порог зачёта ${thr}%
  • +
+

+ Разбор попытки +

+
`; } catch (e) { setErr(e.message); btnFinish.disabled = false; diff --git a/flask_app/app/static/js/editor.js b/flask_app/app/static/js/editor.js index ae33e0d..163777a 100644 --- a/flask_app/app/static/js/editor.js +++ b/flask_app/app/static/js/editor.js @@ -1419,15 +1419,16 @@ li.dataset.versionId = r.id; li.dataset.active = r.is_active ? '1' : '0'; li.innerHTML = ` - - Версия ${r.version} - ${r.is_active ? 'активная' : ''} - - ${fmtDt(r.created_at)} - ${!r.is_active - ? `` - : ''}`; +
+ Версия ${r.version} + ${fmtDt(r.created_at)} +
+
+ ${r.is_active + ? 'активная' + : ``} +
`; versionsListEl.appendChild(li); }); versionsListEl.querySelectorAll('.version-item__activate').forEach((btn) => { diff --git a/flask_app/app/templates/tests/attempt_review.html b/flask_app/app/templates/tests/attempt_review.html index ceb4593..d523146 100644 --- a/flask_app/app/templates/tests/attempt_review.html +++ b/flask_app/app/templates/tests/attempt_review.html @@ -3,42 +3,54 @@ {% block content %}
- -

Разбор: {{ review.testTitle }}

- {% set tl = review.timeLimit %} - {% set timestr = 'без ограничения' if tl is none or tl == 0 else (tl|string ~ ' мин') %} - {% set rm = review.resultMode or 'end' %} - {% set res = 'сразу' if rm == 'immediate' else 'в конце' %} - {% set hint = 'недоступны' if rm != 'immediate' else ('вкл' if review.hintsEnabled else 'выкл') %} -

- Порог: {{ review.passingThreshold }}% · Вопросов: {{ review.totalQuestions }} · Время: {{ timestr }} · Результат: {{ res }} · Подсказки: {{ hint }} -

-

- Правильно: {{ review.correctCount }} из {{ review.totalQuestions }} - ({{ review.percent }}%). Порог: {{ review.passingThreshold }}%. - {% if review.passed %} - Зачёт. - {% else %} - Незачёт. - {% endif %} -

+
+ +

Разбор: {{ review.testTitle }}

+ {% set tl = review.timeLimit %} + {% set timestr = 'без ограничения' if tl is none or tl == 0 else (tl|string ~ ' мин') %} + {% set rm = review.resultMode or 'end' %} + {% set res = 'сразу' if rm == 'immediate' else 'в конце' %} + {% set hint = 'недоступны' if rm != 'immediate' else ('вкл' if review.hintsEnabled else 'выкл') %} +

+ Порог: {{ review.passingThreshold }}% · Вопросов: {{ review.totalQuestions }} · Время: {{ timestr }} · Результат: {{ res }} · Подсказки: {{ hint }} +

+
+
+ Итог +

+ Правильно {{ review.correctCount }} из {{ review.totalQuestions }} + ({{ review.percent }}%) +

+

Порог зачёта: {{ review.passingThreshold }}%

+
+ {% if review.passed %}Зачёт{% else %}Незачёт{% endif %} +
+
-
+
{% for q in review.questions %} -
-
- {{ 'Верно' if q.isUserCorrect else 'Ошибка' }} +
+
+ {{ loop.index }} + + {{ 'Верно' if q.isUserCorrect else 'Ошибка' }} +
-

{{ loop.index }}. {{ q.text }}

-
    +

    {{ q.text }}

    + {% if q.aiHint %} +
    + Подсказка +

    {{ q.aiHint }}

    +
    + {% endif %} +
      {% for o in q.options %}
    • - {% if o.selected %}☑{% else %}☐{% endif %} - {{ o.text }} - {% if o.isCorrect %} (правильный){% endif %} + + {{ o.text }}{% if o.isCorrect %}верный ответ{% endif %}
    • {% endfor %} diff --git a/flask_app/app/templates/tests/editor.html b/flask_app/app/templates/tests/editor.html index d8371ef..f206692 100644 --- a/flask_app/app/templates/tests/editor.html +++ b/flask_app/app/templates/tests/editor.html @@ -115,12 +115,14 @@ class="form-input" style="width:90px;" />
-
-