refactor: enhance transaction and appointment handling with pagination, status filtering, and improved data display
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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, {
|
||||
|
||||
Reference in New Issue
Block a user