{{--
"Добавить результаты" matrix form + "Прогресс по ученикам" history table, rendered both by
admin/ent_scores/index.blade.php (initial load) and by EntScoreController::store()'s AJAX
branch, which re-renders this partial after a successful submit and returns it as data.html
for the js-ajax-form handler (layouts/app.blade.php) to swap in via
data-replace-target="#entScoresCard" — needed to clear the matrix back to blank inputs and
refresh the progress table, both of which only happened via full reload before. Params:
$search, $classId, $students, $sections, $lastProfile, $detail, $attempts (see
EntScoreController::cardData(), which assembles all of them for every call site).
The progress table's rows expand into every sitting that student has (from $attempts), each
with an edit and a delete button — update()/destroy() answer both with a re-rendered copy of
this same partial, so the two write paths added later need no swap logic of their own.
Two entry modes over the same posted shape (scores[student_id][section_id]):
- compact (default) — the mandatory sections as fixed columns, then two "profile subject"
slots per student, each a subject dropdown paired with a score box. That mirrors an
actual ЕНТ sitting (3 mandatory + 2 subjects of the student's own choosing) instead of
asking the user to find two of eleven profile columns in a table that scrolls sideways.
- full — the original every-student × every-section matrix, kept for what the two slots
can't express (a student with three subjects on record, edits across many subjects at
once) and reached via the "Показать все предметы" switch.
Both tables are always rendered, and every input in the inactive one is disabled so the two
can share input names without the browser posting each score twice. Which one is active is
JS's business (see index.blade.php); with JS off the compact table is the one that works,
including whatever its dropdowns were preselected with server-side.
--}}
@php
$mandatorySections = array_values(array_filter($sections, fn ($s) => (int) $s['is_mandatory'] === 1));
$profileSections = array_values(array_filter($sections, fn ($s) => (int) $s['is_mandatory'] !== 1));
$formatMax = fn ($max) => rtrim(rtrim(number_format((float) $max, 1, '.', ''), '0'), '.');
// Subject names for the expanded sitting rows, which carry section ids and nothing else.
$sectionNames = array_column($sections, 'name', 'id');
// Which subject each dropdown starts on. old() wins (a rejected submit has to come back
// exactly as typed), otherwise it's whatever the student sat last time — see
// EntScoreController::lastProfileSections(). Only ids still in the dictionary survive, so a
// section deleted since that sitting doesn't select a phantom option.
$oldScores = old('scores', []);
$profileIds = array_map(fn ($s) => (int) $s['id'], $profileSections);
$preselect = [];
foreach ($students as $s) {
$sid = (int) $s['id'];
$typed = array_map('intval', array_keys(array_filter(
$oldScores[$sid] ?? [],
fn ($v) => $v !== null && $v !== ''
)));
$chosenIds = array_values(array_intersect($typed, $profileIds))
?: array_values(array_intersect($lastProfile[$sid] ?? [], $profileIds));
$preselect[$sid] = array_slice($chosenIds, 0, 2);
}
@endphp
Добавить результаты
@if (! empty($students))
@endif
@if (!empty($detail))
Прогресс по ученикам
{{-- Table sits directly in the section (no ax-info-section-body) so it
stays flush to the edges, as it did inside the old card. --}}
{{-- Collapsed by default: most visits to this page are entry, not
correction, and a student with six sittings would otherwise push
the next student off the screen. --}}
{{-- --ax-gray-50, not Bootstrap's .bg-body-tertiary: this app's dark mode
is its own token set (css/style.css), and
Bootstrap's theme variables never flip with it — .bg-body-tertiary
would be a white panel in the middle of a dark page. --}}
Дата
Сумма
Предметы
Заметка
Действия
@foreach ($attempts[$row['student_id']] ?? [] as $attempt)
{{ $attempt['date'] }}
{{ $attempt['total'] }}
@foreach ($attempt['sections'] as $sectionId => $score)
{{ $sectionNames[$sectionId] ?? ('#' . $sectionId) }}: {{ $score }}
@endforeach
@if ($attempt['unassigned'] !== null)
{{-- A pre-021 row: no subject to name, but it still counts
toward the sum above, so say where the difference is. --}}
без предмета: {{ $attempt['unassigned'] }}
@endif
{{ $attempt['note'] ?? '—' }}
{{-- The dialog itself is shared and filled in from these
attributes on click (see index.blade.php): one modal per
sitting would be every student × every sitting × every
subject worth of inputs in the page. --}}
@endforeach
@endforeach
{{--
Edit dialog, one for the whole card. Every subject in the dictionary gets a field, not
just the ones the sitting already has: "this score is filed under the wrong subject" and
"I missed one" are the two edits this exists for, and neither is expressible otherwise.
An emptied field removes that subject from the sitting — update() reconciles the set.
Sits outside the entry form on purpose (a nested