feat(payment): add payment detail endpoint and update payment model with order_id and patient_name
feat(appointment): enhance appointment detail page with time formatting and additional info fix(payment): update payment query to fetch from the correct endpoint and adjust response structure docs(api): add search parameter to payments API documentation and detail response structure test(payment): add unit test for MellatGateway to verify null credentials handling
This commit is contained in:
@@ -6,7 +6,7 @@ import { toast } from 'sonner';
|
||||
import { api } from '../lib/api';
|
||||
import type { ApiResponse } from '../lib/api';
|
||||
import type { Appointment, AppointmentStatus } from '../types';
|
||||
import { formatDate, formatDateTime } from '../lib/utils';
|
||||
import { formatDate, formatDateTime, toDate } from '../lib/utils';
|
||||
import PageHeader from '../components/ui/PageHeader';
|
||||
import StatusBadge from '../components/ui/StatusBadge';
|
||||
import ConfirmDialog from '../components/ui/ConfirmDialog';
|
||||
@@ -22,6 +22,13 @@ const ALL_STATUSES: { value: AppointmentStatus; label: string }[] = [
|
||||
{ value: 'expired', label: 'منقضی' },
|
||||
];
|
||||
|
||||
const timeOf = (ts?: number | null) => {
|
||||
const d = toDate(ts ?? null);
|
||||
return d
|
||||
? new Intl.DateTimeFormat('fa-IR-u-nu-latn', { hour: '2-digit', minute: '2-digit', hour12: false }).format(d)
|
||||
: '—';
|
||||
};
|
||||
|
||||
function InfoRow({ label, value }: { label: string; value: React.ReactNode }) {
|
||||
return (
|
||||
<div className="cp-info-row">
|
||||
@@ -64,7 +71,8 @@ export default function AppointmentDetailPage() {
|
||||
onError: (err: Error) => toast.error(err.message),
|
||||
});
|
||||
|
||||
const appt = data?.data;
|
||||
// پاسخ single تودرتو است: { data: { data: {...} } }
|
||||
const appt: any = (data?.data as any)?.data ?? data?.data;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -96,10 +104,12 @@ export default function AppointmentDetailPage() {
|
||||
<h3 className="font-semibold text-gray-800 mb-4">اطلاعات بیمار</h3>
|
||||
<InfoRow label="نام بیمار" value={appt.patient_name} />
|
||||
<InfoRow label="موبایل" value={<span dir="ltr">{appt.patient_mobile}</span>} />
|
||||
<InfoRow label="پزشک" value={`دکتر ${appt.doctor_name}`} />
|
||||
<InfoRow label="تاریخ نوبت" value={formatDate(appt.appointment_date)} />
|
||||
<InfoRow label="ساعت شروع" value={appt.appointment_time} />
|
||||
<InfoRow label="ساعت پایان" value={appt.end_time} />
|
||||
<InfoRow label="پزشک" value={appt.doctor?.name ? `دکتر ${appt.doctor.name}` : null} />
|
||||
<InfoRow label="تاریخ نوبت" value={formatDate(appt.slot_start)} />
|
||||
<InfoRow label="ساعت شروع" value={timeOf(appt.slot_start)} />
|
||||
<InfoRow label="ساعت پایان" value={timeOf(appt.slot_end)} />
|
||||
{appt.patient_reason && <InfoRow label="علت مراجعه" value={appt.patient_reason} />}
|
||||
{appt.note && <InfoRow label="توضیحات" value={appt.note} />}
|
||||
<InfoRow label="تاریخ ثبت" value={formatDateTime(appt.created_at)} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,6 +9,12 @@ import { formatDate, formatDateTime, formatRial } from '../lib/utils';
|
||||
import PageHeader from '../components/ui/PageHeader';
|
||||
import StatusBadge from '../components/ui/StatusBadge';
|
||||
|
||||
const PAYMENT_TYPE_LABELS: Record<string, string> = {
|
||||
appointment: 'نوبت',
|
||||
subscription: 'اشتراک',
|
||||
sms_wallet: 'کیف پول پیامک',
|
||||
};
|
||||
|
||||
function InfoRow({ label, value }: { label: string; value: React.ReactNode }) {
|
||||
return (
|
||||
<div className="cp-info-row">
|
||||
@@ -24,7 +30,7 @@ export default function PaymentDetailPage() {
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['payment', uuid],
|
||||
queryFn: () => api.get<ApiResponse<Payment>>(`/api/v1/payment/${uuid}`),
|
||||
queryFn: () => api.get<ApiResponse<Payment>>(`/api/v1/admin/payments/${uuid}`),
|
||||
enabled: !!uuid,
|
||||
});
|
||||
|
||||
@@ -58,7 +64,10 @@ export default function PaymentDetailPage() {
|
||||
<div className="max-w-lg">
|
||||
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
|
||||
<InfoRow label="شناسه" value={<span dir="ltr" className="font-mono text-xs">{payment.uuid}</span>} />
|
||||
<InfoRow label="شماره سفارش" value={payment.order_id ? <span dir="ltr" className="font-mono text-xs">{payment.order_id}</span> : null} />
|
||||
<InfoRow label="بیمار" value={payment.patient_name} />
|
||||
<InfoRow label="موبایل" value={<span dir="ltr">{payment.patient_mobile}</span>} />
|
||||
<InfoRow label="نوع" value={PAYMENT_TYPE_LABELS[payment.type ?? ''] ?? payment.type} />
|
||||
<InfoRow label="مبلغ" value={formatRial(payment.amount)} />
|
||||
<InfoRow label="وضعیت" value={<StatusBadge type="payment" value={payment.status} />} />
|
||||
<InfoRow label="درگاه" value={<span className="uppercase">{payment.gateway}</span>} />
|
||||
|
||||
@@ -122,6 +122,9 @@ export interface Payment {
|
||||
appointment_uuid: string | null;
|
||||
paid_at: string | null;
|
||||
created_at: string;
|
||||
order_id?: string;
|
||||
patient_name?: string | null;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export type SettlementStatus = "pending" | "approved" | "rejected";
|
||||
|
||||
Reference in New Issue
Block a user