Replace Slot ID column in lessons list with the lesson's date/time
CI / Tests (PHP 8.2) (pull_request) Successful in 1m20s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m21s
CI / Coding Standards (pull_request) Successful in 1m45s
CI / PHPStan (pull_request) Successful in 3m22s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.2) (pull_request) Successful in 1m20s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m21s
CI / Coding Standards (pull_request) Successful in 1m45s
CI / PHPStan (pull_request) Successful in 3m22s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Has been skipped
The admin dashboard and instructor My Lessons pages showed the raw availability-slot database ID, which is meaningless to admins and instructors. LessonController now takes AvailabilityRepository, looks up each lesson's slot, and renders its window as e.g. "Jul 6, 2026 9:00 AM-10:00 AM" via mysql2date. The date is repeated on the end time only when a slot crosses midnight, and lessons whose slot row no longer exists show an em dash. Closes #47 Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
+1
-1
@@ -48,7 +48,7 @@ class AdminMenu {
|
||||
|
||||
public function __construct( AvailabilityRepository $availability, BookingRepository $bookings, OfferingRepository $offerings, QuestionRepository $questions, PolicyRepository $policies, PolicyVersionRepository $policyVersions, PolicyService $policyService, InviteRepository $invites, EnrollmentRepository $enrollments, StudioSettings $settings, PaymentRepository $payments, PaymentService $paymentService, BillingMethodResolver $resolver ) {
|
||||
$this->availabilityController = new AvailabilityController( $availability, $offerings );
|
||||
$this->lessonController = new LessonController( $bookings, $payments );
|
||||
$this->lessonController = new LessonController( $bookings, $payments, $availability );
|
||||
$this->offeringController = new OfferingController( $offerings );
|
||||
$this->questionController = new QuestionController( $questions, $offerings );
|
||||
$this->policyController = new PolicyController( $policies, $policyVersions, $policyService );
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
namespace Unsupervised\Schedular\Booking;
|
||||
|
||||
use Unsupervised\Schedular\Auth\RoleManager;
|
||||
use Unsupervised\Schedular\Availability\AvailabilityRepository;
|
||||
use Unsupervised\Schedular\Availability\AvailabilitySlot;
|
||||
use Unsupervised\Schedular\Payment\Payment;
|
||||
use Unsupervised\Schedular\Payment\PaymentRepository;
|
||||
use Unsupervised\Schedular\Val;
|
||||
@@ -13,6 +15,7 @@ class LessonController {
|
||||
public function __construct(
|
||||
private BookingRepository $repository,
|
||||
private PaymentRepository $payments,
|
||||
private AvailabilityRepository $availability,
|
||||
) {}
|
||||
|
||||
public function renderAdminDashboard(): void {
|
||||
@@ -83,11 +86,12 @@ class LessonController {
|
||||
$student = get_userdata( $lesson->studentId );
|
||||
$instructor = get_userdata( $lesson->instructorId );
|
||||
$payment = null !== $lesson->paymentId ? $this->payments->findById( $lesson->paymentId ) : null;
|
||||
$slot = $this->availability->findById( $lesson->slotId );
|
||||
|
||||
return [
|
||||
'student' => $student ? $student->display_name : (string) $lesson->studentId,
|
||||
'instructor' => $instructor ? $instructor->display_name : (string) $lesson->instructorId,
|
||||
'slot_id' => (int) $lesson->slotId,
|
||||
'time' => $slot ? $this->formatSlotTime( $slot ) : '—',
|
||||
'status' => $lesson->status,
|
||||
'notes' => $lesson->notes ?? '',
|
||||
'payment_id' => $payment ? (int) $payment->id : 0,
|
||||
@@ -101,4 +105,16 @@ class LessonController {
|
||||
'tax_editable' => null !== $payment && ! $payment->isPaid(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a slot's window as e.g. "Jul 6, 2026 9:00 AM–10:00 AM", repeating the
|
||||
* date on the end time only when the slot crosses midnight.
|
||||
*/
|
||||
private function formatSlotTime( AvailabilitySlot $slot ): string {
|
||||
$sameDay = substr( $slot->startDt, 0, 10 ) === substr( $slot->endDt, 0, 10 );
|
||||
|
||||
return Val::string( mysql2date( 'M j, Y g:i A', $slot->startDt ) )
|
||||
. '–'
|
||||
. Val::string( mysql2date( $sameDay ? 'g:i A' : 'M j, Y g:i A', $slot->endDt ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/** @var list<array{student: string, instructor: string, slot_id: int, status: string, notes: string, payment_id: int, currency: string, amount: float, tax_rate: float, tax_amount: float, total: float, etransfer_email: string, etransfer_editable: bool, tax_editable: bool}> $rows */
|
||||
/** @var list<array{student: string, instructor: string, time: string, status: string, notes: string, payment_id: int, currency: string, amount: float, tax_rate: float, tax_amount: float, total: float, etransfer_email: string, etransfer_editable: bool, tax_editable: bool}> $rows */
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e('Lessons', 'unsupervised-schedular'); ?></h1>
|
||||
@@ -18,7 +18,7 @@ if (! defined('ABSPATH')) {
|
||||
<tr>
|
||||
<th><?php esc_html_e('Student', 'unsupervised-schedular'); ?></th>
|
||||
<th><?php esc_html_e('Instructor', 'unsupervised-schedular'); ?></th>
|
||||
<th><?php esc_html_e('Slot ID', 'unsupervised-schedular'); ?></th>
|
||||
<th><?php esc_html_e('Date/Time', 'unsupervised-schedular'); ?></th>
|
||||
<th><?php esc_html_e('Status', 'unsupervised-schedular'); ?></th>
|
||||
<th><?php esc_html_e('HST', 'unsupervised-schedular'); ?></th>
|
||||
<th><?php esc_html_e('Total', 'unsupervised-schedular'); ?></th>
|
||||
@@ -31,7 +31,7 @@ if (! defined('ABSPATH')) {
|
||||
<tr>
|
||||
<td><?php echo esc_html($row['student']); ?></td>
|
||||
<td><?php echo esc_html($row['instructor']); ?></td>
|
||||
<td><?php echo esc_html((string) $row['slot_id']); ?></td>
|
||||
<td><?php echo esc_html($row['time']); ?></td>
|
||||
<td><?php echo esc_html($row['status']); ?></td>
|
||||
<td>
|
||||
<?php if ($row['tax_editable']) : ?>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Unsupervised\Schedular\Tests\Unit\Booking;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use Mockery;
|
||||
use Unsupervised\Schedular\Availability\AvailabilityRepository;
|
||||
use Unsupervised\Schedular\Availability\AvailabilitySlot;
|
||||
use Unsupervised\Schedular\Booking\BookingRepository;
|
||||
use Unsupervised\Schedular\Booking\Lesson;
|
||||
use Unsupervised\Schedular\Booking\LessonController;
|
||||
use Unsupervised\Schedular\Payment\PaymentRepository;
|
||||
use Unsupervised\Schedular\Tests\Unit\TestCase;
|
||||
|
||||
class LessonControllerTest extends TestCase
|
||||
{
|
||||
private BookingRepository&Mockery\MockInterface $bookings;
|
||||
private PaymentRepository&Mockery\MockInterface $payments;
|
||||
private AvailabilityRepository&Mockery\MockInterface $availability;
|
||||
private LessonController $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->bookings = Mockery::mock(BookingRepository::class);
|
||||
$this->payments = Mockery::mock(PaymentRepository::class);
|
||||
$this->availability = Mockery::mock(AvailabilityRepository::class);
|
||||
$this->controller = new LessonController($this->bookings, $this->payments, $this->availability);
|
||||
|
||||
$_POST = [];
|
||||
|
||||
Functions\when('current_user_can')->justReturn(true);
|
||||
Functions\when('get_userdata')->justReturn(false);
|
||||
Functions\when('mysql2date')->alias(
|
||||
static fn (string $format, string $date) => date($format, (int) strtotime($date))
|
||||
);
|
||||
}
|
||||
|
||||
public function testAdminDashboardShowsSlotDateTimeInsteadOfSlotId(): void
|
||||
{
|
||||
$lesson = new Lesson(slotId: 10, studentId: 5, instructorId: 3, id: 1);
|
||||
$slot = new AvailabilitySlot(
|
||||
instructorId: 3,
|
||||
startDt: '2026-07-06 09:00:00',
|
||||
endDt: '2026-07-06 10:00:00',
|
||||
id: 10
|
||||
);
|
||||
|
||||
$this->bookings->shouldReceive('findAllUpcoming')->once()->andReturn([$lesson]);
|
||||
$this->availability->shouldReceive('findById')->once()->with(10)->andReturn($slot);
|
||||
|
||||
$html = $this->render();
|
||||
|
||||
self::assertStringContainsString('Jul 6, 2026 9:00 AM–10:00 AM', $html);
|
||||
self::assertStringContainsString('Date/Time', $html);
|
||||
self::assertStringNotContainsString('Slot ID', $html);
|
||||
}
|
||||
|
||||
public function testSlotCrossingMidnightRepeatsTheDateOnTheEndTime(): void
|
||||
{
|
||||
$lesson = new Lesson(slotId: 11, studentId: 5, instructorId: 3, id: 2);
|
||||
$slot = new AvailabilitySlot(
|
||||
instructorId: 3,
|
||||
startDt: '2026-07-06 23:00:00',
|
||||
endDt: '2026-07-07 00:30:00',
|
||||
id: 11
|
||||
);
|
||||
|
||||
$this->bookings->shouldReceive('findAllUpcoming')->once()->andReturn([$lesson]);
|
||||
$this->availability->shouldReceive('findById')->once()->with(11)->andReturn($slot);
|
||||
|
||||
$html = $this->render();
|
||||
|
||||
self::assertStringContainsString('Jul 6, 2026 11:00 PM–Jul 7, 2026 12:30 AM', $html);
|
||||
}
|
||||
|
||||
public function testMissingSlotRendersDash(): void
|
||||
{
|
||||
$lesson = new Lesson(slotId: 99, studentId: 5, instructorId: 3, id: 3);
|
||||
|
||||
$this->bookings->shouldReceive('findAllUpcoming')->once()->andReturn([$lesson]);
|
||||
$this->availability->shouldReceive('findById')->once()->with(99)->andReturn(null);
|
||||
|
||||
$html = $this->render();
|
||||
|
||||
self::assertStringContainsString('—', $html);
|
||||
}
|
||||
|
||||
public function testInstructorLessonsShowSlotDateTime(): void
|
||||
{
|
||||
Functions\when('get_current_user_id')->justReturn(3);
|
||||
|
||||
$lesson = new Lesson(slotId: 12, studentId: 5, instructorId: 3, id: 4);
|
||||
$slot = new AvailabilitySlot(
|
||||
instructorId: 3,
|
||||
startDt: '2026-08-01 14:00:00',
|
||||
endDt: '2026-08-01 15:00:00',
|
||||
id: 12
|
||||
);
|
||||
|
||||
$this->bookings->shouldReceive('findUpcomingForInstructor')->once()->with(3)->andReturn([$lesson]);
|
||||
$this->availability->shouldReceive('findById')->once()->with(12)->andReturn($slot);
|
||||
|
||||
ob_start();
|
||||
$this->controller->renderInstructorLessons();
|
||||
$html = (string) ob_get_clean();
|
||||
|
||||
self::assertStringContainsString('Aug 1, 2026 2:00 PM–3:00 PM', $html);
|
||||
}
|
||||
|
||||
private function render(): string
|
||||
{
|
||||
ob_start();
|
||||
$this->controller->renderAdminDashboard();
|
||||
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user