();
appointments.forEach(a => apptByStart.set(a.slot_start, a));
@@ -484,7 +495,7 @@ export default function AppointmentsPage() {
return (
{/* Stats Bar */}
- {isAdmin && }
+
{/* Doctor tabs (clinic with ≥2 doctors) */}
{showDoctorTabs && (
diff --git a/src/Appointment/Controller/MyAppointmentsController.php b/src/Appointment/Controller/MyAppointmentsController.php
index 60a0e683..96bb5032 100644
--- a/src/Appointment/Controller/MyAppointmentsController.php
+++ b/src/Appointment/Controller/MyAppointmentsController.php
@@ -28,35 +28,35 @@ class MyAppointmentsController extends BaseController
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function myAppointments(Request $request, #[CurrentUser] User $user): JsonResponse
{
- $page = max(1, (int) $request->query->get('page', 1));
- $limit = min(100, max(1, (int) $request->query->get('limit', 15)));
- $search = trim((string) $request->query->get('search', ''));
- $status = trim((string) $request->query->get('status', ''));
- $date = trim((string) $request->query->get('date', ''));
+ $page = max(1, (int) $request->query->get('page', 1));
+ $limit = min(500, max(1, (int) $request->query->get('limit', 15)));
+ $search = trim((string) $request->query->get('search', ''));
+ $status = trim((string) $request->query->get('status', ''));
+ $date = trim((string) $request->query->get('date', ''));
+ $doctorUuid = trim((string) $request->query->get('doctor_uuid', ''));
$qb = $this->em->createQueryBuilder()
->select(
- 'DISTINCT a.uuid, a.slotStart, a.slotEnd, a.status, a.createdAt',
+ 'DISTINCT a.uuid, a.slotStart, a.slotEnd, a.status, a.createdAt, a.version',
'd.uuid as doctor_uuid, d.name as doctor_name',
- 'u.mobileNumber as patient_mobile, u.realName as patient_name',
- 'c.name as clinic_name'
+ 'u.mobileNumber as patient_mobile, u.realName as patient_name'
)
->from(Appointment::class, 'a')
->join('a.doctor', 'd')
->join('a.user', 'u')
- ->leftJoin('App\Clinic\Entity\Clinic', 'c', 'WITH', 'd MEMBER OF c.doctors')
- ->orderBy('a.slotStart', 'DESC');
+ ->orderBy('a.slotStart', 'ASC');
$roles = $user->getRoles();
if (in_array('ROLE_ADMIN', $roles, true)) {
- // Admin voit tout
+ // Admin sees all
} elseif (in_array('ROLE_CLINIC', $roles, true)) {
$clinic = $this->clinicRepo->findByUser($user);
if ($clinic === null) {
return $this->paginated([], 0, $page, $limit);
}
- $qb->andWhere('c = :clinic')
+ $qb->join('App\Clinic\Entity\Clinic', 'c', 'WITH', 'd MEMBER OF c.doctors')
+ ->andWhere('c = :clinic')
->setParameter('clinic', $clinic);
} elseif (in_array('ROLE_DOCTOR', $roles, true)) {
$doctor = $this->doctorRepo->findByUser($user);
@@ -87,14 +87,15 @@ class MyAppointmentsController extends BaseController
if ($status !== '') {
$qb->andWhere('a.status = :status')->setParameter('status', $status);
}
- if ($date !== '') {
- $dayStart = strtotime($date . ' 00:00:00');
- $dayEnd = strtotime($date . ' 23:59:59');
- if ($dayStart && $dayEnd) {
- $qb->andWhere('a.slotStart >= :dayStart AND a.slotStart <= :dayEnd')
- ->setParameter('dayStart', $dayStart)
- ->setParameter('dayEnd', $dayEnd);
- }
+ if ($date !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
+ $dayStart = (int) strtotime($date . ' 00:00:00');
+ $dayEnd = (int) strtotime($date . ' 23:59:59');
+ $qb->andWhere('a.slotStart >= :dayStart AND a.slotStart <= :dayEnd')
+ ->setParameter('dayStart', $dayStart)
+ ->setParameter('dayEnd', $dayEnd);
+ }
+ if ($doctorUuid !== '') {
+ $qb->andWhere('d.uuid = :doctorUuid')->setParameter('doctorUuid', $doctorUuid);
}
$total = (clone $qb)->select('COUNT(a.id)')->getQuery()->getSingleScalarResult();
@@ -106,16 +107,80 @@ class MyAppointmentsController extends BaseController
'uuid' => $a['uuid'],
'patient_name' => $a['patient_name'] ?? '',
'patient_mobile' => $a['patient_mobile'],
+ 'doctor_uuid' => $a['doctor_uuid'],
'doctor_name' => $a['doctor_name'],
- 'clinic_name' => $a['clinic_name'] ?? null,
+ 'slot_start' => (int) $a['slotStart'],
+ 'slot_end' => (int) $a['slotEnd'],
'appointment_date' => date('Y-m-d', (int) $a['slotStart']),
'appointment_time' => date('H:i', (int) $a['slotStart']),
- 'slot_start' => (int) $a['slotStart'],
+ 'end_time' => date('H:i', (int) $a['slotEnd']),
'status' => $a['status'],
- 'amount' => 0,
+ 'version' => (int) $a['version'],
'created_at' => date('c', (int) $a['createdAt']),
], $rows);
return $this->paginated($items, (int) $total, $page, $limit);
}
+
+ #[Route('/api/v1/my/appointments/today-stats', methods: ['GET'])]
+ #[IsGranted('IS_AUTHENTICATED_FULLY')]
+ public function todayStats(Request $request, #[CurrentUser] User $user): JsonResponse
+ {
+ $date = trim((string) $request->query->get('date', date('Y-m-d')));
+ if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
+ $date = date('Y-m-d');
+ }
+ $dayStart = (int) strtotime($date . ' 00:00:00');
+ $dayEnd = (int) strtotime($date . ' 23:59:59');
+
+ $qb = $this->em->createQueryBuilder()
+ ->select('a.status, COUNT(a.id) AS cnt')
+ ->from(Appointment::class, 'a')
+ ->join('a.doctor', 'd')
+ ->where('a.slotStart >= :dayStart AND a.slotStart <= :dayEnd')
+ ->setParameter('dayStart', $dayStart)
+ ->setParameter('dayEnd', $dayEnd)
+ ->groupBy('a.status');
+
+ $roles = $user->getRoles();
+ if (in_array('ROLE_CLINIC', $roles, true)) {
+ $clinic = $this->clinicRepo->findByUser($user);
+ if ($clinic) {
+ $qb->join('App\Clinic\Entity\Clinic', 'c', 'WITH', 'd MEMBER OF c.doctors')
+ ->andWhere('c = :clinic')
+ ->setParameter('clinic', $clinic);
+ }
+ } elseif (in_array('ROLE_DOCTOR', $roles, true)) {
+ $doctor = $this->doctorRepo->findByUser($user);
+ if ($doctor) {
+ $qb->andWhere('a.doctor = :doctor')->setParameter('doctor', $doctor);
+ }
+ } elseif (in_array('ROLE_SECRETARY', $roles, true)) {
+ $rel = $this->secretaryRepo->findActiveBySecretary($user);
+ if ($rel) {
+ $qb->andWhere('a.doctor = :doctor')->setParameter('doctor', $rel->getDoctor());
+ }
+ }
+
+ $rows = $qb->getQuery()->getArrayResult();
+ $byStatus = [];
+ foreach ($rows as $row) {
+ $byStatus[$row['status']] = (int) $row['cnt'];
+ }
+
+ $total = array_sum($byStatus);
+ $completed = ($byStatus['completed'] ?? 0);
+ $cancelled = ($byStatus['cancelled_by_doctor'] ?? 0)
+ + ($byStatus['cancelled_by_user'] ?? 0)
+ + ($byStatus['no_show'] ?? 0)
+ + ($byStatus['expired'] ?? 0);
+ $waiting = $total - $completed - $cancelled;
+
+ return $this->success([
+ 'total' => $total,
+ 'completed' => $completed,
+ 'waiting' => max(0, $waiting),
+ 'cancelled' => $cancelled,
+ ]);
+ }
}