1. شارژ کیف پول is now functional end-to-end. New owner-gated
POST /api/v1/patient/{uuid}/wallet/charge creates a manual credit
WalletTransaction (computed balance_after); the patient detail's wallet tab
gains a top-up modal (PriceInput + description) and supports ?tab= deep
links. The deposit sections of the create drawer, the edit page and the
replace modal link to it via WalletChargeLink (record resolved by mobile).
2. جایگزینی نوبت now matches appointments-replace.pdf: patient search-or-new,
بخش/سرویس/پرسنل selects prefilled from the appointment, deposit toggle +
amount + charge link, read-only original date/time, status pick and notes —
all through the general PATCH.
3. The confirmed-appointments table is paginated (20/page, client-side so the
schedule view and doctor-tab derivation keep the whole day), resetting on
date/doctor/filter changes. The page-local STATUS_META also adopts the
design labels plus following_up/salon for the schedule cards.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
108 lines
5.8 KiB
TypeScript
108 lines
5.8 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import { renderWithProviders } from '../test/utils';
|
|
|
|
vi.mock('../lib/api', () => ({
|
|
api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() },
|
|
ApiError: class extends Error {},
|
|
}));
|
|
|
|
import { api } from '../lib/api';
|
|
import NewAppointmentDrawer from './NewAppointmentDrawer';
|
|
|
|
const get = api.get as ReturnType<typeof vi.fn>;
|
|
const post = api.post as ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
get.mockReset(); post.mockReset();
|
|
get.mockImplementation((url: string) => {
|
|
if (url === '/api/v1/service-sections') return Promise.resolve({ success: true, data: [{ uuid: 'sec1', name: 'زیبایی' }] });
|
|
if (url.startsWith('/api/v1/service-items/sec1')) return Promise.resolve({ success: true, data: [{ uuid: 'it1', name: 'لیزر توتال' }] });
|
|
if (url === '/api/v1/staff') return Promise.resolve({ success: true, data: [{ uuid: 'st1', full_name: 'سحر ایمانی' }] });
|
|
if (url.startsWith('/api/v1/patient?search=')) return Promise.resolve({ success: true, data: [{ uuid: 'rec1', user_name: 'ساغر صابری', user_mobile: '09356619438' }] });
|
|
return Promise.resolve({ success: true, data: [] });
|
|
});
|
|
post.mockResolvedValue({ success: true, data: { uuid: 'new1' } });
|
|
});
|
|
|
|
function renderDrawer() {
|
|
return renderWithProviders(
|
|
<NewAppointmentDrawer doctorUuid="d1" defaultDate="2026-08-01" queryKey={['appts']} onClose={() => {}} />,
|
|
);
|
|
}
|
|
|
|
describe('NewAppointmentDrawer (اضافه کردن نوبت جدید)', () => {
|
|
it('renders the Figma sections and disables submit until valid', async () => {
|
|
renderDrawer();
|
|
expect(screen.getByText('اطلاعات مراجعه کننده:')).toBeInTheDocument();
|
|
expect(screen.getByText('مشخصات سرویس:')).toBeInTheDocument();
|
|
expect(screen.getByText('زمان نوبت:')).toBeInTheDocument();
|
|
expect(screen.getByText('بیعانه:')).toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: 'ثبت نوبت' })).toBeDisabled();
|
|
});
|
|
|
|
it('end time follows start + default duration', () => {
|
|
renderDrawer();
|
|
fireEvent.change(screen.getByLabelText('ساعت شروع'), { target: { value: '09:00' } });
|
|
fireEvent.change(screen.getByLabelText('زمان پیش فرض'), { target: { value: '45' } });
|
|
expect((screen.getByLabelText('ساعت پایان') as HTMLInputElement).value).toBe('09:45');
|
|
});
|
|
|
|
it('posts the extended payload for a new patient with service specs', async () => {
|
|
renderDrawer();
|
|
fireEvent.change(screen.getByPlaceholderText('نام و نام خانوادگی مراجعه کننده را وارد نمایید'), { target: { value: 'مریم خلیلی' } });
|
|
fireEvent.change(screen.getByPlaceholderText('شماره تماس مراجعه کننده را وارد نمایید'), { target: { value: '09136549874' } });
|
|
await screen.findByRole('option', { name: 'زیبایی' });
|
|
fireEvent.change(screen.getByLabelText('بخش'), { target: { value: 'sec1' } });
|
|
await screen.findByRole('option', { name: 'لیزر توتال' });
|
|
fireEvent.change(screen.getByLabelText('سرویس'), { target: { value: 'it1' } });
|
|
await screen.findByRole('option', { name: 'سحر ایمانی' });
|
|
fireEvent.change(screen.getByLabelText('پرسنل'), { target: { value: 'st1' } });
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'ثبت نوبت' }));
|
|
await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/my/appointment', expect.objectContaining({
|
|
doctor_uuid: 'd1',
|
|
patient_name: 'مریم خلیلی',
|
|
patient_mobile: '09136549874',
|
|
service_section_uuid: 'sec1',
|
|
service_item_uuid: 'it1',
|
|
staff_uuid: 'st1',
|
|
is_reserve: false,
|
|
})));
|
|
});
|
|
|
|
it('picks an existing patient from the search results', async () => {
|
|
renderDrawer();
|
|
fireEvent.change(screen.getByPlaceholderText('جستجوی نام، شماره تماس، شماره پرونده...'), { target: { value: 'ساغر' } });
|
|
fireEvent.click(await screen.findByText('ساغر صابری'));
|
|
fireEvent.click(screen.getByRole('button', { name: 'ثبت نوبت' }));
|
|
await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/my/appointment', expect.objectContaining({
|
|
patient_name: 'ساغر صابری', patient_mobile: '09356619438',
|
|
})));
|
|
});
|
|
|
|
it('deposit toggle reveals the amount field and the wallet-charge link', async () => {
|
|
renderDrawer();
|
|
fireEvent.click(screen.getByLabelText('بیعانه مورد نیاز است.'));
|
|
expect(await screen.findByText('مبلغ بیعانه (تومان)')).toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: /شارژ کیف پول/ })).toBeInTheDocument();
|
|
});
|
|
|
|
it('reserve mode hides time fields and posts a day-level entry', async () => {
|
|
renderWithProviders(
|
|
<NewAppointmentDrawer doctorUuid="d1" defaultDate="2026-08-01" queryKey={['r']} onClose={() => {}} isReserve />,
|
|
);
|
|
expect(screen.getByText('اضافه کردن نوبت رزرو')).toBeInTheDocument();
|
|
expect(screen.queryByLabelText('ساعت شروع')).toBeNull();
|
|
|
|
fireEvent.change(screen.getByPlaceholderText('نام و نام خانوادگی مراجعه کننده را وارد نمایید'), { target: { value: 'مریم خلیلی' } });
|
|
fireEvent.change(screen.getByPlaceholderText('شماره تماس مراجعه کننده را وارد نمایید'), { target: { value: '09136549874' } });
|
|
fireEvent.click(screen.getByRole('button', { name: 'ثبت نوبت' }));
|
|
|
|
const day = Math.floor(new Date('2026-08-01T00:00').getTime() / 1000);
|
|
await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/my/appointment', expect.objectContaining({
|
|
is_reserve: true, slot_start: day, slot_end: day,
|
|
})));
|
|
});
|
|
});
|