diff --git a/frontend/src/pages/TestDetail.jsx b/frontend/src/pages/TestDetail.jsx index b6c0a67..27036c2 100644 --- a/frontend/src/pages/TestDetail.jsx +++ b/frontend/src/pages/TestDetail.jsx @@ -34,13 +34,18 @@ function createEmptyQuestion() { } /** - * @param {{ title: string, defaultOpen?: boolean, id?: string, children: import('react').ReactNode }} p + * @param {{ title: string, subtitle?: string, defaultOpen?: boolean, id?: string, children: import('react').ReactNode }} p */ -function AccSection({ title, defaultOpen, id, children }) { +function AccSection({ title, subtitle, defaultOpen, id, children }) { return (
- {title} + + + {title} + {subtitle ? {subtitle} : null} + +
{children}
@@ -235,6 +240,16 @@ export default function TestDetail() { }); } + function selectAllVisible() { + setAssignSelected((prev) => { + const next = new Set(prev); + for (const p of assignPeople) { + next.add(assignPersonKey(p)); + } + return next; + }); + } + async function postAssign() { const selectedRows = assignPeople.filter((p) => assignSelected.has(assignPersonKey(p)) @@ -657,14 +672,13 @@ export default function TestDetail() { При сохранении будет создана новая версия теста. )} - {data && attemptsErr && ( -

- {attemptsErr} -

- )} - +
- +
-
+

(

))} -
-
+ +
+

Документ в вопросы

+

+ PDF, Word или текст — вставьте в черновик вопросов. +

+
+ + +
+ {importErr && ( +

+ {importErr} +

+ )} + {importPreview && ( +
+

+ {importPreview.generation?.message} +

+ {importPreview.generation?.textPreview && !importPreview.generation?.available && ( +
+                  {importPreview.generation.textPreview}
+                  {importPreview.textLength > 4000 ? '…' : ''}
+                
+ )} + {importPreview.generation?.draft && ( +
+ +
+ )} + {importPreview.extractedText && ( +
+ +
+ )} +
+ )} +
@@ -959,295 +1048,276 @@ export default function TestDetail() {

)}
- -
    - {versions.map((r) => ( -
  • -
    -
    -
    - - v{r.version} - - {r.is_active && ( - - текущая + +
    +

    Версии

    +
      + {versions.map((r) => ( +
    • +
      +
      +
      + + v{r.version} - )} -
      -

      - {fmtDt(r.created_at)} -

      -

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

      -
      - {!r.is_active && ( - - )} -
      -
    • - ))} -
    - - - {attemptsList != null && attemptsList.length > 0 && ( - - {attemptsErr && ( -

    - {attemptsErr} -

    - )} -
      - {attemptsList.map((a) => { - const when = a.completedAt - ? fmtDt(a.completedAt) - : a.startedAt - ? fmtDt(a.startedAt) - : '—'; - const result = - a.status === 'completed' && a.totalQuestions != null ? ( - <> - {a.correctCount}/{a.totalQuestions} - {a.passed ? ' · зачёт' : ' · незачёт'} - - ) : ( - a.status - ); - return ( -
    • -
      -
      -

      - {when} -

      -

      - {a.attempterName || '—'} - {a.attempterLogin && ( - - {a.attempterLogin} - - )} -

      -

      - v{a.testVersion} ·{' '} - {result} -

      + {r.is_active && ( + + текущая + + )}
      - {a.status === 'completed' && ( - - Разбор - - )} +

      + {fmtDt(r.created_at)} +

      +

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

      -
    • - ); - })} + {!r.is_active && ( + + )} +
    +
  • + ))}
-
- )} - - -
- {test?.chainActive !== false ? ( - - ) : ( - +
+ {attemptsList !== undefined && ( +
+

Прохождения

+ {attemptsList == null && ( +

+ {attemptsErr || 'Список прогонов сейчас недоступен.'} +

+ )} + {attemptsList && attemptsList.length > 0 && ( +
    + {attemptsList.map((a) => { + const when = a.completedAt + ? fmtDt(a.completedAt) + : a.startedAt + ? fmtDt(a.startedAt) + : '—'; + const result = + a.status === 'completed' && a.totalQuestions != null ? ( + <> + {a.correctCount}/{a.totalQuestions} + {a.passed ? ' · зачёт' : ' · незачёт'} + + ) : ( + a.status + ); + return ( +
  • +
    +
    +

    + {when} +

    +

    + {a.attempterName || '—'} + {a.attempterLogin && ( + + {a.attempterLogin} + + )} +

    +

    + v{a.testVersion} ·{' '} + {result} +

    +
    + {a.status === 'completed' && ( + + Разбор + + )} +
    +
  • + ); + })} +
+ )} + {attemptsList && attemptsList.length === 0 && ( +

+ Пока нет зарегистрированных прогонов. +

+ )} +
)} -
- -
- - -
- {importErr && ( -

- {importErr} -

- )} - {importPreview && ( -
-

- {importPreview.generation?.message} + +

+

Видимость

+

+ Скрытые тесты в общем списке не показываются; ссылку на тест по-прежнему можно открыть.

- {importPreview.generation?.textPreview && !importPreview.generation?.available && ( -
-              {importPreview.generation.textPreview}
-              {importPreview.textLength > 4000 ? '…' : ''}
-            
- )} - {importPreview.generation?.draft && ( -
+
+ {test?.chainActive !== false ? ( -
- )} - {importPreview.extractedText && ( -
+ ) : ( -
- )} + )} +
- )} - - - {assignmentUi && data && ( - - {assignErr && ( -

- {assignErr} + {assignmentUi && data && ( +

+

Кому выдать

+

+ Список с учётом поиска и фильтров; можно отметить всех на экране.

- )} -
- setAssignSearch(e.target.value)} - aria-label="Поиск сотрудника" - /> - - - {assignLoadBusy && Загрузка…} -
- {assignPeople.length > 0 ? ( -
- {assignPeople.map((p) => { - const k = assignPersonKey(p); - const picked = assignSelected.has(k); - return ( -