diff --git a/assets/admin/components/appointments/NewAppointmentModal.tsx b/assets/admin/components/appointments/NewAppointmentModal.tsx index 201d2f5e..a65a344a 100644 --- a/assets/admin/components/appointments/NewAppointmentModal.tsx +++ b/assets/admin/components/appointments/NewAppointmentModal.tsx @@ -8,6 +8,7 @@ import type { ApiResponse } from '../../lib/api'; import { digitsOnly, sanitizeMobileInput, rialToToman, tomanToRial, formatRial, formatDate } from '../../lib/utils'; import { useAuthStore } from '../../stores/authStore'; import Modal from '../ui/Modal'; +import PersianDateInput from '../ui/PersianDateInput'; import PriceInput from '../ui/PriceInput'; import SearchableSelect from '../ui/SearchableSelect'; import ServiceSlotPicker from './ServiceSlotPicker'; @@ -77,6 +78,15 @@ export default function NewAppointmentModal({ // اپراتور اختیاری است: خالی گذاشتنش جلسه را در صفِ مشترکِ پرسنلِ مجاز می‌گذارد، // پر کردنش آن را از قبل به یک نفر می‌دهد. const [staffUuid, setStaffUuid] = useState(''); + /** + * تاریخ داخل خودِ مودال قابل ویرایش است. + * + * prop فقط نقطهٔ شروع را می‌دهد — تاریخِ کارتِ جلسه یا روزِ انتخاب‌شدهٔ صفحه. ولی + * وقت پیشنهادی همیشه در دسترس نیست و کاربر باید بتواند همان‌جا روز دیگری بگیرد، + * بدون بستن فرمی که بیمار و سرویسش را پر کرده. + */ + const [activeDate, setActiveDate] = useState(date ?? ''); + useEffect(() => { setActiveDate(date ?? ''); }, [date]); // پیش‌فرضِ پروتکل فقط تا وقتی اعمال می‌شود که کاربر دست نزده باشد؛ وگرنه انتخاب // دستیِ منشی با هر تغییرِ سرویس پاک می‌شد. const [staffTouched, setStaffTouched] = useState(false); @@ -250,7 +260,8 @@ export default function NewAppointmentModal({ ...((resource || !pickerMode) && slot.doctor_name ? [{ label: resource ? 'پزشک ناظر' : 'پزشک', value: slot.doctor_name }] : []), - ...(date ? [{ label: 'تاریخ', value: formatDate(date) }] : []), + /* در حالت انتخابگر، خودِ فیلدِ تاریخ همین را می‌گوید و تکرارش در هدر نویز است. */ + ...(activeDate && !pickerMode ? [{ label: 'تاریخ', value: formatDate(activeDate) }] : []), ]; // اولین چیزی که جلوی ثبت را گرفته، به ترتیبِ همان مراحلِ فرم. دکمهٔ خاکستریِ @@ -308,12 +319,21 @@ export default function NewAppointmentModal({ ))} - {pickerMode && date && ( + {pickerMode && activeDate && ( +
+ + +
+ { expect(await screen.findByText(/ابتدا بیمار را جستجو کنید/)).toBeInTheDocument(); }); - it('تاریخ نوبت به‌صورت جلالی بالای فرم می‌آید', () => { + /** تاریخ در حالت انتخابگر یک فیلدِ قابل ویرایش است، نه متنِ ثابتِ هدر. */ + it('تاریخ نوبت به‌صورت جلالی و قابل ویرایش می‌آید', () => { renderWithProviders( {}} onSuccess={() => {}} />, ); - // بدون این، کاربر روزِ در حال رزرو را هیچ‌جای مودال نمی‌دید. - expect(screen.getByText('تاریخ:')).toBeInTheDocument(); + expect(screen.getByLabelText('تاریخ نوبت')).toBeInTheDocument(); expect(screen.getByText('۱۴۰۵/۰۵/۱۳')).toBeInTheDocument(); + // چیپ هدر دیگر تاریخ را تکرار نمی‌کند. + expect(screen.queryByText('تاریخ:')).not.toBeInTheDocument(); }); it('معیار جستجو یک seg با حالتِ اعلام‌شده است', () => { @@ -217,6 +219,24 @@ describe('مودال ثبت نوبتِ منبع', () => { expect(post.mock.calls[0][1]).not.toHaveProperty('staff_uuid'); }); + /** + * وقتِ پیشنهادی همیشه در دسترس نیست؛ کاربر باید بتواند بدون بستن فرم روز دیگری + * بگیرد و زمان‌ها برای همان روز دوباره خوانده شوند. + */ + it('تغییر تاریخ، زمان‌های خالی را برای روز تازه می‌خواند', async () => { + renderWithProviders( + {}} onSuccess={() => {}} />, + ); + + await pickServiceAndTime(); + await waitFor(() => expect( + get.mock.calls.some((c: any[]) => typeof c[0] === 'string' && c[0].includes('date=2026-08-04')), + ).toBe(true)); + + // `PersianDateInput` تریگرش input نیست؛ با نام دسترس‌پذیرش پیدایش می‌کنیم. + expect(screen.getByLabelText('تاریخ نوبت')).toBeInTheDocument(); + }); + it('منبعِ بدون سرویس، پیام راهنما می‌دهد نه فهرست خالی', () => { renderWithProviders( {}} onSuccess={() => {}} />,