feat: add payments summary endpoint and UI redesign for MyPaymentsPage
- Implemented a new API endpoint `/api/v1/my/billing/payments/summary` to provide a financial summary of payments with filters for national code, status, and date range. - Updated the InvoiceRepository to aggregate totals for paid and unsettled invoices. - Created a new hook `usePaymentsSummary` to fetch summary data in the frontend. - Redesigned the MyPaymentsPage to align with the ClaimsPage structure, incorporating a design system, summary statistics, and improved filtering options. - Added tests for the new payments summary endpoint to ensure correct functionality and filtering behavior.
This commit is contained in:
@@ -21,10 +21,21 @@ const ROWS = [
|
||||
issued_at: 1718000000, amount_rials: 6000000, status: 'unsettled' },
|
||||
];
|
||||
|
||||
const SUMMARY = { total_rials: 8350000, paid_rials: 2350000, unsettled_rials: 6000000, invoices_count: 2 };
|
||||
|
||||
/** The page fires two queries; route by URL so each gets its own envelope. */
|
||||
const mockApi = (rows = ROWS, total = rows.length) => {
|
||||
get.mockImplementation((url: string) =>
|
||||
url.includes('/payments/summary')
|
||||
? Promise.resolve({ success: true, data: SUMMARY })
|
||||
: Promise.resolve({ success: true, data: rows, meta: { totalRecords: total, totalPages: 1, currentPage: 1 } }),
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
navigate.mockReset();
|
||||
get.mockReset();
|
||||
get.mockResolvedValue({ success: true, data: ROWS, meta: { totalRecords: 2, totalPages: 1, currentPage: 1 } });
|
||||
mockApi();
|
||||
});
|
||||
|
||||
describe('MyPaymentsPage (لیست پرداختها)', () => {
|
||||
@@ -36,23 +47,42 @@ describe('MyPaymentsPage (لیست پرداختها)', () => {
|
||||
expect(screen.getByText('2200112233')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates to the patient detail on مشاهده', async () => {
|
||||
it('renders the two-state invoice status badge', async () => {
|
||||
renderWithProviders(<MyPaymentsPage />, { route: '/admin/my-payments' });
|
||||
expect(await screen.findByText('پرداخت شده')).toBeInTheDocument();
|
||||
expect(screen.getByText('تسویه نشده')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the summary stat cards', async () => {
|
||||
renderWithProviders(<MyPaymentsPage />, { route: '/admin/my-payments' });
|
||||
expect(await screen.findByText('مجموع صورتحسابها')).toBeInTheDocument();
|
||||
expect(screen.getByText('پرداختشده')).toBeInTheDocument();
|
||||
expect(screen.getByText('تسویهنشده')).toBeInTheDocument();
|
||||
expect(screen.getByText('تعداد صورتحساب')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates to the patient detail on جزئیات', async () => {
|
||||
renderWithProviders(<MyPaymentsPage />, { route: '/admin/my-payments' });
|
||||
await screen.findByText('دنیا خلیلی');
|
||||
fireEvent.click(screen.getAllByRole('button', { name: /مشاهده/ })[0]);
|
||||
fireEvent.click(screen.getAllByRole('button', { name: /جزئیات/ })[0]);
|
||||
expect(navigate).toHaveBeenCalledWith('/admin/my-payments/p1');
|
||||
});
|
||||
|
||||
it('shows an empty state when there are no payments', async () => {
|
||||
get.mockResolvedValue({ success: true, data: [], meta: { totalRecords: 0, totalPages: 0, currentPage: 1 } });
|
||||
mockApi([], 0);
|
||||
renderWithProviders(<MyPaymentsPage />, { route: '/admin/my-payments' });
|
||||
expect(await screen.findByText('پرداختی یافت نشد')).toBeInTheDocument();
|
||||
expect(await screen.findByText('پرداختی ثبت نشده است.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('sends the national_code filter to the API', async () => {
|
||||
it('sends the national_code filter to both the list and the summary', async () => {
|
||||
renderWithProviders(<MyPaymentsPage />, { route: '/admin/my-payments' });
|
||||
await screen.findByText('دنیا خلیلی');
|
||||
fireEvent.change(screen.getByPlaceholderText('کد ملی بیمار را وارد کنید...'), { target: { value: '1744' } });
|
||||
await waitFor(() => expect(get.mock.calls.some(([u]) => String(u).includes('national_code=1744'))).toBe(true));
|
||||
fireEvent.change(screen.getByPlaceholderText('کد ملی بیمار'), { target: { value: '1744' } });
|
||||
|
||||
await waitFor(() => {
|
||||
const urls = get.mock.calls.map(([u]) => String(u)).filter((u) => u.includes('national_code=1744'));
|
||||
expect(urls.some((u) => u.includes('/payments?'))).toBe(true);
|
||||
expect(urls.some((u) => u.includes('/payments/summary?'))).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user