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:
hamed
2026-07-19 10:12:01 +03:30
parent 7ebc04fc3f
commit 5c8fe8ece4
11 changed files with 735 additions and 129 deletions
+10 -2
View File
@@ -1,5 +1,5 @@
import React from 'react';
import type { AppointmentStatus, PaymentStatus, SmsTemplateStatus, SettlementStatus, ClaimStatus } from '../../types';
import type { AppointmentStatus, PaymentStatus, SmsTemplateStatus, SettlementStatus, ClaimStatus, InvoiceListStatus } from '../../types';
type BadgeColor = 'green' | 'amber' | 'red' | 'blue' | 'violet' | 'gray';
@@ -45,8 +45,13 @@ const claimMap: Record<ClaimStatus, { color: BadgeColor; label: string }> = {
mixed: { color: 'violet', label: 'وضعیت‌های مختلف' },
};
const invoiceMap: Record<InvoiceListStatus, { color: BadgeColor; label: string }> = {
paid: { color: 'green', label: 'پرداخت شده' },
unsettled: { color: 'amber', label: 'تسویه نشده' },
};
interface Props {
type: 'appointment' | 'payment' | 'sms' | 'settlement' | 'active' | 'claim';
type: 'appointment' | 'payment' | 'sms' | 'settlement' | 'active' | 'claim' | 'invoice';
value: string;
}
@@ -69,6 +74,9 @@ export default function StatusBadge({ type, value }: Props) {
} else if (type === 'claim') {
const m = claimMap[value as ClaimStatus];
if (m) { color = m.color; label = m.label; }
} else if (type === 'invoice') {
const m = invoiceMap[value as InvoiceListStatus];
if (m) { color = m.color; label = m.label; }
} else if (type === 'active') {
color = value === 'true' || value === 'active' ? 'green' : 'gray';
label = value === 'true' || value === 'active' ? 'فعال' : 'غیرفعال';