@extends('layouts.app') @section('title', 'Информация о студенте') {{-- Ports app/Views/student/show.php (legacy, 1716 lines) + the render() payload from App\Controllers\StudentController::show() (app/Controllers/StudentController.php:756-959) — see the Phase 8 plan. Every write form on this page is Laravel-native (@csrf) as of Phase 14 — payments/charges (PaymentController, Phases 9-10/12), frozen days (Phase 11), student edit/responsibles (Phase 13), and status change/tariff assignment (Phase 14) each moved once their last legacy caller was confirmed gone. The lessonStatusModal's fetch() still has no CSRF field at all, matching legacy LessonController::updateStudentStatus(), which never called verifyCsrf() either — its endpoint moved to LessonAttendanceController::updateStudentStatus() in Phase 27 (the JS here is unchanged, still posts the same JSON to the same URL). Role gating below mirrors the legacy view's per-section in_array($role, [...]) checks exactly — the page itself has no page-level role restriction (only SessionAuth::check()), which is a pre-existing gap flagged (not fixed) in the Phase 8 plan. --}} @php $attColorClass = $attendancePct < 50 ? 'ax-metric-value--danger' : ($attendancePct < 80 ? 'ax-metric-value--warning' : 'ax-metric-value--success'); $latenessColorClass = $latenessCount > 0 ? 'ax-metric-value--danger' : ''; $absencesColorClass = $absences > 0 ? 'ax-metric-value--danger' : ''; @endphp @section('content')
@if (!$studentInfo)
Информация о студенте не найдена.
@else {{-- SECTION 1 — Main info --}}
Общая информация
@if (\App\Support\Modules::enabled('student_password')) @endif @if ($role === 'admin') @endif
{{-- Удаление ученика — admin only, and only for a student with no history behind them. StudentProfileController::destroyStudent() re-checks both server-side and refuses with a message naming what blocked it; this modal just states the rule up front so an admin isn't surprised by the refusal. --}} @if ($role === 'admin') @endif
{{ mb_strtoupper(mb_substr($studentInfo->last_name, 0, 1).mb_substr($studentInfo->first_name, 0, 1)) }}

{{ trim($studentInfo->last_name.' '.$studentInfo->first_name.' '.($studentInfo->middle_name ?? '')) }}

@if (!empty($studentInfo->status_name)) {{ $studentInfo->status_name }} @endif
Фамилия
{{ $studentInfo->last_name ?: '—' }}
Имя
{{ $studentInfo->first_name ?: '—' }}
Класс
{{ $studentInfo->class_name ?? '—' }}
Язык обучения
{{ $studentInfo->language_name ?? '—' }}
Формат обучения
{{ $studentInfo->learning_type_name ?? '—' }}
Цель
Статус
{{ $studentInfo->status_name ?? '—' }}
Кол-во договоров
Скидка за лояльность
{{ $studentInfo->discount_percent > 0 ? $studentInfo->discount_percent.'%' : 'Без скидки' }}
{{-- SECTION 2 — Contacts --}}
Контакты @if (in_array($role, ['admin'], true)) @endif
Телефон студента
{{ $studentInfo->phone ?? '—' }}
@if (\App\Support\Modules::enabled('student_iin'))
ИИН
{{ $studentInfo->iin ?? '—' }}
@endif @if (\App\Support\Modules::enabled('student_agreement_number'))
Номер договора
{{ $studentInfo->agreement_number ?? '—' }}
@endif
Цель
{!! nl2br(e($studentInfo->goal ?? '—')) !!}
Фамилия родителя
{{ $studentInfo->agent_last_name ?? '—' }}
Имя родителя
{{ $studentInfo->agent_first_name ?? '—' }}
Телефон родителя
{{ $studentInfo->agent_phone_number ?? '—' }}
@if (in_array($role, ['admin'], true))
Куратор
{{ trim($responsibles->curator_fullname ?? '—') }}
Менеджер
{{ trim($responsibles->manager_fullname ?? '—') }}
@endif
{{-- SECTION 3 — Indicators --}}
Показатели
Дата добавления
{{ $studentInfo->created_at ? date('d.m.Y', strtotime($studentInfo->created_at)) : '—' }}
Последний урок
{{ $lastLessonDate ? date('d.m.Y', strtotime($lastLessonDate)) : '—' }}
Часов в {{ config('app.name') }}
{{ $hoursInAxioma }}
Посещаемость
{{ $attendancePct }}%
Опозданий
{{ $latenessCount }}
Пропусков
{{ $absences }}
Средний балл КР/ДЗ
{{ $avgKrDz ?? '—' }}
Средний балл тестов
{{ $avgTestScore ?? '—' }}
{{-- SECTION 4 — Finance & Gamification (admin only) --}} @if (in_array($role, ['admin'], true)) @php $totalPaid = (float) ($balance->total_paid ?? 0); $totalCharged = (float) ($balance->total_charged ?? 0); $net = $totalPaid - $totalCharged; if ($net >= 0) { $payStatusLabel = 'Оплачено'; $payStatusClass = 'success'; } elseif ($net > -5000) { $payStatusLabel = 'Частично'; $payStatusClass = 'warning'; } else { $payStatusLabel = 'Долг'; $payStatusClass = 'danger'; } @endphp
Финансы и геймификация
Финансы
Оплачено (всё время)
{{ number_format($totalPaid, 0, ',', ' ') }} ₸
Долг за год
{{ number_format(abs($annualDebt), 0, ',', ' ') }} ₸
Статус оплаты
{{ $payStatusLabel }}

Групповой тариф
@if (!empty($studentInfo->general_tariff_name))
{{ preg_replace('/^\[manual\]\s*/', '', $studentInfo->general_tariff_name) }}  ·  {{ number_format((float) $studentInfo->general_tariff_price, 0, ',', ' ') }} ₸ / {{ (int) $studentInfo->general_tariff_lessons_count }} уроков @if ((int) $studentInfo->general_tariff_lessons_count > 0) ({{ number_format((float) $studentInfo->general_tariff_price / (int) $studentInfo->general_tariff_lessons_count, 0, ',', ' ') }} ₸/ур.) @endif
@else Не назначен @endif @if ($role === 'admin') @endif
Индивидуальный тариф
@if (!empty($studentInfo->individual_tariff_name))
{{ preg_replace('/^\[manual\]\s*/', '', $studentInfo->individual_tariff_name) }}  ·  {{ number_format((float) $studentInfo->individual_tariff_price, 0, ',', ' ') }} ₸ / {{ (int) $studentInfo->individual_tariff_lessons_count }} уроков @if ((int) $studentInfo->individual_tariff_lessons_count > 0) ({{ number_format((float) $studentInfo->individual_tariff_price / (int) $studentInfo->individual_tariff_lessons_count, 0, ',', ' ') }} ₸/ур.) @endif
@else Не назначен @endif @if ($role === 'admin') @endif

Жалобы и похвалы
Похвалы
{{ $praiseCount }}
Жалобы
{{ $complaintCount }}

Геймификация
Имя героя
Опыт (XP)
Монеты

Геймификация пока не настроена для этого студента.

@endif {{-- GROUPS --}}
Группы студента
@if (in_array($role, ['admin'], true)) @endif
@if (!empty($groupInfo['active']) || !empty($groupInfo['inactive'])) @foreach ($groupInfo['active'] as $group) @include('students._group_card', ['group' => $group]) @endforeach @if (!empty($groupInfo['inactive']))
@foreach ($groupInfo['inactive'] as $group) @include('students._group_card', ['group' => $group]) @endforeach
@endif @else
Студент не состоит ни в одной группе.
@endif {{-- Weekly test results --}} @if (in_array($role, ['admin', 'adviser'], true))
Результаты еженедельных тестов
@if (!empty($weeklyResults))
@foreach ($weeklyResults as $res) @php $scoreText = $res->score !== null ? "{$res->score}/{$res->max_score}" : '—'; $passed = $res->score !== null && $res->score >= ($res->max_score / 2); $bgColor = $passed ? '#28a745' : '#dc3545'; $tooltip = 'Дата: '.date('d.m.Y', strtotime($res->lesson_date ?? '')). "\nУчитель: ".($res->teacher_name ?? '—'). "\nРезультат: ".$scoreText; @endphp
{{ $scoreText }}
@endforeach
@else

Нет результатов тестов.

@endif
@endif {{-- ЕНТ results: null when the viewer isn't admin/adviser or the instance doesn't enter ЕНТ scores (MODULE_ADMIN_ENT_SCORES) — see show(). Same numbers and formatting as /teacher/ent_results.php, for one student. --}} @if ($entResults !== null) @php $entFmt = fn ($value) => rtrim(rtrim(number_format((float) $value, 1, '.', ' '), '0'), '.'); $entSigned = fn ($value) => ($value >= 0 ? '+' : '−') . $entFmt(abs($value)); $entTone = fn ($value) => $value === null ? 'ax-metric-value--dummy' : ($value >= 0 ? 'text-success' : 'text-danger'); @endphp
Результаты ЕНТ
@if (empty($entResults['attempts']))

Нет результатов ЕНТ.

@else
Попыток
{{ count($entResults['attempts']) }}
Последний
{{ $entFmt($entResults['latest']['total']) }}
{{ date('d.m.Y', strtotime($entResults['latest']['date'])) }}
Δ к предыдущему
{{ $entResults['delta'] === null ? '—' : $entSigned($entResults['delta']) }}
С первой попытки
{{ $entResults['total_delta'] === null ? '—' : $entSigned($entResults['total_delta']) }}
Ср. прогресс
{{ $entResults['avg_delta'] === null ? '—' : $entSigned($entResults['avg_delta']) }}
Лучший
{{ $entFmt($entResults['best']) }}
@foreach ($entResults['attempts'] as $attempt) @endforeach
Дата Итого Δ По предметам Заметка
{{ date('d.m.Y', strtotime($attempt['date'])) }} {{ $entFmt($attempt['total']) }} @if ($attempt['max_total'] > 0) / {{ $entFmt($attempt['max_total']) }} @endif @if ($attempt['delta'] === null) @else {{ $entSigned($attempt['delta']) }} @endif @forelse ($attempt['sections'] as $section) {{ $section['name'] }}: {{ $entFmt($section['score']) }}/{{ $entFmt($section['max_score']) }} @empty {{-- Rows entered before ent_scores.section_id existed have no subject. --}} без разбивки по предметам @endforelse {{ $attempt['note'] ?: '—' }}
@endif
@endif {{-- Frozen days --}} @if (in_array($role, ['admin', 'adviser'], true))
Замороженные дни
@if ($frozenDays)
@foreach ($frozenDays as $freeze) @endforeach
СПоПричинаДействие
{{ $freeze->date_from }} {{ $freeze->date_to }} {!! nl2br(e($freeze->reason)) !!}
@csrf
@else

Нет замороженных дней.

@endif
Добавить заморозку
@csrf
@endif {{-- Status history --}} @if (in_array($role, ['admin', 'adviser'], true))
История статусов
@if ($statusHistory)
@foreach ($statusHistory as $change) @endforeach
Дата и времяБылоСталоПричинаКто изменил
{{ date('d.m.Y H:i', strtotime($change->created_at)) }} @if ($change->old_status_name) {{ $change->old_status_name }} @else @endif @if ($change->new_status_name) {{ $change->new_status_name }} @else @endif {{ $change->reason_name ?? '—' }} {{ $change->changed_by_name ?? '—' }}
@else

Изменений статуса ещё не было.

@endif
@endif Вернуться на панель @endif {{-- end $studentInfo check --}}
{{-- MODALS --}} @if (\App\Support\Modules::enabled('student_password')) @endif @if (in_array($role, ['admin'], true)) @endif @if (in_array($role, ['admin'], true)) @endif @if (in_array($role, ['admin'], true)) @endif @if (in_array($role, ['admin'], true)) @endif @if (in_array($role, ['admin'], true)) @endif @endsection @push('scripts') @endpush