refactor: enhance transaction and appointment handling with pagination, status filtering, and improved data display

This commit is contained in:
hamed
2025-11-18 22:21:47 +03:30
parent 430e0b3b11
commit e7c8501680
9 changed files with 286 additions and 75 deletions
+7 -10
View File
@@ -213,16 +213,13 @@ export default function PaymentDetailsPage() {
بروزرسانی وضعیت
</button>
</>
) : (
payment.frontend_address && (
<button
onClick={() => router.push(payment.frontend_address)}
className="flex-1 bg-[#5559CE] hover:bg-[#4448b3] text-white font-bold py-3 px-6 rounded-lg transition-colors"
>
بازگشت به داشبورد
</button>
)
)}
) : null}
<button
onClick={() => router.push("/dashboard?sidebar=2")}
className="flex-1 bg-[#5559CE] hover:bg-[#4448b3] text-white font-bold py-3 px-6 rounded-lg transition-colors"
>
بازگشت به تراکنشها
</button>
</div>
</div>
</div>
@@ -1,41 +1,41 @@
import CircularLoading from "@/app/component/loading/Circular";
import TextLoading from "@/app/component/loading/Text";
import Image from "next/image";
import { convertTimestampToJalali, convertTimestampToTime } from "@/helper";
import Link from "next/link";
function Card({ data, loading }) {
return (
<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-start gap-[8px]">
<CircularLoading width={32} height={32} loading={loading}>
<Image src={data.image} alt="profile" height={32} width={32} />
</CircularLoading>
<TextLoading width={90} height={18} loading={loading}>
<TextLoading width={120} height={18} loading={loading}>
<p className="text-[#616161] text-[14px] font-medium">
{data.name}
{data.appointment_details?.doctor_name || "--"}
</p>
</TextLoading>
</div>
<TextLoading loading={loading} width={95} height={25}>
<div
className={`
<Link href={`/payment/${data.uuid}`}>
<div
className={`
w-[100px] p-[4px] rounded-[4px] text-center text-[14px] font-medium cursor-pointer select-none
${
data.status === "success"
? "bg-[#E8FADD] text-[#75E22E]"
: data.status === "pending"
? "bg-[#FFF3DD] text-[#FDBA35]"
: "bg-[#FFE3E2] text-[#FF5450]"
}
${data.status === "received"
? "bg-[#E8FADD] text-[#75E22E]"
: data.status === "pending"
? "bg-[#FFF3DD] text-[#FDBA35]"
: "bg-[#FFE3E2] text-[#FF5450]"
}
`}
variant="text"
>
{data.status === "success"
? "پرداخت شده"
: data.status === "pending"
? "در انتظار"
: "پرداخت نشده"}
</div>
variant="text"
>
{data.status === "received"
? "پرداخت شده"
: data.status === "pending"
? "در انتظار"
: "لغو شده"}
</div>
</Link>
</TextLoading>
</div>
<ul className="flex flex-col mt-[16px]">
@@ -54,9 +54,9 @@ function Card({ data, loading }) {
تاریخ نوبت:
</p>
</TextLoading>
<TextLoading loading={loading} width={60} height={18}>
<TextLoading loading={loading} width={80} height={18}>
<p className="text-[#616161] text-[14px] font-medium">
{data.date}
{convertTimestampToJalali(data.appointment_details?.start_time)}
</p>
</TextLoading>
</li>
@@ -67,7 +67,7 @@ function Card({ data, loading }) {
</TextLoading>
<TextLoading loading={loading} width={80} height={18}>
<p className="text-[#616161] text-[14px] font-medium">
{data.time}
{convertTimestampToTime(data.appointment_details?.start_time)}
</p>
</TextLoading>
</li>
@@ -75,12 +75,12 @@ function Card({ data, loading }) {
<li className="flex items-center justify-between gap-[12px]">
<TextLoading loading={loading} width={110} height={18}>
<p className="text-[#7E7E7E] text-[14px] font-normal">
مبلغ پیش پرداخت:
مبلغ:
</p>
</TextLoading>
<TextLoading loading={loading} width={40} height={18}>
<TextLoading loading={loading} width={80} height={18}>
<p className="text-[#616161] text-[14px] font-medium">
{data.amount} تومان
{data.amount ? data.amount.toLocaleString('fa-IR') : "--"} تومان
</p>
</TextLoading>
</li>
@@ -0,0 +1,60 @@
import Tabs from "@/app/component/Tabs";
const listTab = ["همه", "در انتظار", "پرداخت شده", "لغو شده"];
function Head({ status, setStatus }) {
const statusMap = {
0: undefined, // all
1: "pending",
2: "received",
3: "canceled",
};
const handleChange = (e, value) => {
setStatus(statusMap[value]);
};
const getCurrentIndex = () => {
const entries = Object.entries(statusMap);
const found = entries.find(([_, val]) => val === status);
return found ? parseInt(found[0]) : 0;
};
return (
<div className="flex items-center justify-start mt-[24px] sm:mt-[26px] md:mt-[28px] lg:mt-[30px]">
<div className="block w-full">
<Tabs
style={{
".MuiTabs-indicator": {
height: "2px",
borderRadius: "16px",
background: "#5559CE",
},
"& .MuiButtonBase-root": {
color: "#7E7E7E",
fontSize: "14px",
fontStyle: "normal",
fontWeight: "700",
},
"& .MuiTabs-flexContainer": {
paddingRight: "15px",
paddingLeft: "15px",
},
"& .mui-11pdt05-MuiButtonBase-root-MuiTab-root.Mui-selected": {
color: "#5559CE !important",
},
"& .MuiButtonBase-root.Mui-selected": {
color: "#5559CE !important",
},
}}
color="#5559CE"
listTab={listTab}
value={getCurrentIndex()}
handleChange={handleChange}
/>
</div>
</div>
);
}
export default Head;
@@ -5,8 +5,11 @@ import Card from "./Card";
import TextLoading from "@/app/component/loading/Text";
import CustomLoading from "@/app/component/loading/Custom";
import CircularLoading from "@/app/component/loading/Circular";
import { convertTimestampToJalali, convertTimestampToTime } from "@/helper";
import Pagination from "@/app/component/Pagination";
import Link from "next/link";
function List({ user, loading }) {
function List({ user, loading, page, totalPages, onPageChange }) {
const tableHead = [
"ردیف",
"شناسه",
@@ -45,26 +48,16 @@ function List({ user, loading }) {
component="th"
scope="row"
>
<div className="flex items-center justify-start gap-[8px] !text-nowrap">
<CircularLoading width={32} height={32} loading={loading}>
<Image
src={row.image}
alt="doctor"
height={32}
width={32}
/>
</CircularLoading>
<TextLoading width={40} height={18} loading={loading}>
{row.name}
</TextLoading>
</div>
<TextLoading width={120} height={18} loading={loading}>
{row.appointment_details?.doctor_name || "--"}
</TextLoading>
</TableCell>
<TableCell
className="!text-start !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.date}
<TextLoading width={80} height={18} loading={loading}>
{convertTimestampToJalali(row.appointment_details?.start_time)}
</TextLoading>
</TableCell>
<TableCell
@@ -72,7 +65,7 @@ function List({ user, loading }) {
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.time}
{convertTimestampToTime(row.appointment_details?.start_time)}
</TextLoading>
</TableCell>
<TableCell
@@ -80,30 +73,31 @@ function List({ user, loading }) {
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.amount}
{row.amount ? row.amount.toLocaleString('fa-IR') : "--"}
</TextLoading>
</TableCell>
<TableCell className="!pr-[18px] !text-nowrap" align="right">
<CustomLoading width={80} height={22} loading={loading}>
<div
className={`
w-[124px] py-[4px] px-[4px] rounded-[4px] text-center text-[16px] font-medium select-none
${
row.status === "success"
<Link href={`/payment/${row.uuid}`}>
<div
className={`
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 === "success"
? "پرداخت شده"
: row.status === "pending"
? "در انتظار"
: "پرداخت نشده"}
</div>
variant="text"
>
{row.status === "received"
? "پرداخت شده"
: row.status === "pending"
? "در انتظار"
: "لغو شده"}
</div>
</Link>
</CustomLoading>
</TableCell>
</TableRow>
@@ -115,6 +109,15 @@ function List({ user, loading }) {
<Card key={idx} data={row} loading={loading} />
))}
</ul>
{totalPages > 1 && (
<div className="flex justify-center mt-[32px] w-full">
<Pagination
page={page}
totalPages={totalPages}
onPageChange={onPageChange}
/>
</div>
)}
</div>
);
}
@@ -1,12 +1,85 @@
"use client";
import { useEffect, useState } from "react";
import List from "./List";
import Head from "./Head";
import { request } from "@/services/response";
import Cookies from "js-cookie";
function Transactions({ user, loading }) {
const [payments, setPayments] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [status, setStatus] = useState(undefined); // undefined = all, 'pending', 'success', 'failed'
useEffect(() => {
const fetchPayments = async () => {
setIsLoading(true);
try {
const userInfo = Cookies.get("userInfo");
if (!userInfo) {
setIsLoading(false);
return;
}
const parsedData = JSON.parse(userInfo);
const userId = parsedData.id || parsedData.uuid;
if (!userId) {
setIsLoading(false);
return;
}
const params = {
page,
limit: 10,
};
if (status) {
params.status = status;
}
const response = await request.getMyPayments(userId, params);
if (response?.data) {
setPayments(response.data);
if (response.page) {
setTotalPages(response.page.totalPages);
}
}
} catch (error) {
console.error("Error fetching payments:", error);
} finally {
setIsLoading(false);
}
};
fetchPayments();
}, [page, status]);
const handlePageChange = (event, value) => {
setPage(value);
};
const handleStatusChange = (newStatus) => {
setStatus(newStatus);
setPage(1); // Reset to first page when status changes
};
return (
<div className="opacity-page">
<p className="text-[#3B3B3B] hidden md:block text-[16px] font-bold">
تاریخچه تراکنش ها
</p>
<List user={user} loading={loading} />
<Head status={status} setStatus={handleStatusChange} />
<List
user={{ ...user, turns: { done: payments } }}
loading={isLoading}
page={page}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
);
}
@@ -6,8 +6,9 @@ import Card from "./Card";
import CircularLoading from "@/app/component/loading/Circular";
import TextLoading from "@/app/component/loading/Text";
import CustomLoading from "@/app/component/loading/Custom";
import Pagination from "@/app/component/Pagination";
function List({ user, loading, setIsTurnsDetails }) {
function List({ user, loading, setIsTurnsDetails, page, totalPages, onPageChange }) {
const tableHead = [
"ردیف",
"نام پزشک",
@@ -110,6 +111,15 @@ function List({ user, loading, setIsTurnsDetails }) {
/>
))}
</ul>
{totalPages > 1 && (
<div className="flex justify-center mt-[32px] w-full">
<Pagination
page={page}
totalPages={totalPages}
onPageChange={onPageChange}
/>
</div>
)}
</div>
);
}
@@ -1,9 +1,60 @@
import { useState } from "react";
"use client";
import { useEffect, useState } from "react";
import Head from "./Head";
import List from "./List";
import { request } from "@/services/response";
import Cookies from "js-cookie";
function Turns({ user, setIsTurnsDetails, loading }) {
const [isDone, setIsDone] = useState(true);
const [appointments, setAppointments] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
useEffect(() => {
const fetchAppointments = async () => {
setIsLoading(true);
try {
const userInfo = Cookies.get("userInfo");
if (!userInfo) {
setIsLoading(false);
return;
}
const parsedData = JSON.parse(userInfo);
const userId = parsedData.id || parsedData.uuid;
if (!userId) {
setIsLoading(false);
return;
}
const response = await request.getMyAppointments(userId, {
page,
limit: 10,
});
if (response?.data) {
setAppointments(response.data);
if (response.page) {
setTotalPages(response.page.totalPages);
}
}
} catch (error) {
console.error("Error fetching appointments:", error);
} finally {
setIsLoading(false);
}
};
fetchAppointments();
}, [page]);
const handlePageChange = (event, value) => {
setPage(value);
};
return (
<div className="opacity-page">
@@ -12,9 +63,12 @@ function Turns({ user, setIsTurnsDetails, loading }) {
</p>
<Head isDone={isDone} setIsDone={setIsDone} />
<List
loading={loading}
loading={isLoading}
setIsTurnsDetails={setIsTurnsDetails}
user={user.turns[isDone ? "done" : "current"]}
user={appointments}
page={page}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
);
+12
View File
@@ -23,6 +23,18 @@ export const convertToJalali = (date) => {
return `${jalaliYear}/${jalaliMonth}/${jalaliDay}`;
};
export const convertTimestampToJalali = (timestamp) => {
if (!timestamp) return "--";
const date = moment.unix(timestamp);
return date.format("jYYYY/jMM/jDD");
};
export const convertTimestampToTime = (timestamp) => {
if (!timestamp) return "--";
const date = moment.unix(timestamp);
return date.format("HH:mm");
};
export const handleTwoLength = (value) =>
String(value).length === 1 ? `0${value}` : value;
+2
View File
@@ -67,8 +67,10 @@ export const request = {
removeTokenHead
),
postAppointment: (data) => api.post(`api/v1/appointment`, data, { requireAuth: true }),
getMyAppointments: (userId, params) => api.get(`api/v1/appointment/my-appointments/${userId}`, { params, requireAuth: true }),
postPayment: (data) => api.post(`api/v1/payment`, data, { requireAuth: true }),
getPayment: (uuid) => api.get(`api/v1/payment/${uuid}`, { requireAuth: true }),
getMyPayments: (userId, params) => api.get(`api/v1/payment/my-payments/${userId}`, { params, requireAuth: true }),
postDoctor: (data) => api.post("api/v1/doctor", data),
postImageUpload: (data) =>
api.post("file/upload/clinic_pro/doctor/field_image", data, {