Show booked lesson info on upcoming lists and add admin booking detail
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Coding Standards (pull_request) Successful in 2m53s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 3s
CI / PHPStan (pull_request) Successful in 3m12s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Skipped

Front end: the student "upcoming lessons" panel now shows each booked
offering's name and length next to the time, and renders only the soonest
five lessons with a "Show all" reveal. GET /bookings returns offering_title
and duration_minutes so the list needs no extra request.

Admin: the Scheduler and My Lessons week/list views now show the booked
offering, and each lesson links to a detail view showing the policy versions
the student accepted (with acceptance time and IP) and their intake answers.
On My Lessons an instructor may only open their own lessons; the studio
Scheduler may open any.

composer test / composer lint / composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-07-24 11:07:06 -03:00
co-authored by Claude Opus 4.8
parent 991ed2f5ad
commit 32619a1b75
12 changed files with 569 additions and 21 deletions
+73
View File
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Unsupervised\Schedular\Booking;
use Unsupervised\Schedular\Policy\AcceptanceRepository;
use Unsupervised\Schedular\Policy\PolicyAcceptance;
use Unsupervised\Schedular\Policy\PolicyRepository;
use Unsupervised\Schedular\Policy\PolicyVersionRepository;
use Unsupervised\Schedular\Registration\Answer;
use Unsupervised\Schedular\Registration\AnswerRepository;
use Unsupervised\Schedular\Registration\QuestionRepository;
/**
* Builds the display rows for the admin lesson detail view: the intake answers
* the student submitted and the policy versions they accepted when booking.
*
* Scoped to a single lesson (the `lesson` registration type), mirroring the
* per-student history in {@see \Unsupervised\Schedular\Auth\StudentHistory}.
*/
class LessonDetail {
public function __construct(
private AnswerRepository $answers,
private QuestionRepository $questions,
private AcceptanceRepository $acceptances,
private PolicyRepository $policies,
private PolicyVersionRepository $versions,
) {}
/**
* The intake-question answers recorded for this lesson, in submission order.
*
* @return list<array{question: string, answer: string}>
*/
public function answers( int $lessonId ): array {
return array_map(
function ( Answer $answer ): array {
$question = $this->questions->findById( $answer->questionId );
$value = $answer->answerValue ?? '';
return [
'question' => $question ? $question->label : sprintf( '#%d', $answer->questionId ),
'answer' => '' === $value ? '—' : $value,
];
},
$this->answers->findByRegistration( Answer::REG_LESSON, $lessonId )
);
}
/**
* The policy versions the student accepted when booking this lesson, with the
* captured acceptance time and IP for the audit trail.
*
* @return list<array{policy: string, version: string, accepted_at: string, ip: string}>
*/
public function acceptances( int $lessonId ): array {
return array_map(
function ( PolicyAcceptance $acceptance ): array {
$version = $this->versions->findById( $acceptance->policyVersionId );
$policy = $version ? $this->policies->findById( $version->policyId ) : null;
return [
'policy' => $policy ? $policy->title : sprintf( '#%d', $acceptance->policyVersionId ),
'version' => $version ? sprintf( 'v%d', $version->versionNumber ) : '—',
'accepted_at' => $acceptance->acceptedAt ?? '',
'ip' => $acceptance->ipAddress ?? '',
];
},
$this->acceptances->findByRegistration( PolicyAcceptance::REG_LESSON, $lessonId )
);
}
}