fix(dashboard): transactions tab uses real payment fields + empty state

The transactions list/card read mock fields (appointment_details,
row.amount, status 'received') and iterated user.turns.done. Pass the
real payments array and read order_id/type/created_at/amount_rials/status
from Payment.toArray(); map status to Persian labels, show rials→toman,
and render an empty state when there are no transactions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-15 21:48:06 +03:30
co-authored by Claude Opus 4.8
parent 8a87d9cf92
commit c80f002670
3 changed files with 67 additions and 91 deletions
@@ -1,39 +1,36 @@
import CircularLoading from "@/app/component/loading/Circular";
import TextLoading from "@/app/component/loading/Text"; import TextLoading from "@/app/component/loading/Text";
import Image from "next/image"; import { convertTimestampToJalali } from "@/helper";
import { convertTimestampToJalali, convertTimestampToTime } from "@/helper";
import Link from "next/link"; import Link from "next/link";
const PAYMENT_STATUS = {
success: { label: "پرداخت شده", cls: "bg-[#E8FADD] text-[#75E22E]" },
pending: { label: "در انتظار", cls: "bg-[#FFF3DD] text-[#FDBA35]" },
failed: { label: "ناموفق", cls: "bg-[#FFE3E2] text-[#FF5450]" },
cancelled: { label: "لغو شده", cls: "bg-[#FFE3E2] text-[#FF5450]" },
refunded: { label: "بازگشت وجه", cls: "bg-[#EFEFEF] text-[#616161]" },
};
const TYPE_LABELS = {
appointment: "نوبت",
subscription: "اشتراک",
sms_wallet: "کیف پول پیامک",
};
function Card({ data, loading }) { function Card({ data, loading }) {
const status = PAYMENT_STATUS[data.status] || { label: data.status, cls: "bg-[#FFE3E2] text-[#FF5450]" };
return ( return (
<li className="p-[12px] shadow-[0px_1px_24.8px_0px_rgba(204,_204,_204,_0.18)] bg-[#FFF] rounded-[8px]"> <li className="p-[12px] shadow-[0px_1px_24.8px_0px_rgba(204,_204,_204,_0.18)] bg-[#FFF] rounded-[8px]">
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
<div className="flex items-center justify-start gap-[8px]"> <TextLoading width={120} height={18} loading={loading}>
<TextLoading width={120} height={18} loading={loading}> <p className="text-[#616161] text-[14px] font-medium">
<p className="text-[#616161] text-[14px] font-medium"> {TYPE_LABELS[data.type] || data.type || "--"}
{data.appointment_details?.doctor_name || "--"} </p>
</p> </TextLoading>
</TextLoading>
</div>
<TextLoading loading={loading} width={95} height={25}> <TextLoading loading={loading} width={95} height={25}>
<Link href={`/payment/${data.uuid}`}> <Link href={`/payment/${data.uuid}`}>
<div <div className={`w-[100px] p-[4px] rounded-[4px] text-center text-[14px] font-medium cursor-pointer select-none ${status.cls}`}>
className={` {status.label}
w-[100px] p-[4px] rounded-[4px] text-center text-[14px] font-medium cursor-pointer select-none
${data.status === "received"
? "bg-[#E8FADD] text-[#75E22E]"
: data.status === "pending"
? "bg-[#FFF3DD] text-[#FDBA35]"
: "bg-[#FFE3E2] text-[#FF5450]"
}
`}
variant="text"
>
{data.status === "received"
? "پرداخت شده"
: data.status === "pending"
? "در انتظار"
: "لغو شده"}
</div> </div>
</Link> </Link>
</TextLoading> </TextLoading>
@@ -44,43 +41,28 @@ function Card({ data, loading }) {
<p className="text-[#7E7E7E] text-[14px] font-normal">شناسه:</p> <p className="text-[#7E7E7E] text-[14px] font-normal">شناسه:</p>
</TextLoading> </TextLoading>
<TextLoading loading={loading} width={90} height={18}> <TextLoading loading={loading} width={90} height={18}>
<p className="text-[#616161] text-[14px] font-medium">{data.id}</p> <p className="text-[#616161] text-[14px] font-medium">{data.order_id || "--"}</p>
</TextLoading> </TextLoading>
</li> </li>
<span className="block h-px w-[calc(100%-20px)] mx-auto bg-[#EFEFEF] my-[8px]"></span> <span className="block h-px w-[calc(100%-20px)] mx-auto bg-[#EFEFEF] my-[8px]"></span>
<li className="flex items-center justify-between gap-[12px]"> <li className="flex items-center justify-between gap-[12px]">
<TextLoading loading={loading} width={90} height={18}> <TextLoading loading={loading} width={90} height={18}>
<p className="text-[#7E7E7E] text-[14px] font-normal"> <p className="text-[#7E7E7E] text-[14px] font-normal">تاریخ:</p>
تاریخ نوبت:
</p>
</TextLoading> </TextLoading>
<TextLoading loading={loading} width={80} height={18}> <TextLoading loading={loading} width={80} height={18}>
<p className="text-[#616161] text-[14px] font-medium"> <p className="text-[#616161] text-[14px] font-medium">
{convertTimestampToJalali(data.appointment_details?.start_time)} {convertTimestampToJalali(data.created_at)}
</p>
</TextLoading>
</li>
<span className="block h-px w-[calc(100%-20px)] mx-auto bg-[#EFEFEF] my-[8px]"></span>
<li className="flex items-center justify-between gap-[12px]">
<TextLoading loading={loading} width={80} height={18}>
<p className="text-[#7E7E7E] text-[14px] font-normal">ساعت نوبت:</p>
</TextLoading>
<TextLoading loading={loading} width={80} height={18}>
<p className="text-[#616161] text-[14px] font-medium">
{convertTimestampToTime(data.appointment_details?.start_time)}
</p> </p>
</TextLoading> </TextLoading>
</li> </li>
<span className="block h-px w-[calc(100%-20px)] mx-auto bg-[#EFEFEF] my-[8px]"></span> <span className="block h-px w-[calc(100%-20px)] mx-auto bg-[#EFEFEF] my-[8px]"></span>
<li className="flex items-center justify-between gap-[12px]"> <li className="flex items-center justify-between gap-[12px]">
<TextLoading loading={loading} width={110} height={18}> <TextLoading loading={loading} width={110} height={18}>
<p className="text-[#7E7E7E] text-[14px] font-normal"> <p className="text-[#7E7E7E] text-[14px] font-normal">مبلغ:</p>
مبلغ:
</p>
</TextLoading> </TextLoading>
<TextLoading loading={loading} width={80} height={18}> <TextLoading loading={loading} width={80} height={18}>
<p className="text-[#616161] text-[14px] font-medium"> <p className="text-[#616161] text-[14px] font-medium">
{data.amount ? data.amount.toLocaleString('fa-IR') : "--"} تومان {data.amount_rials ? Math.round(data.amount_rials / 10).toLocaleString("fa-IR") : "--"} تومان
</p> </p>
</TextLoading> </TextLoading>
</li> </li>
@@ -5,42 +5,57 @@ import Card from "./Card";
import TextLoading from "@/app/component/loading/Text"; import TextLoading from "@/app/component/loading/Text";
import CustomLoading from "@/app/component/loading/Custom"; import CustomLoading from "@/app/component/loading/Custom";
import CircularLoading from "@/app/component/loading/Circular"; import CircularLoading from "@/app/component/loading/Circular";
import { convertTimestampToJalali, convertTimestampToTime } from "@/helper"; import { convertTimestampToJalali } from "@/helper";
import Pagination from "@/app/component/Pagination"; import Pagination from "@/app/component/Pagination";
import Link from "next/link"; import Link from "next/link";
function List({ user, loading, page, totalPages, onPageChange }) { const PAYMENT_STATUS = {
const tableHead = [ success: { label: "پرداخت شده", cls: "bg-[#E8FADD] text-[#75E22E]" },
"ردیف", pending: { label: "در انتظار", cls: "bg-[#FFF3DD] text-[#FDBA35]" },
"شناسه", failed: { label: "ناموفق", cls: "bg-[#FFE3E2] text-[#FF5450]" },
"نام پزشک", cancelled: { label: "لغو شده", cls: "bg-[#FFE3E2] text-[#FF5450]" },
"تاریخ", refunded: { label: "بازگشت وجه", cls: "bg-[#EFEFEF] text-[#616161]" },
"ساعت", };
"مبلغ (تومان)",
"وضعیت", const TYPE_LABELS = {
]; appointment: "نوبت",
subscription: "اشتراک",
sms_wallet: "کیف پول پیامک",
};
function List({ payments, loading, page, totalPages, onPageChange }) {
const rows = Array.isArray(payments) ? payments : [];
const tableHead = ["ردیف", "شناسه پرداخت", "نوع", "تاریخ", "مبلغ (تومان)", "وضعیت"];
const centerTable = [0, 4, 5]; const centerTable = [0, 4, 5];
if (!loading && rows.length === 0) {
return (
<p className="text-[#7E7E7E] text-[14px] md:text-[16px] font-medium text-center py-[56px]">
تراکنشی ثبت نشده است.
</p>
);
}
return ( return (
<div className="!mt-[32px]"> <div className="!mt-[32px]">
<div className="hidden md:block w-full"> <div className="hidden md:block w-full">
<CustomTable tableHead={tableHead} centerTable={centerTable}> <CustomTable tableHead={tableHead} centerTable={centerTable}>
{user.turns.done.map((row, idx) => ( {rows.map((row, idx) => (
<TableRow <TableRow
key={idx} key={idx}
className="!border-0 !border-b !border-[#DBDBDB]" className="!border-0 !border-b !border-[#DBDBDB]"
> >
<TableCell className="!pr-[18px] !text-nowrap" align="center"> <TableCell className="!pr-[18px] !text-nowrap" align="center">
<TextLoading width={15} height={18} loading={loading}> <TextLoading width={15} height={18} loading={loading}>
{idx + 1} {(page - 1) * 10 + idx + 1}
</TextLoading> </TextLoading>
</TableCell> </TableCell>
<TableCell <TableCell
className="!text-start !pr-[18px] !text-nowrap" className="!text-start !pr-[18px] !text-nowrap"
align="right" align="right"
> >
<TextLoading width={30} height={18} loading={loading}> <TextLoading width={120} height={18} loading={loading}>
{row.id} {row.order_id || "--"}
</TextLoading> </TextLoading>
</TableCell> </TableCell>
<TableCell <TableCell
@@ -48,8 +63,8 @@ function List({ user, loading, page, totalPages, onPageChange }) {
component="th" component="th"
scope="row" scope="row"
> >
<TextLoading width={120} height={18} loading={loading}> <TextLoading width={80} height={18} loading={loading}>
{row.appointment_details?.doctor_name || "--"} {TYPE_LABELS[row.type] || row.type || "--"}
</TextLoading> </TextLoading>
</TableCell> </TableCell>
<TableCell <TableCell
@@ -57,7 +72,7 @@ function List({ user, loading, page, totalPages, onPageChange }) {
align="right" align="right"
> >
<TextLoading width={80} height={18} loading={loading}> <TextLoading width={80} height={18} loading={loading}>
{convertTimestampToJalali(row.appointment_details?.start_time)} {convertTimestampToJalali(row.created_at)}
</TextLoading> </TextLoading>
</TableCell> </TableCell>
<TableCell <TableCell
@@ -65,37 +80,16 @@ function List({ user, loading, page, totalPages, onPageChange }) {
align="right" align="right"
> >
<TextLoading width={50} height={18} loading={loading}> <TextLoading width={50} height={18} loading={loading}>
{convertTimestampToTime(row.appointment_details?.start_time)} {row.amount_rials ? Math.round(row.amount_rials / 10).toLocaleString("fa-IR") : "--"}
</TextLoading>
</TableCell>
<TableCell
className="!text-center !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.amount ? row.amount.toLocaleString('fa-IR') : "--"}
</TextLoading> </TextLoading>
</TableCell> </TableCell>
<TableCell className="!pr-[18px] !text-nowrap" align="right"> <TableCell className="!pr-[18px] !text-nowrap" align="right">
<CustomLoading width={80} height={22} loading={loading}> <CustomLoading width={80} height={22} loading={loading}>
<Link href={`/payment/${row.uuid}`}> <Link href={`/payment/${row.uuid}`}>
<div <div
className={` className={`w-[124px] py-[4px] px-[4px] rounded-[4px] text-center text-[16px] font-medium select-none cursor-pointer ${(PAYMENT_STATUS[row.status] || PAYMENT_STATUS.failed).cls}`}
w-[124px] py-[4px] px-[4px] rounded-[4px] text-center text-[16px] font-medium select-none cursor-pointer
${row.status === "received"
? "bg-[#E8FADD] text-[#75E22E]"
: row.status === "pending"
? "bg-[#FFF3DD] text-[#FDBA35]"
: "bg-[#FFE3E2] text-[#FF5450]"
}
`}
variant="text"
> >
{row.status === "received" {(PAYMENT_STATUS[row.status] || { label: row.status }).label}
? "پرداخت شده"
: row.status === "pending"
? "در انتظار"
: "لغو شده"}
</div> </div>
</Link> </Link>
</CustomLoading> </CustomLoading>
@@ -105,7 +99,7 @@ function List({ user, loading, page, totalPages, onPageChange }) {
</CustomTable> </CustomTable>
</div> </div>
<ul className="flex md:hidden flex-col gap-y-[24px]"> <ul className="flex md:hidden flex-col gap-y-[24px]">
{user.turns.done.map((row, idx) => ( {rows.map((row, idx) => (
<Card key={idx} data={row} loading={loading} /> <Card key={idx} data={row} loading={loading} />
))} ))}
</ul> </ul>
@@ -71,7 +71,7 @@ function Transactions({ user, loading }) {
</p> </p>
<Head status={status} setStatus={handleStatusChange} /> <Head status={status} setStatus={handleStatusChange} />
<List <List
user={{ ...user, turns: { done: payments } }} payments={payments}
loading={isLoading} loading={isLoading}
page={page} page={page}
totalPages={totalPages} totalPages={totalPages}