From 6ec011e3adf7d3ad4a3dcef592e0942ab51eecfc Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Wed, 29 Jul 2026 21:15:09 +0330 Subject: [PATCH] feat: enhance NewAppointmentsTable with dynamic patient record navigation and update DashboardPage tests --- .../dashboard/NewAppointmentsTable.tsx | 53 ++++++++++++++++--- assets/admin/pages/DashboardPage.test.tsx | 35 +++++++++++- assets/admin/pages/DashboardPage.tsx | 22 +------- 3 files changed, 79 insertions(+), 31 deletions(-) diff --git a/assets/admin/components/dashboard/NewAppointmentsTable.tsx b/assets/admin/components/dashboard/NewAppointmentsTable.tsx index b5b392d4..3218f21c 100644 --- a/assets/admin/components/dashboard/NewAppointmentsTable.tsx +++ b/assets/admin/components/dashboard/NewAppointmentsTable.tsx @@ -8,9 +8,11 @@ * editable in place only when the row carries a `version` (optimistic lock) * and the parent passes the query key to invalidate. */ -import React from 'react'; -import { Link } from 'react-router-dom'; +import React, { useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { toast } from 'sonner'; import AppointmentStatusDropdown, { STATUS_META } from '../ui/AppointmentStatusDropdown'; +import { findRecordUuid } from '../AppointmentActions'; export interface ApptRow { uuid: string; @@ -95,12 +97,7 @@ export function NewAppointmentsTable({ rows, loading, queryKey, emptyText }: Pro : } - - مشاهده - + ))} @@ -110,6 +107,46 @@ export function NewAppointmentsTable({ rows, loading, queryKey, emptyText }: Pro ); } +/** + * وضعیت‌هایی که نوبت از مرحلهٔ «قطعی‌شدن» رد شده — در این‌ها پروندهٔ بیمار حتماً + * ساخته شده است (ساخت پرونده اثر جانبیِ قطعی‌کردن است). + */ +const CONFIRMED_STATUSES = ['confirmed', 'following_up', 'salon', 'completed', 'no_show']; + +/** + * «مشاهده» — نوبتِ قطعی‌شده به **پروندهٔ بیمار** می‌رود (چون از لحظهٔ قطعی‌شدن پرونده + * دارد)، بقیه به لیست نوبت‌های همان روز. uuid پرونده روی ردیف نیست، پس با موبایلِ + * بیمار resolve می‌شود — همان مسیری که منوی عملیات نوبت‌ها استفاده می‌کند. + */ +function ViewLink({ row }: { row: ApptRow }) { + const navigate = useNavigate(); + const [busy, setBusy] = useState(false); + const dayHref = isoDay(row.slot_start) ? `/admin/appointments?date=${isoDay(row.slot_start)}` : '/admin/appointments'; + const cls = 'text-[var(--primary)] text-[12px] font-medium hover:underline whitespace-nowrap'; + + if (!CONFIRMED_STATUSES.includes(row.status) || !row.patient_mobile) { + return مشاهده; + } + + const openRecord = async () => { + setBusy(true); + try { + const recordUuid = await findRecordUuid(row.patient_mobile!); + navigate(recordUuid ? `/admin/patients/${recordUuid}` : dayHref); + } catch { + toast.error('خطا در یافتن پرونده بیمار'); + } finally { + setBusy(false); + } + }; + + return ( + + ); +} + /** نمایش فقط‌خواندنی وضعیت — وقتی version یا queryKey در دسترس نیست. */ function StatusPill({ status }: { status: string }) { const meta = STATUS_META[status] ?? { label: status, color: 'var(--text-3)' }; diff --git a/assets/admin/pages/DashboardPage.test.tsx b/assets/admin/pages/DashboardPage.test.tsx index be4e4ca6..47414d8f 100644 --- a/assets/admin/pages/DashboardPage.test.tsx +++ b/assets/admin/pages/DashboardPage.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { screen } from '@testing-library/react'; +import { screen, fireEvent, waitFor } from '@testing-library/react'; import { renderWithProviders } from '../test/utils'; vi.mock('../lib/api', () => ({ @@ -7,6 +7,12 @@ vi.mock('../lib/api', () => ({ ApiError: class extends Error {}, })); +const navigate = vi.fn(); +vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), + useNavigate: () => navigate, +})); + import { api } from '../lib/api'; import { useAuthStore } from '../stores/authStore'; import DashboardPage from './DashboardPage'; @@ -103,7 +109,32 @@ describe('DashboardPage (ported clinic dashboard)', () => { expect(screen.queryByText('پزشکان کلینیک')).not.toBeInTheDocument(); }); - it('«مشاهده» به همان روزِ نوبت در لیست نوبت‌ها لینک می‌دهد', async () => { + it('«مشاهده» نوبتِ قطعی‌شده، پروندهٔ بیمار را باز می‌کند', async () => { + // fixture وضعیت `completed` دارد — یعنی از قطعی‌شدن رد شده و پرونده دارد. + get.mockImplementation((url: string) => { + if (url.startsWith('/api/v1/patients?search=')) + return Promise.resolve({ success: true, data: [{ uuid: 'rec-42' }] }); + return Promise.resolve(clinicPayload); + }); + + renderWithProviders(, { route: '/admin/dashboard' }); + await screen.findByText('دنیا خلیلی'); + + fireEvent.click(screen.getByText('مشاهده')); + + await waitFor(() => expect(get).toHaveBeenCalledWith(expect.stringContaining('/api/v1/patients?search=09136549874'))); + await waitFor(() => expect(navigate).toHaveBeenCalledWith('/admin/patients/rec-42')); + }); + + it('«مشاهده» نوبتِ ثبت‌شده (قطعی‌نشده) به لیست نوبت‌های همان روز می‌رود', async () => { + get.mockResolvedValue({ + ...clinicPayload, + data: { + ...clinicPayload.data, + today_appointments: [{ ...clinicPayload.data.today_appointments[0], status: 'pending' }], + }, + }); + renderWithProviders(, { route: '/admin/dashboard' }); await screen.findByText('دنیا خلیلی'); diff --git a/assets/admin/pages/DashboardPage.tsx b/assets/admin/pages/DashboardPage.tsx index 29cca6e7..28928c20 100644 --- a/assets/admin/pages/DashboardPage.tsx +++ b/assets/admin/pages/DashboardPage.tsx @@ -839,28 +839,8 @@ function DoctorDashboard() { currentJalaliMonth={chartPeriod.currentJalaliMonth} /> -
+
-
-
-

کلینیک‌های من

-
- {!d?.clinics.length ? ( -

عضو کلینیکی نیستید

- ) : ( -
- {d.clinics.map((c, i) => ( -
- -
- {c.name} -
- فعال -
- ))} -
- )} -
);