loading ,page

This commit is contained in:
Arezoo
2025-10-27 10:58:26 +03:30
parent 8ab33b4e93
commit 79ff139779
9 changed files with 136 additions and 140 deletions
+4 -3
View File
@@ -2,15 +2,16 @@ import ClinicPage from "@/components/clinic";
import Layout from "@/components/layout/StLayout"; import Layout from "@/components/layout/StLayout";
import { fetchReq } from "@/lib/req"; import { fetchReq } from "@/lib/req";
async function Clinic({ params: { slug } }) { async function Clinic({ params: { slug },searchParams }) {
const API_URL = process.env.NEXT_PUBLIC_API_URL; const API_URL = process.env.NEXT_PUBLIC_API_URL;
const page = Number(searchParams?.page) || 1;
const reqClinics = await fetchReq( const reqClinics = await fetchReq(
`${API_URL}/api/v1/clinic/${slug}` `${API_URL}/api/v1/clinic/${slug}`
); );
const limit=50 const limit=50
const reqDoctors = await fetchReq( const reqDoctors = await fetchReq(
`${API_URL}/api/v1/clinic/doctor-list/${slug}?page=1&limit=${limit}` `${API_URL}/api/v1/clinic/doctor-list/${slug}?page=${page}&limit=${limit}`
); );
+37 -16
View File
@@ -20,34 +20,50 @@ function List({
const router = useRouter(); const router = useRouter();
const changePage = async (_, num) => { const changePage = async (_, num) => {
setLoading(true); setLoading(true);
window.scrollTo({ top: 0, behavior: "smooth" });
const searchParams = new URLSearchParams(window.location.search); const searchParams = new URLSearchParams(window.location.search);
searchParams.set("page", num); searchParams.set("page", num);
router.push(`/clinic/${slug}?${searchParams.toString()}`); router.push(`/clinic/${slug}?${searchParams.toString()}`);
setPage(num); setPage(num);
let params = {
...QueryForDoctorsClinicReq(filter),
current: num,
limit,
};
try { try {
const doctors = await getClinicDoctors(slug, params); let params = {
setDoctors(doctors.data); ...QueryForDoctorsClinicReq(filter),
setTotalPage(doctors?.page?.total_pages || 1); page: num,
limit,
};
const res = await getClinicDoctors(slug, params);
console.log("📦 پاسخ API:", res);
// اگر سرور داده را در data برمی‌گرداند
if (res?.data) {
setDoctors([...res.data]);
setTotalPage(res?.page?.total_pages || 1);
}
// اگر فقط آرایه برمی‌گرداند
else if (Array.isArray(res)) {
setDoctors([...res]);
setTotalPage(1);
}
setPage(num); setPage(num);
} catch (error) {
console.error("❌ خطا در دریافت داده:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const doctorList = Array.isArray(doctors?.data) ? doctors.data : doctors; const doctorList = Array.isArray(doctors)
? doctors
: Array.isArray(doctors?.data)
? doctors.data
: [];
return ( return (
<> <>
{loading ? ( { doctorList?.length === 0 ? (
<div className="flex justify-center items-center py-20">
<div className="w-10 h-10 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
</div>
) : doctorList?.length === 0 ? (
<div className="text-center text-gray-500 mt-10"> <div className="text-center text-gray-500 mt-10">
دکتری مطابق فیلتر شما یافت نشد دکتری مطابق فیلتر شما یافت نشد
</div> </div>
@@ -55,7 +71,12 @@ function List({
<> <>
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 mt-[40px] gap-[16px] md:gap-[20px] lg:gap-[24px]"> <ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 mt-[40px] gap-[16px] md:gap-[20px] lg:gap-[24px]">
{doctorList?.map((doctor) => ( {doctorList?.map((doctor) => (
<ItemDoctor key={doctor.id} doctor={doctor} /> <ItemDoctor
loading={loading}
setDoctors={setDoctors}
key={doctor.id}
doctor={doctor}
/>
))} ))}
</ul> </ul>
<div className="mt-[56px] flex justify-center"> <div className="mt-[56px] flex justify-center">
@@ -57,9 +57,10 @@ function Head({ setDoctors, slug, filter, setFilter,setPage,setTotalPage }) {
let params = QueryForDoctorsClinicReq(data); let params = QueryForDoctorsClinicReq(data);
if ( if (
data?.name === data.specialty?.name || data?.specialty &&
data?.name === data.category?.name (data?.name === data.specialty?.name ||
data?.name === data.category?.name)
) { ) {
delete params.name; delete params.name;
} }
@@ -71,7 +72,7 @@ function Head({ setDoctors, slug, filter, setFilter,setPage,setTotalPage }) {
const doctors = await getClinicDoctors(slug, params); const doctors = await getClinicDoctors(slug, params);
// تنظیم داده‌ها در state // تنظیم داده‌ها در state
setDoctors(doctors); setDoctors(doctors.data);
// ✅ حالا که جواب API اومده، مقدار صفحات مشخصه // ✅ حالا که جواب API اومده، مقدار صفحات مشخصه
if (doctors?.page) { if (doctors?.page) {
@@ -2,71 +2,61 @@ import { Autocomplete, TextField } from "@mui/material";
import { useRef, useState, useEffect } from "react"; import { useRef, useState, useEffect } from "react";
function AutoComplete({ function AutoComplete({
data, data = [], // همیشه آرایه ایمن
noneBg, noneBg = false,
clearText, clearText,
inputValue, inputValue = "",
placeholder, placeholder = "",
handleSearch, handleSearch,
setInputValue, debounce = 400,
setSelectedOption,
debounce = 500,
}) { }) {
const typingTimeoutRef = useRef(null); const typingTimeoutRef = useRef(null);
const [localInput, setLocalInput] = useState(inputValue || ""); const [localInput, setLocalInput] = useState(inputValue);
// sync بین inputValue بیرونی و مقدار محلی
useEffect(() => { useEffect(() => {
setLocalInput(inputValue || ""); if (inputValue !== localInput) setLocalInput(inputValue || "");
}, [inputValue]); }, [inputValue]);
// پاک‌سازی تایمر هنگام خروج
useEffect(() => { useEffect(() => {
return () => { return () => {
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current); if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
}; };
}, []); }, []);
const onInputChange = (_, newInputValue) => { // تایپ در input
setLocalInput(newInputValue); const onInputChange = (_, newValue) => {
setLocalInput(newValue);
if (setInputValue) setInputValue(newInputValue);
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current); if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
typingTimeoutRef.current = setTimeout(() => { typingTimeoutRef.current = setTimeout(() => {
handleSearch(newInputValue); handleSearch(newValue);
}, debounce); }, debounce);
}; };
// انتخاب از بین گزینه‌ها
const onChange = (_, newValue) => { const onChange = (_, newValue) => {
if (typingTimeoutRef.current) { if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
clearTimeout(typingTimeoutRef.current); const val = typeof newValue === "string" ? newValue : newValue?.name || "";
typingTimeoutRef.current = null;
}
const val = newValue
? typeof newValue === "string"
? newValue
: newValue.name
: "";
setLocalInput(val); setLocalInput(val);
setSelectedOption && setSelectedOption(newValue);
handleSearch(val); handleSearch(val);
if (setInputValue) setInputValue(val);
}; };
return ( return (
<Autocomplete <Autocomplete
freeSolo freeSolo
options={data}
getOptionLabel={(option) =>
typeof option === "string" ? option : option.name
}
disableClearable disableClearable
options={Array.isArray(data) ? data : []}
getOptionLabel={(option) =>
typeof option === "string" ? option : option.name || ""
}
inputValue={localInput} inputValue={localInput}
className="!w-full"
onInputChange={onInputChange} onInputChange={onInputChange}
onChange={onChange} onChange={onChange}
noOptionsText="پزشکی یافت نشد"
className="!w-full"
renderOption={(props, option) => ( renderOption={(props, option) => (
<li {...props} key={option.id}> <li {...props} key={option.id || option.name}>
{option.name} {option.name}
</li> </li>
)} )}
@@ -1,79 +1,59 @@
"use client";
import { clearFilterParams } from "@/helper"; import { clearFilterParams } from "@/helper";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import specialties from "@/data/specialties.json"; import { useRef } from "react";
import AutoComplete from "./AutoComplete"; import AutoComplete from "./AutoComplete";
function SearchField({ filter, setFilter, setDataInURL }) { function SearchField({ filter, setFilter, setDataInURL, sendReq }) {
const router = useRouter(); const router = useRouter();
const typingTimeoutRef = useRef(null);
// ✅ پاک کردن فیلد سرچ
const clearText = () => { const clearText = () => {
clearFilterParams(router, ["name", "category", "specialty"]); clearFilterParams(router, ["name"]);
const newFilter = { const newFilter = { ...filter, name: "" };
...filter,
...{
name: "",
category: undefined,
specialty: undefined,
},
};
setFilter(newFilter); setFilter(newFilter);
setDataInURL(newFilter);
sendReq(newFilter); // جستجو مجدد بدون نام پزشک
}; };
const handleSearch = (e) => { // ✅ فقط جستجوی پزشک بر اساس name
const findName = specialties.find((item) => item.name === e); const handleSearch = (value) => {
const newFilter = { ...filter, name: value };
const searchParams = new URLSearchParams(window.location.search); const searchParams = new URLSearchParams(window.location.search);
let dataUrl = {};
if (findName) {
let newFilter = { ...filter };
if (findName.parent) {
const itemParent = specialties.find(
(item) => item.id === findName.parent
);
dataUrl.specialty = findName.name;
newFilter.category = itemParent;
newFilter.specialty = findName;
} else {
dataUrl.specialty = findName.name;
newFilter.category = findName;
newFilter.specialty = "";
}
// اگر کاربر پاک کرد
if (!value.trim()) {
searchParams.delete("name"); searchParams.delete("name");
Object.entries(dataUrl).forEach(([key, value]) => {
if (value) {
searchParams.set(key, value);
}
});
router.push(`?${searchParams.toString()}`); router.push(`?${searchParams.toString()}`);
newFilter.name = e;
setFilter(newFilter);
} else {
let newFilter = filter;
if (
filter.name === filter.specialty?.name ||
filter.name === filter.category?.name
) {
searchParams.delete("specialty");
newFilter.specialty = "";
newFilter.category = "";
router.push(`?${searchParams.toString()}`);
}
newFilter.name = e;
setFilter(newFilter); setFilter(newFilter);
setDataInURL(newFilter); setDataInURL(newFilter);
sendReq(newFilter);
return;
} }
// مقدار name در URL قرار می‌گیرد
searchParams.set("name", value);
router.push(`?${searchParams.toString()}`);
setFilter(newFilter);
setDataInURL(newFilter);
// جلوگیری از درخواست زیاد
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
typingTimeoutRef.current = setTimeout(() => {
sendReq(newFilter); // ارسال درخواست به بک‌اند
}, 400);
}; };
return ( return (
<div className="!min-w-[50%] !w-full"> <div className="!min-w-[50%] !w-full">
<AutoComplete <AutoComplete
data={specialties} data={[]} // داده از بک‌اند میاد، نیازی به local data نیست
clearText={clearText} clearText={clearText}
inputValue={filter.name} inputValue={filter.name}
handleSearch={handleSearch} handleSearch={handleSearch}
placeholder="جستجوی نام پزشک، تخصص، بیماری ..." placeholder="جست‌وجوی نام پزشک..."
/> />
</div> </div>
); );
@@ -5,39 +5,28 @@ import specialties from "@/data/specialties.json";
function SendReq({ filter, setFilter, setDataInURL, sendReq }) { function SendReq({ filter, setFilter, setDataInURL, sendReq }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// const checkName = () => {
// let newFilter = filter;
// const specialtyItem =
// filter.name &&
// specialties.find((item) => item.name.includes(filter.name));
// if (specialtyItem) {
// if (specialtyItem.parent) {
// newFilter.specialty = specialtyItem;
// newFilter.category = specialties.find(
// (item) => item.id === specialtyItem.parent
// );
// } else {
// newFilter.specialty = specialtyItem;
// }
// }
// return newFilter;
// };
const filterData = () => { const filterData = () => {
if (!filter?.name?.trim()) {
// اگر کاربر چیزی ننوشته، فقط کل لیست رو بگیر
setDataInURL(filter);
sendReq(filter);
return;
}
setLoading(true); setLoading(true);
// setLoading(true);
// // const newFilter = checkName();
// // setFilter(filter);
// // setDataInURL(filter);
sendReq(filter).finally(() => { // ✅ 1. مقدار در URL ست بشه
setLoading(false); if (setDataInURL) {
}); setDataInURL(filter);
}
// ✅ 2. درخواست به بک‌اند بره
sendReq(filter)
.catch((err) => console.error("❌ خطا در جستجو:", err))
.finally(() => {
setLoading(false); // ✅ 3. بعد از پاسخ، loading خاموش
});
}; };
return ( return (
<Button <Button
loading={loading} loading={loading}
@@ -44,6 +44,7 @@ function SearchBar({ slug,filter, sendReq, setFilter, updateData, setDataInURL,s
filter={filter} filter={filter}
setFilter={setFilter} setFilter={setFilter}
setDataInURL={setDataInURL} setDataInURL={setDataInURL}
sendReq={sendReq}
/> />
<SendReq <SendReq
filter={filter} filter={filter}
+7 -1
View File
@@ -17,8 +17,14 @@ function ListDoctors({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const router = useRouter(); const router = useRouter();
const changePage = (_, num) => { const changePage =async (_, num) => {
setLoading(true); setLoading(true);
// 👇 اسکرول به بالا با انیمیشن
window.scrollTo({ top: 0, behavior: "smooth" });
// 👇 کمی تاخیر برای نمایش نرم‌تر
await new Promise((res) => setTimeout(res, 300));
const searchParams = new URLSearchParams(window.location.search); const searchParams = new URLSearchParams(window.location.search);
searchParams.set("page", num); searchParams.set("page", num);
router.push(`?${searchParams.toString()}`); router.push(`?${searchParams.toString()}`);
+10 -3
View File
@@ -16,12 +16,19 @@ export async function getClinicDoctors(slug, params = {}) {
} }
const json = await response.json(); const json = await response.json();
return {
// ✅ خروجی همیشه ساختار ثابت دارد
return {
data: json?.data || [], data: json?.data || [],
page: json?.page || json?.pages || { total_pages: 1, current: 1 }, page: json?.page || json?.pages || { total_pages: 1, current: 1 },
}; };
} catch (error) { } catch (error) {
console.error("خطا در دریافت دکترهای کلینیک:", error); console.error("خطا در دریافت دکترهای کلینیک:", error);
return [];
// ✅ ساختار خروجی در حالت خطا هم مثل حالت عادی
return {
data: [],
page: { total_pages: 1, current: 1 },
};
} }
} }