feat(dashboard): doctor/clinic dashboard matching Figma layout
Restructure to Figma: 4 stat cards (today appts, today/week payments, total patients) -> two charts (revenue area + patient-count vertical bars) -> today appointments table. Add today/week payments, total patients, and 7-day revenue/appointments series to doctor + clinic dashboard endpoints. Add SvgVBars chart. Update dashboard.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,17 @@ class DashboardController extends BaseController
|
||||
$smsBalance = $this->smsWalletService->getBalance('clinic', $clinicId);
|
||||
$uniquePatients = $this->patientRecordRepo->countUnique('clinic', $clinicId, $from, $to);
|
||||
$revenuePeriod = $this->patientSessionRepo->sumRevenue('clinic', $clinicId, $from, $to);
|
||||
$totalPatients = $this->patientRecordRepo->countUnique('clinic', $clinicId, 0, time());
|
||||
|
||||
$rev = $this->revenueDaily('clinic', $clinicId);
|
||||
$apptByDay = $this->appointmentsDaily(function (int $ds, int $de) use ($clinicId): int {
|
||||
return (int) $this->em->createQuery('
|
||||
SELECT COUNT(a.id) FROM App\Appointment\Entity\Appointment a
|
||||
JOIN App\Doctor\Entity\Doctor d WITH a.doctor = d
|
||||
JOIN App\Clinic\Entity\Clinic c WITH d MEMBER OF c.doctors
|
||||
WHERE c.id = :clinicId AND a.slotStart >= :s AND a.slotStart <= :e
|
||||
')->setParameters(['clinicId' => $clinicId, 's' => $ds, 'e' => $de])->getSingleScalarResult();
|
||||
});
|
||||
|
||||
return $this->success([
|
||||
'clinic' => [
|
||||
@@ -147,7 +158,14 @@ class DashboardController extends BaseController
|
||||
'pending_invitations' => $pendingInvitations,
|
||||
'sms_wallet_balance' => $smsBalance,
|
||||
'unique_patients_count' => $uniquePatients,
|
||||
'total_patients' => $totalPatients,
|
||||
'revenue_period_rials' => $revenuePeriod,
|
||||
'today_payments_rials' => $rev['today_payments_rials'],
|
||||
'week_payments_rials' => $rev['week_payments_rials'],
|
||||
],
|
||||
'charts' => [
|
||||
'revenue_by_day' => $rev['revenue'],
|
||||
'appointments_by_day' => $apptByDay,
|
||||
],
|
||||
'period' => ['from' => $from, 'to' => $to],
|
||||
'today_appointments' => $todayAppts,
|
||||
@@ -227,6 +245,15 @@ class DashboardController extends BaseController
|
||||
$smsBalance = $this->smsWalletService->getBalance('doctor', $doctorId);
|
||||
$uniquePatients = $this->patientRecordRepo->countUnique('doctor', $doctorId, $from, $to);
|
||||
$revenuePeriod = $this->patientSessionRepo->sumRevenue('doctor', $doctorId, $from, $to);
|
||||
$totalPatients = $this->patientRecordRepo->countUnique('doctor', $doctorId, 0, time());
|
||||
|
||||
$rev = $this->revenueDaily('doctor', $doctorId);
|
||||
$apptByDay = $this->appointmentsDaily(function (int $ds, int $de) use ($doctor): int {
|
||||
return (int) $this->em->createQuery('
|
||||
SELECT COUNT(a.id) FROM App\Appointment\Entity\Appointment a
|
||||
WHERE a.doctor = :d AND a.slotStart >= :s AND a.slotStart <= :e
|
||||
')->setParameters(['d' => $doctor, 's' => $ds, 'e' => $de])->getSingleScalarResult();
|
||||
});
|
||||
|
||||
return $this->success([
|
||||
'doctor' => [
|
||||
@@ -242,7 +269,14 @@ class DashboardController extends BaseController
|
||||
'total_ratings' => (int) ($ratingRow['total'] ?? 0),
|
||||
'sms_wallet_balance' => $smsBalance,
|
||||
'unique_patients_count' => $uniquePatients,
|
||||
'total_patients' => $totalPatients,
|
||||
'revenue_period_rials' => $revenuePeriod,
|
||||
'today_payments_rials' => $rev['today_payments_rials'],
|
||||
'week_payments_rials' => $rev['week_payments_rials'],
|
||||
],
|
||||
'charts' => [
|
||||
'revenue_by_day' => $rev['revenue'],
|
||||
'appointments_by_day' => $apptByDay,
|
||||
],
|
||||
'period' => ['from' => $from, 'to' => $to],
|
||||
'today_appointments' => $todayAppts,
|
||||
@@ -250,6 +284,44 @@ class DashboardController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* سری ۷ روز اخیر درآمد (بر اساس PatientSession) + پرداختی امروز و هفته.
|
||||
* @return array{revenue: array<int, array{label:string, amount_rials:int}>, today_payments_rials:int, week_payments_rials:int}
|
||||
*/
|
||||
private function revenueDaily(string $entityType, int $entityId): array
|
||||
{
|
||||
$fmt = new \IntlDateFormatter('fa_IR@calendar=persian', \IntlDateFormatter::NONE, \IntlDateFormatter::NONE, 'Asia/Tehran', \IntlDateFormatter::TRADITIONAL, 'd MMMM');
|
||||
$series = [];
|
||||
$todayPay = 0;
|
||||
$weekPay = 0;
|
||||
for ($i = 6; $i >= 0; $i--) {
|
||||
$ds = strtotime('today midnight') - $i * 86400;
|
||||
$de = $ds + 86399;
|
||||
$rev = (int) $this->patientSessionRepo->sumRevenue($entityType, $entityId, $ds, $de);
|
||||
$series[] = ['label' => $fmt->format($ds), 'amount_rials' => $rev];
|
||||
$weekPay += $rev;
|
||||
if ($i === 0) { $todayPay = $rev; }
|
||||
}
|
||||
return ['revenue' => $series, 'today_payments_rials' => $todayPay, 'week_payments_rials' => $weekPay];
|
||||
}
|
||||
|
||||
/**
|
||||
* سری ۷ روز اخیر تعداد نوبت با شمارندهی دلخواه (doctor/clinic).
|
||||
* @param callable(int $dayStart, int $dayEnd): int $counter
|
||||
* @return array<int, array{label:string, count:int}>
|
||||
*/
|
||||
private function appointmentsDaily(callable $counter): array
|
||||
{
|
||||
$fmt = new \IntlDateFormatter('fa_IR@calendar=persian', \IntlDateFormatter::NONE, \IntlDateFormatter::NONE, 'Asia/Tehran', \IntlDateFormatter::TRADITIONAL, 'd MMMM');
|
||||
$series = [];
|
||||
for ($i = 6; $i >= 0; $i--) {
|
||||
$ds = strtotime('today midnight') - $i * 86400;
|
||||
$de = $ds + 86399;
|
||||
$series[] = ['label' => $fmt->format($ds), 'count' => $counter($ds, $de)];
|
||||
}
|
||||
return $series;
|
||||
}
|
||||
|
||||
// ── Secretary Dashboard ──────────────────────────────────────────────────
|
||||
|
||||
#[Route('/api/v1/dashboard/secretary', methods: ['GET'])]
|
||||
|
||||
Reference in New Issue
Block a user