import React, { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { api } from '../lib/api'; import type { ApiResponse, PaginatedResponse } from '../lib/api'; import { formatRial, formatDate } from '../lib/utils'; import DataTable, { Column } from '../components/ui/DataTable'; import Pagination from '../components/ui/Pagination'; import SearchableSelect from '../components/ui/SearchableSelect'; import PersianDatePicker from '../components/ui/PersianDatePicker'; const dayStart = (d: string): number => Math.floor(new Date(`${d}T00:00:00`).getTime() / 1000); const dayEnd = (d: string): number => Math.floor(new Date(`${d}T23:59:59`).getTime() / 1000); interface Breakdown { uuid: string; order_id: string; source: 'appointment' | 'subscription'; gross_rials: number; sms_fee_rials: number; tax_percent: number; tax_rials: number; net_after_tax_rials: number; commission_percent: number; representation_share_rials: number; system_share_rials: number; representation_id: number | null; representation_name: string | null; doctor_id: number | null; clinic_id: number | null; created_at: string; } interface Summary { total_gross: number; total_representation_income: number; total_tax_collected: number; total_sms_fee: number; total_system_share: number; } const SOURCE_LABEL: Record = { appointment: 'نوبت', subscription: 'ارتقاء اشتراک', }; export default function FinancialReportPage() { const [page, setPage] = useState(1); const [source, setSource] = useState(''); const [repId, setRepId] = useState(null); const [fromDate, setFromDate] = useState(''); const [toDate, setToDate] = useState(''); const limit = 15; const summaryQ = useQuery({ queryKey: ['financial-summary'], queryFn: () => api.get>('/api/v1/admin/financial-summary'), staleTime: 30_000, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any const summary: Summary | undefined = (summaryQ.data?.data as any)?.data ?? summaryQ.data?.data; const repsQ = useQuery({ queryKey: ['representations-select'], queryFn: () => api.get>('/api/v1/admin/representations?limit=200'), staleTime: 60_000, }); const repOptions = [ { value: '', label: 'همه نمایندگان' }, ...(repsQ.data?.data ?? []).map((r) => ({ value: r.id, label: `${r.full_name}${r.city ? ` — ${r.city}` : ''}` })), ]; const { data, isLoading } = useQuery({ queryKey: ['financial-breakdowns', page, source, repId, fromDate, toDate], queryFn: () => { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (source) params.set('source', source); if (repId) params.set('representation_id', String(repId)); if (fromDate) params.set('from', String(dayStart(fromDate))); if (toDate) params.set('to', String(dayEnd(toDate))); return api.get>(`/api/v1/admin/financial-breakdowns?${params}`); }, }); const items: Breakdown[] = data?.data ?? []; const total = data?.meta?.totalRecords ?? 0; const cards = [ { label: 'مجموع ناخالص', value: summary?.total_gross, color: 'var(--text-2)', bg: 'var(--surface-3)' }, { label: 'درآمد نمایندگان', value: summary?.total_representation_income, color: 'var(--primary)', bg: 'var(--primary-soft)' }, { label: 'مالیات دریافت‌شده', value: summary?.total_tax_collected, color: 'var(--warning)', bg: 'var(--warning-bg)' }, { label: 'هزینه پنل پیامک', value: summary?.total_sms_fee, color: 'var(--violet)', bg: 'var(--violet-bg)' }, { label: 'سهم سیستم', value: summary?.total_system_share, color: 'var(--success)', bg: 'var(--success-bg)' }, ]; const columns: Column[] = [ { key: 'order_id', header: 'شناسه سفارش', render: (r) => {r.order_id} }, { key: 'source', header: 'نوع', render: (r) => SOURCE_LABEL[r.source] ?? r.source }, { key: 'representation_name', header: 'نماینده', render: (r) => r.representation_name ?? '—' }, { key: 'gross_rials', header: 'ناخالص', render: (r) => formatRial(r.gross_rials) }, { key: 'sms_fee_rials', header: 'پیامک', render: (r) => formatRial(r.sms_fee_rials) }, { key: 'tax_rials', header: 'مالیات', render: (r) => `${formatRial(r.tax_rials)} (${r.tax_percent}٪)` }, { key: 'representation_share_rials', header: 'سهم نماینده', render: (r) => `${formatRial(r.representation_share_rials)} (${r.commission_percent}٪)` }, { key: 'system_share_rials', header: 'سهم سیستم', render: (r) => formatRial(r.system_share_rials) }, { key: 'created_at', header: 'تاریخ', render: (r) => formatDate(r.created_at) }, ]; return (

گزارش مالی

تفکیک پورسانت، مالیات و هزینه پنل پیامک هر تراکنش
{cards.map((c) => (
{c.label}
{c.value === undefined ? '—' : formatRial(c.value)}
))}
{ setRepId(v === '' || v === null ? null : Number(v)); setPage(1); }} placeholder="فیلتر نماینده…" />
{ setFromDate(v); setPage(1); }} placeholder="از تاریخ" /> { setToDate(v); setPage(1); }} placeholder="تا تاریخ" /> {(repId || fromDate || toDate || source) && ( )}
); }