diff --git a/docs/features/group-classes.md b/docs/features/group-classes.md index e1365ba..95ff985 100644 --- a/docs/features/group-classes.md +++ b/docs/features/group-classes.md @@ -54,13 +54,15 @@ payment step). instructor's group classes if the caller has `view_own_lessons` on those offerings. ## Admin Interface -- **Group Classes** (`manage_options` / studio admin): all enrolments across instructors -- Instructors see enrolments for their own group classes under **My Lessons** +- **Group Classes** (`view_all_lessons` / studio admin): all active enrolments across instructors +- **My Lessons → My Group Classes** (`view_own_lessons` / instructor): the instructor's + own group classes, each showing its active-enrolment count against capacity and a + per-class roster of enrolled students with enrolment and payment status ## Implementation - Repository: `Unsupervised\Schedular\GroupClass\EnrollmentRepository` (`countActiveForOffering`/`hasActiveEnrollment` enforce capacity and prevent duplicates) - Model: `Unsupervised\Schedular\GroupClass\Enrollment` -- Admin controller: `Unsupervised\Schedular\GroupClass\GroupClassController` (gated on `view_all_lessons`) +- Admin controller: `Unsupervised\Schedular\GroupClass\GroupClassController` — `renderPage` (studio admin, `view_all_lessons`) and `renderInstructorPage` (instructor, `view_own_lessons`) - REST endpoint: `Unsupervised\Schedular\GroupClass\EnrollmentEndpoint` - Frontend: `Unsupervised\Schedular\GroupClass\GroupClassPage` (`[us_group_classes]` shortcode; `offering="…"` restricts it to a single class for embedding on a dedicated page — the block equivalent is the `offeringId` attribute) - Reuses `Registration\RegistrationGate` (intake answers + booking-scoped policy acceptance, type `enrollment`) @@ -68,12 +70,10 @@ instructor's group classes if the caller has `view_own_lessons` on those offerin > **Payment:** a priced enrolment creates a payment via `Payment\PaymentService` > (`registration_type = enrollment`) and links it as `payment_id`; unpriced > enrolments return `payment: null` and skip the payment step. See `payments.md` -> for the card/e-transfer/comp flows. Instructor-specific enrolment views (the -> spec's "under My Lessons") are a follow-up (#71) — this iteration ships the -> studio-admin **Group Classes** page (`view_all_lessons`) plus -> per-student/per-instructor REST queries. +> for the card/e-transfer/comp flows. ## Tests +- `tests/Unit/GroupClass/GroupClassControllerTest.php` - `tests/Unit/GroupClass/EnrollmentTest.php` - `tests/Unit/GroupClass/EnrollmentRepositoryTest.php` - `tests/Unit/GroupClass/GroupClassPageTest.php` diff --git a/src/AdminMenu.php b/src/AdminMenu.php index dbfcd76..6fb5e01 100644 --- a/src/AdminMenu.php +++ b/src/AdminMenu.php @@ -61,7 +61,7 @@ class AdminMenu { $this->policyController = new PolicyController( $policies, $policyVersions, $policyService ); $this->registrationController = new RegistrationController( $invites ); $this->registrationApprovalController = new RegistrationApprovalController( new RegistrationMailer() ); - $this->groupClassController = new GroupClassController( $enrollments, $offerings ); + $this->groupClassController = new GroupClassController( $enrollments, $offerings, $payments ); $this->studentController = new StudentController( $bookings, $availability, $offerings, $enrollments, $resolver, new StudentHistory( $acceptances, $policies, $policyVersions, $answers, $questions, $payments ), new StudentActions( $bookings, $availability, $enrollments, $paymentService ) ); $this->instructorController = new InstructorController(); $this->settings = $settings; @@ -247,6 +247,16 @@ class AdminMenu { 'dashicons-welcome-learn-more', 42 ); + + // Instructor: their own group classes with per-class rosters. + add_submenu_page( + 'us-my-lessons', + __( 'My Group Classes', 'unsupervised-schedular' ), + __( 'My Group Classes', 'unsupervised-schedular' ), + RoleManager::CAP_VIEW_LESSONS, + 'us-my-group-classes', + [ $this->groupClassController, 'renderInstructorPage' ] + ); } } diff --git a/src/GroupClass/GroupClassController.php b/src/GroupClass/GroupClassController.php index 25d10fd..00deca1 100644 --- a/src/GroupClass/GroupClassController.php +++ b/src/GroupClass/GroupClassController.php @@ -4,13 +4,16 @@ declare(strict_types=1); namespace Unsupervised\Schedular\GroupClass; use Unsupervised\Schedular\Auth\RoleManager; +use Unsupervised\Schedular\Offering\Offering; use Unsupervised\Schedular\Offering\OfferingRepository; +use Unsupervised\Schedular\Payment\PaymentRepository; class GroupClassController { public function __construct( private EnrollmentRepository $enrollments, private OfferingRepository $offerings, + private PaymentRepository $payments, ) {} public function renderPage(): void { @@ -34,4 +37,54 @@ class GroupClassController { include USC_PLUGIN_DIR . 'templates/admin/group-classes.php'; } + + /** + * Instructor view: their own group classes with per-class rosters. Each class + * shows its enrolment count against capacity plus a roster of enrolled + * students with enrolment and payment status. + */ + public function renderInstructorPage(): void { + if ( ! current_user_can( RoleManager::CAP_VIEW_LESSONS ) ) { + wp_die( esc_html__( 'You do not have permission to view group classes.', 'unsupervised-schedular' ) ); + } + + $instructorId = get_current_user_id(); + $enrollments = $this->enrollments->findByInstructor( $instructorId ); + + $classes = array_map( + function ( Offering $offering ) use ( $enrollments ): array { + $roster = []; + $enrolled = 0; + + foreach ( $enrollments as $enrollment ) { + if ( $enrollment->offeringId !== $offering->id ) { + continue; + } + + if ( Enrollment::STATUS_ACTIVE === $enrollment->status ) { + ++$enrolled; + } + + $student = get_userdata( $enrollment->studentId ); + $payment = null !== $enrollment->paymentId ? $this->payments->findById( $enrollment->paymentId ) : null; + + $roster[] = [ + 'student' => $student ? $student->display_name : (string) $enrollment->studentId, + 'status' => $enrollment->status, + 'payment' => $payment?->status, + ]; + } + + return [ + 'title' => $offering->title, + 'capacity' => $offering->capacity, + 'enrolled' => $enrolled, + 'roster' => $roster, + ]; + }, + $this->offerings->findAll( $instructorId, Offering::KIND_GROUP_CLASS ) + ); + + include USC_PLUGIN_DIR . 'templates/admin/my-group-classes.php'; + } } diff --git a/templates/admin/my-group-classes.php b/templates/admin/my-group-classes.php new file mode 100644 index 0000000..b0775e9 --- /dev/null +++ b/templates/admin/my-group-classes.php @@ -0,0 +1,64 @@ +}> $classes */ +?> +
| + | + | + |
|---|---|---|
| + | + | + |