feat: port clinic dashboard components from clinic-pro-tauri
- Add NewAppointmentsTable for displaying today's appointments with status chips and formatted time. - Implement TauriCharts for bar and line charts representing patient counts and revenue. - Create TauriDashboardView to combine stat cards, charts, and new appointments list. - Introduce TauriStatCards for displaying key statistics with icons. - Add dashboardIcons for SVG icons used in stat cards. - Implement tests for DashboardPage to ensure correct rendering and API calls. - Create DashboardTodayAppointmentsTest to validate extended fields in today's appointments API response.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Dashboard;
|
||||
|
||||
use App\Appointment\Entity\Appointment;
|
||||
use App\Auth\Entity\User;
|
||||
use App\Clinic\Entity\Clinic;
|
||||
use App\ClinicService\Entity\ServiceItem;
|
||||
use App\ClinicService\Entity\ServiceSection;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* GET /api/v1/dashboard/{doctor,clinic} — `today_appointments` rows must carry
|
||||
* the extended fields (patient_mobile, doctor_name, service_name, slot_end)
|
||||
* added for the ported «لیست نوبتهای جدید» dashboard table.
|
||||
*
|
||||
* NOTE: db_test is never reset, so patient mobiles use the ApiTestCase random
|
||||
* generator (never hardcoded) to avoid unique-constraint clashes across runs.
|
||||
*/
|
||||
class DashboardTodayAppointmentsTest extends ApiTestCase
|
||||
{
|
||||
private function todayAppointment(Doctor $doctor, User $patient): Appointment
|
||||
{
|
||||
// now() is guaranteed within [today midnight, tomorrow midnight-1]
|
||||
$start = time();
|
||||
$appt = new Appointment($doctor, $patient, $start, $start + 1_800);
|
||||
$this->em->persist($appt);
|
||||
$this->em->flush();
|
||||
|
||||
return $appt;
|
||||
}
|
||||
|
||||
public function testDoctorTodayAppointmentsExposeExtendedFields(): void
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'حمیدی');
|
||||
$patient = $this->createUser(['ROLE_USER']);
|
||||
|
||||
$section = new ServiceSection('doctor', 1, 'عمومی');
|
||||
$item = new ServiceItem($section, 'ویزیت عمومی');
|
||||
$this->em->persist($doctor);
|
||||
$this->em->persist($section);
|
||||
$this->em->persist($item);
|
||||
$this->em->flush();
|
||||
|
||||
$appt = $this->todayAppointment($doctor, $patient);
|
||||
$appt->setServiceItem($item);
|
||||
$this->em->flush();
|
||||
|
||||
$res = $this->authJson('GET', '/api/v1/dashboard/doctor', $owner);
|
||||
self::assertSame(200, $this->responseCode());
|
||||
|
||||
$rows = $res['data']['today_appointments'];
|
||||
self::assertNotEmpty($rows, 'today_appointments should contain the booked slot');
|
||||
$row = $rows[0];
|
||||
|
||||
self::assertSame($patient->getMobileNumber(), $row['patient_mobile']);
|
||||
self::assertSame('حمیدی', $row['doctor_name']);
|
||||
self::assertSame('ویزیت عمومی', $row['service_name']);
|
||||
self::assertSame($appt->getSlotStart(), $row['slot_start']);
|
||||
self::assertSame($appt->getSlotEnd(), $row['slot_end']);
|
||||
self::assertArrayHasKey('status', $row);
|
||||
}
|
||||
|
||||
public function testServiceNameNullWhenNoServiceItem(): void
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'حمیدی');
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$patient = $this->createUser(['ROLE_USER']);
|
||||
$this->todayAppointment($doctor, $patient);
|
||||
|
||||
$res = $this->authJson('GET', '/api/v1/dashboard/doctor', $owner);
|
||||
self::assertSame(200, $this->responseCode());
|
||||
$row = $res['data']['today_appointments'][0];
|
||||
|
||||
self::assertNull($row['service_name'], 'service_name is null when no service item is booked');
|
||||
self::assertSame($patient->getMobileNumber(), $row['patient_mobile']);
|
||||
}
|
||||
|
||||
public function testClinicTodayAppointmentsExposeExtendedFields(): void
|
||||
{
|
||||
$clinicOwner = $this->createUser(['ROLE_CLINIC']);
|
||||
$clinic = new Clinic($clinicOwner);
|
||||
$clinic->setName('کلینیک نمونه');
|
||||
|
||||
$docOwner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($docOwner, 'حمیدی');
|
||||
$clinic->getDoctors()->add($doctor);
|
||||
|
||||
$section = new ServiceSection('doctor', 1, 'عمومی');
|
||||
$item = new ServiceItem($section, 'ویزیت عمومی');
|
||||
$patient = $this->createUser(['ROLE_USER']);
|
||||
|
||||
$this->em->persist($doctor);
|
||||
$this->em->persist($clinic);
|
||||
$this->em->persist($section);
|
||||
$this->em->persist($item);
|
||||
$this->em->flush();
|
||||
|
||||
$appt = $this->todayAppointment($doctor, $patient);
|
||||
$appt->setServiceItem($item);
|
||||
$this->em->flush();
|
||||
|
||||
$res = $this->authJson('GET', '/api/v1/dashboard/clinic', $clinicOwner);
|
||||
self::assertSame(200, $this->responseCode());
|
||||
|
||||
$rows = $res['data']['today_appointments'];
|
||||
self::assertNotEmpty($rows);
|
||||
$row = $rows[0];
|
||||
|
||||
self::assertSame($patient->getMobileNumber(), $row['patient_mobile']);
|
||||
self::assertSame('حمیدی', $row['doctor_name']);
|
||||
self::assertSame('ویزیت عمومی', $row['service_name']);
|
||||
self::assertSame($appt->getSlotEnd(), $row['slot_end']);
|
||||
}
|
||||
|
||||
public function testDoctorNotFoundReturns404(): void
|
||||
{
|
||||
// ROLE_DOCTOR user without a Doctor entity
|
||||
$user = $this->createUser(['ROLE_DOCTOR']);
|
||||
|
||||
$res = $this->authJson('GET', '/api/v1/dashboard/doctor', $user);
|
||||
self::assertSame(404, $this->responseCode());
|
||||
self::assertFalse($res['success']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user