loading ,page
This commit is contained in:
@@ -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}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
// ✅ 1. مقدار در URL ست بشه
|
||||||
// // const newFilter = checkName();
|
if (setDataInURL) {
|
||||||
// // setFilter(filter);
|
setDataInURL(filter);
|
||||||
// // setDataInURL(filter);
|
}
|
||||||
|
|
||||||
sendReq(filter).finally(() => {
|
// ✅ 2. درخواست به بکاند بره
|
||||||
setLoading(false);
|
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}
|
||||||
|
|||||||
@@ -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
@@ -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 },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user