From 93fa0fc2c9105b10b7d7244b554782358e34990f Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Wed, 24 Jun 2026 20:16:10 +0330 Subject: [PATCH] feat(representation): enhance finance page with summary cards and improved table styling --- .../admin/pages/RepresentationFinancePage.tsx | 132 ++++++++++++++---- assets/admin/styles.css | 2 + 2 files changed, 108 insertions(+), 26 deletions(-) diff --git a/assets/admin/pages/RepresentationFinancePage.tsx b/assets/admin/pages/RepresentationFinancePage.tsx index 0987e46d..eb370b18 100644 --- a/assets/admin/pages/RepresentationFinancePage.tsx +++ b/assets/admin/pages/RepresentationFinancePage.tsx @@ -1,8 +1,12 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; +import { + BanknotesIcon, ReceiptPercentIcon, ChatBubbleLeftRightIcon, + CalculatorIcon, ArrowPathIcon, DocumentTextIcon, +} from '@heroicons/react/24/outline'; import { api } from '../lib/api'; import type { ApiResponse, PaginatedResponse } from '../lib/api'; -import { formatRial, formatDate } from '../lib/utils'; +import { formatRial, formatNumber, formatDate } from '../lib/utils'; import Pagination from '../components/ui/Pagination'; interface FinanceRow { @@ -27,16 +31,19 @@ function rangeFrom(key: RangeKey): number | null { return null; } -const RANGE_LABEL: Record = { - today: 'امروز', week: 'این هفته', month: 'این ماه', all: 'همه', -}; +const RANGES: { key: RangeKey; label: string }[] = [ + { key: 'today', label: 'امروز' }, + { key: 'week', label: '۷ روز اخیر' }, + { key: 'month', label: '۳۰ روز اخیر' }, + { key: 'all', label: 'همه' }, +]; export default function RepresentationFinancePage() { const [range, setRange] = useState('month'); const [page, setPage] = useState(1); const limit = 15; - const { data, isLoading } = useQuery({ + const { data, isLoading, isFetching, refetch } = useQuery({ queryKey: ['representation-finance', range, page], queryFn: () => { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); @@ -49,48 +56,121 @@ export default function RepresentationFinancePage() { const items: FinanceRow[] = data?.data ?? []; const total = data?.meta?.totalRecords ?? 0; + // جمعِ ردیف‌های صفحه‌ی جاری (نمایش سریع؛ دقیق برای همین صفحه). + const pageSums = useMemo(() => items.reduce( + (acc, r) => { + acc.share += r.representation_share_rials; + acc.gross += r.gross_rials; + acc.tax += r.tax_rials; + acc.sms += r.sms_fee_rials; + return acc; + }, + { share: 0, gross: 0, tax: 0, sms: 0 }, + ), [items]); + + const cards = [ + { label: 'سهم نماینده (این صفحه)', value: formatRial(pageSums.share), icon: BanknotesIcon, color: 'var(--primary)', bg: 'var(--primary-soft)' }, + { label: 'مبلغ نوبت‌ها', value: formatRial(pageSums.gross), icon: CalculatorIcon, color: 'var(--info)', bg: 'var(--info-bg)' }, + { label: 'مالیات', value: formatRial(pageSums.tax), icon: ReceiptPercentIcon, color: 'var(--warning)', bg: 'var(--warning-bg)' }, + { label: 'هزینه پیامک', value: formatRial(pageSums.sms), icon: ChatBubbleLeftRightIcon, color: 'var(--violet)', bg: 'var(--violet-bg)' }, + ]; + return (

گزارش مالی

-
درآمد ثبت‌شده از پورسانت نوبت‌ها
+
+ درآمد ثبت‌شده از پورسانت نوبت‌ها — تفکیک هر تراکنش +
+
-
- {(['today', 'week', 'month', 'all'] as RangeKey[]).map(k => ( - ))}
-
+ {/* کارت‌های خلاصه */} +
+ {cards.map(c => ( +
+
+ +
+
{c.label}
+
+ {isLoading ? '—' : c.value} +
+
+ ))} +
+ + {/* جدول تراکنش‌ها */} +
+
+

+ + تراکنش‌ها +

+ {!isLoading && {formatNumber(total)} ردیف} +
+
- +
- - + + + + + + + - {isLoading && ( - - )} + {isLoading && Array.from({ length: 5 }).map((_, i) => ( + + {Array.from({ length: 7 }).map((__, j) => ( + + ))} + + ))} + {!isLoading && items.length === 0 && ( - + + + )} - {items.map(r => ( + + {!isLoading && items.map(r => ( - + - - - - - + + + + + ))} diff --git a/assets/admin/styles.css b/assets/admin/styles.css index e746175c..b0affd54 100644 --- a/assets/admin/styles.css +++ b/assets/admin/styles.css @@ -456,6 +456,8 @@ body { .tbl th { text-align: right; padding: 8px 12px; color: var(--text-3); font-weight: 500; white-space: nowrap; } .tbl td { padding: 8px 12px; border-bottom: 1px solid var(--border); } .tbl tbody tr:last-child td { border-bottom: none; } +.tbl-zebra tbody tr:nth-child(even) td { background: var(--surface-2); } +.tbl-zebra tbody tr:hover td { background: var(--surface-3); } /* ── Appointment status badges ───────────────────────────────── */ .appt-status {
پزشکمبلغ نوبتمالیاتپیامکدرصدسهم نمایندهتاریخپزشکمبلغ نوبتمالیاتهزینه پیامکدرصد پورسانتسهم نمایندهتاریخ
در حال بارگذاری...
درآمدی در این بازه ثبت نشده است
+
+ +
در این بازه درآمدی ثبت نشده است
+
با ثبت و پرداخت نوبت از دامنه‌ی شما، پورسانت اینجا نمایش داده می‌شود.
+
+
{r.doctor_name ?? '—'}{r.doctor_name ?? '—'} {formatRial(r.gross_rials)}{formatRial(r.tax_rials)}{formatRial(r.sms_fee_rials)}{r.commission_percent}٪{formatRial(r.representation_share_rials)}{formatDate(r.created_at)}{formatRial(r.tax_rials)}{formatRial(r.sms_fee_rials)}{formatNumber(r.commission_percent)}٪{formatRial(r.representation_share_rials)}{formatDate(r.created_at)}