Replace the placeholder row list on the patient detail «نوبتها» tab with the
card-grid design ported pixel-for-pixel from clinic-pro-tauri TurnsSection:
- New AppointmentTurnCard mirrors tauri TurnsCard (success icon, title, date/time
chips, personnel, status). Status uses the live AppointmentStatusDropdown
instead of the tauri mock.
- New AppointmentsTab in PatientDetailPage: sort/filter toolbar (client-side) +
reserve/new buttons linking to existing /admin/appointments pages + card grid.
- Add CalendarD/ClockP/UserD/StatusGlobe icons (verbatim from tauri).
- Backend: expose version on GET /patient/{uuid}/appointments so the status
dropdown can optimistic-lock. No new endpoint.
- Tests: PatientAppointmentsTest (shape/version/order/empty/ownership) +
AppointmentTurnCard + tab data/empty cases. docs/api/patient.md updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
3.5 KiB
PHP
98 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Patient;
|
|
|
|
use App\Appointment\Entity\Appointment;
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Patient\Entity\PatientRecord;
|
|
use App\Tests\ApiTestCase;
|
|
|
|
/**
|
|
* List Patient Appointments (تب نوبتها): shape (incl. version), ordering,
|
|
* empty boundary, and ownership scoping.
|
|
*/
|
|
class PatientAppointmentsTest extends ApiTestCase
|
|
{
|
|
/** @return array{0: \App\Auth\Entity\User, 1: Doctor, 2: PatientRecord} */
|
|
private function recordFor(): array
|
|
{
|
|
$owner = $this->createUser(['ROLE_DOCTOR']);
|
|
$doctor = new Doctor($owner, 'دکتر ژیلا فتحی');
|
|
$this->em->persist($doctor);
|
|
$this->em->flush();
|
|
|
|
$patient = $this->createUser(['ROLE_USER']);
|
|
$record = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId());
|
|
$this->em->persist($record);
|
|
$this->em->flush();
|
|
|
|
return [$owner, $doctor, $record];
|
|
}
|
|
|
|
private function appointment(Doctor $doctor, PatientRecord $record, int $slotStart): Appointment
|
|
{
|
|
$appt = new Appointment($doctor, $record->getUser(), $slotStart, $slotStart + 1800);
|
|
$this->em->persist($appt);
|
|
$this->em->flush();
|
|
|
|
return $appt;
|
|
}
|
|
|
|
public function testListReturnsShapeWithVersion(): void
|
|
{
|
|
[$owner, $doctor, $record] = $this->recordFor();
|
|
$this->appointment($doctor, $record, 1_754_000_000);
|
|
|
|
$list = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/appointments', $owner);
|
|
self::assertSame(200, $this->responseCode());
|
|
self::assertCount(1, $list['data']);
|
|
|
|
$row = $list['data'][0];
|
|
foreach (['uuid', 'starts_at', 'ends_at', 'status', 'version', 'doctor_name'] as $key) {
|
|
self::assertArrayHasKey($key, $row);
|
|
}
|
|
self::assertSame(1, $row['version']);
|
|
self::assertSame('pending', $row['status']);
|
|
self::assertSame('دکتر ژیلا فتحی', $row['doctor_name']);
|
|
self::assertSame(1_754_000_000, $row['starts_at']);
|
|
}
|
|
|
|
public function testSortedByStartDescending(): void
|
|
{
|
|
[$owner, $doctor, $record] = $this->recordFor();
|
|
$this->appointment($doctor, $record, 1_754_000_000);
|
|
$this->appointment($doctor, $record, 1_755_000_000);
|
|
|
|
$list = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/appointments', $owner);
|
|
self::assertCount(2, $list['data']);
|
|
self::assertSame(1_755_000_000, $list['data'][0]['starts_at']);
|
|
self::assertSame(1_754_000_000, $list['data'][1]['starts_at']);
|
|
}
|
|
|
|
public function testEmptyWhenNoAppointments(): void
|
|
{
|
|
[$owner, , $record] = $this->recordFor();
|
|
|
|
$list = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/appointments', $owner);
|
|
self::assertSame(200, $this->responseCode());
|
|
self::assertCount(0, $list['data']);
|
|
}
|
|
|
|
public function testNotFoundForUnknownRecord(): void
|
|
{
|
|
[$owner] = $this->recordFor();
|
|
$this->authJson('GET', '/api/v1/patient/00000000-0000-0000-0000-000000000000/appointments', $owner);
|
|
self::assertSame(404, $this->responseCode());
|
|
}
|
|
|
|
public function testOwnershipScoped(): void
|
|
{
|
|
[, $doctor, $record] = $this->recordFor();
|
|
$this->appointment($doctor, $record, 1_754_000_000);
|
|
|
|
[$other] = $this->recordFor();
|
|
$this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/appointments', $other);
|
|
self::assertSame(404, $this->responseCode());
|
|
}
|
|
}
|