add state of page in url, add location and working_days on list in clinics page, fix update list clinic with page and limit value, hide pagination when list of clinic is empty, round image of doctor and appo in doctor item

This commit is contained in:
ehsan
2025-09-30 15:58:31 +03:30
parent ae8c49bd07
commit 645da72002
22 changed files with 98 additions and 136 deletions
+1 -5
View File
@@ -24,11 +24,7 @@ export default async function Clinics({ searchParams }) {
else if (matchedCity && matchedCity.id !== "600")
newSearchParams.city = matchedCity.name;
const params = {
...buildClinicParams(newSearchParams),
page: 1,
limit: 12,
};
const params = buildClinicParams(newSearchParams);
// Req
clinics = await fetchReq(`${API_URL}/api/v1/clinics`, {
+1 -1
View File
@@ -11,7 +11,7 @@ import ArrowLeftD from "@/components/icons/ArrowLeftD";
import ClockD from "@/components/icons/ClockD";
import LikeD from "@/components/icons/LikeD";
import PointD from "@/components/icons/PointD";
import { convertTimestampToPersianDate, convertTimestampToPersianDateSimple } from "@/components/doctors/listDoctors/timestamp_to_persian_date";
import { convertTimestampToPersianDateSimple } from "@/helper";
moment.loadPersian({ dialect: "persian-modern" });
+1 -1
View File
@@ -9,7 +9,7 @@ function PaginationContent({ page, pageDetail, changePage, limit = 12 }) {
page={page || 1}
onChange={changePage}
color="primary"
className={count < 2 && "!hidden"}
className={(!count || count < 2) && "!hidden"}
sx={{
"& .mui-8q7g72-MuiButtonBase-root-MuiPaginationItem-root": {
color: "#616161",
+2 -9
View File
@@ -3,21 +3,14 @@ import Layout from "@/components/layout/StLayout";
import axios from "axios";
async function Doctor({ params: { slug } }) {
let doctors = null;
let doctor = null;
let comments = null;
const API_URL = process.env.NEXT_PUBLIC_API_URL;
try {
const resDoctors = await axios.get(
`${API_URL}/api/v1/doctors?active=1`
);
const resDoctor = await axios.get(
`${API_URL}/api/v1/doctor/${slug}`
);
const resDoctor = await axios.get(`${API_URL}/api/v1/doctor/${slug}`);
doctor = resDoctor.data;
doctors = resDoctors.data.data;
if (doctor) {
const resComments = await axios.get(
`${API_URL}/api/v1/clinicpro/comments/${doctor.id}`
@@ -31,7 +24,7 @@ async function Doctor({ params: { slug } }) {
return (
<Layout>
<DoctorPage doctor={doctor} doctors={doctors} comments={comments} />
<DoctorPage doctor={doctor} comments={comments} />
</Layout>
);
}
+10 -2
View File
@@ -1,13 +1,21 @@
import { useState } from "react";
import { Button } from "@mui/material";
import SearchD from "@/components/icons/SearchD";
import { useRouter } from "next/navigation";
function FilterButton({ filterData }) {
function FilterButton({ limit, selected, filterData }) {
const router = useRouter();
const [loading, setLoading] = useState(false);
const sendReq = () => {
setLoading(true);
filterData().finally(() => setLoading(false));
const newSelected = { ...selected, page: 1, limit };
filterData(newSelected).finally(() => {
setLoading(false);
const searchParams = new URLSearchParams(window.location.search);
searchParams.set("page", 1);
router.push(`?${searchParams.toString()}`);
});
};
return (
+3 -1
View File
@@ -12,6 +12,7 @@ import { request } from "@/services/response";
function SearchBar({
state,
limit,
selected,
specialties,
setSelected,
@@ -59,6 +60,7 @@ function SearchBar({
>
<ModalSearchCity
open={open}
limit={limit}
state={state}
setOpen={setOpen}
selected={selected}
@@ -73,7 +75,7 @@ function SearchBar({
placeholder="جستجوی تخصص"
handleSearch={handleSearch}
/>
<FilterButton filterData={filterData} />
<FilterButton limit={limit} selected={selected} filterData={filterData} />
</ButtonGroup>
);
}
+10 -4
View File
@@ -1,13 +1,14 @@
import { Button } from "@mui/material";
import { useState } from "react";
// Icon
import { Button } from "@mui/material";
import CloseModalD from "@/components/icons/CloseModalD";
import ArrowLeftM from "@/components/icons/ArrowLeftM";
import AutoComplete from "./AutoComplete";
import { useState } from "react";
import { useRouter } from "next/navigation";
function Content({
state,
limit,
states,
selected,
updateData,
@@ -16,6 +17,7 @@ function Content({
filteredCities,
handleSearch,
}) {
const router = useRouter();
const [loading, setLoading] = useState(false);
const listboxProps = {
style: {
@@ -27,8 +29,12 @@ function Content({
const handleFilter = () => {
handleSearch && handleSearch("");
setLoading(true);
filterData().finally(() => {
const newSelected = { ...selected, page: 1, limit };
filterData(newSelected).finally(() => {
handleClose();
const searchParams = new URLSearchParams(window.location.search);
searchParams.set("page", 1);
router.push(`?${searchParams.toString()}`);
setLoading(false);
});
};
+2
View File
@@ -13,6 +13,7 @@ import { useRouter } from "next/navigation";
function ModalSearchCity({
open,
state,
limit,
setOpen,
children,
selected,
@@ -67,6 +68,7 @@ function ModalSearchCity({
<Modal open={open} onClose={handleClose}>
<Box sx={styleDefault} className="!w-full !h-full md:!w-fit md:!h-fit">
<Content
limit={limit}
state={state}
states={states}
selected={selected}
+3 -2
View File
@@ -14,7 +14,7 @@ import cities from "@/data/city.json";
function ClinicsPage({ clinics, matchedCity, matchedState }) {
const searchParams = useSearchParams();
const [filteredClinics, setFilteredClinics] = useState(clinics.data);
const [filteredClinics, setFilteredClinics] = useState(clinics?.data);
const selectedState = searchParams.get("state") || matchedState?.name;
const selectedCity =
@@ -37,6 +37,7 @@ function ClinicsPage({ clinics, matchedCity, matchedState }) {
detail="در قسمت زیر می توانید کلینیک مورد نظر خود را جستجو نمایید"
>
<SearchBar
limit={12}
selected={selected}
state={matchedState}
setSelected={setSelected}
@@ -47,7 +48,7 @@ function ClinicsPage({ clinics, matchedCity, matchedState }) {
<List
limit={12}
selected={selected}
pages={clinics.page}
pages={clinics?.page || []}
clinics={filteredClinics}
setFilteredClinics={setFilteredClinics}
/>
+2 -2
View File
@@ -48,7 +48,7 @@ function ItemClinic({ setFilteredClinics, data, loading }) {
<LocationClinic />
<TextLoading loading={loading} width={150} height={18}>
<p className="text-[#7E7E7E] text-[12px] font-normal">
{data.location}
{data.address}
</p>
</TextLoading>
</div>
@@ -56,7 +56,7 @@ function ItemClinic({ setFilteredClinics, data, loading }) {
<ClockClinic />
<TextLoading loading={loading} width={120} height={18}>
<p className="text-[#7E7E7E] text-[12px] font-normal">
ساعت کاری: {data.hours_of_work}
ساعت کاری: {data.field_working_days}
</p>
</TextLoading>
</div>
+5
View File
@@ -4,14 +4,19 @@ import { QueryForClinicsFilter } from "@/helper";
import Pageguide from "@/app/component/Pageguide";
import PaginationContent from "@/app/component/PaginationContent";
import { request } from "@/services/response";
import { useRouter } from "next/navigation";
function List({ limit, selected, clinics, pages, setFilteredClinics }) {
const listPage = ["کلینیک ها", selected?.city?.name || ""];
const [page, setPage] = useState(pages?.currentPage);
const [loading, setLoading] = useState(false);
const router = useRouter();
const changePage = (_, num) => {
setLoading(true);
const searchParams = new URLSearchParams(window.location.search);
searchParams.set("page", num);
router.push(`?${searchParams.toString()}`);
setPage(num);
const params = {
@@ -19,7 +19,13 @@ function ItemAppointment({ turn, loading }) {
<span className="block w-full h-px bg-[#EFEFEF] my-4"></span>
<div className="flex items-center justify-start gap-3">
<CircularLoading width={56} height={56} loading={loading}>
<Image width={56} height={56} alt="doctor" src={turn?.img[0]?.url} />
<Image
className="rounded-full"
width={56}
height={56}
alt="doctor"
src={turn?.img[0]?.url}
/>
</CircularLoading>
<div className="flex flex-col items-start justify-center gap-2">
<TextLoading width={60} height={15} loading={loading}>
-2
View File
@@ -1,5 +1,3 @@
import { useRef } from "react";
// Icons
import CopyBlueD from "@/components/icons/CopyBlueD";
import DownloadBlue from "@/components/icons/DownloadBlue";
+2 -4
View File
@@ -1,12 +1,9 @@
"use client";
import { useRef, useState } from "react";
import { styleDefault } from "@/mui";
import ShareDI from "@/components/icons/ShareDI";
import { Box, Button, Modal } from "@mui/material";
// Icons
import CloseModalD from "@/components/icons/CloseModalD";
import List from "./List";
import Poster from "../poster";
@@ -28,7 +25,8 @@ function Share({ data }) {
<ShareDI />
<p className="hidden lg:flex">اشتراک گذاری</p>
</Button>
<div className="fixed top-0 left-0 opacity-0 pointer-events-none -z-10">
{/* <div className="fixed top-0 left-0 opacity-0 pointer-events-none -z-10"> */}
<div className="">
<div ref={hiddenRef}>
<Poster data={data} />
</div>
+1 -1
View File
@@ -12,7 +12,7 @@ function Title({ doctor }) {
<CircularLoading width={100} height={100}>
<Image
className="
object-contain
object-contain rounded-full
w-[40px] sm:w-[81px] md:w-[123px] lg:w-[164px]
h-[40px] sm:h-[81px] md:h-[123px] lg:h-[164px]
"
+1 -1
View File
@@ -3,7 +3,7 @@ import DetailDoctor from "./detailDoctor";
import Share from "./detailDoctor/Share";
import CustomLoading from "@/app/component/loading/Custom";
function DoctorPage({ doctor, doctors, comments }) {
function DoctorPage({ doctor, comments }) {
return (
<div className="pt-[92px] sm:pt-[120px] mt:pt-[148px] lg:pt-[176px] mt-[px] padding-responsive">
<div className="flex items-center justify-between">
+2 -2
View File
@@ -7,8 +7,8 @@ function Address({ data }) {
<ClipBoard />
<p className="text-[#E7EEF6] text-[20px] font-semibold">آدرس:</p>
</div>
<p className="text-[#E7EEF6] text-[16px] !font-normal mt-[16px]">
{data?.location}
<p className="text-[#E7EEF6] text-[16px] !font-normal mt-[16px] text-right">
{data?.address[0]?.address}
</p>
</div>
);
+7 -1
View File
@@ -3,7 +3,13 @@ import Image from "next/image";
function ImageProfile({ data }) {
return (
<div className="bg-stroke-circle ml-[10px] mt-[57px] p-[9px] rounded-full overflow-hidden">
<Image src={data?.img} width={173} height={173} alt="profile-user" />
<Image
src={data?.img[0]?.url}
width={173}
height={173}
alt="profile-user"
className="rounded-full"
/>
</div>
);
}
+5 -5
View File
@@ -2,17 +2,17 @@ import TelefonPoster from "@/components/icons/TelefonPoster";
function Telefon({ data }) {
return (
<div className="flex flex-col items-start justify-start mt-[40px]">
<div className={`flex-col items-start justify-start mt-[40px] ${data?.address[0]?.phone ? 'flex' : 'hidden'}`}>
<div className="flex items-center justify-start gap-[6px]">
<TelefonPoster />
<p className="text-[#E7EEF6] text-[20px] font-semibold">تلفن:</p>
</div>
<p className="text-[#E7EEF6] text-[16px] font-normal mt-[16px]">
{data?.phone}
</p>
<p className="text-[#E7EEF6] text-[16px] font-normal mt-[12px]">
{data?.phone} - {data?.phone}
{data?.address[0]?.phone}
</p>
{/* <p className="text-[#E7EEF6] text-[16px] font-normal mt-[12px]">
{data?.address[0]?.phone} - {data?.address[0]?.phone}
</p> */}
</div>
);
}
+2 -2
View File
@@ -14,10 +14,10 @@ function Poster({ data }) {
<Image src={Logo} width={30} height={30} alt="logo" />
<p className="text-[#E7EEF6] text-[16px] font-bold">nobat724.com</p>
</div>
<p className="mt-[40px] text-[#E7EEF6] text-[20px] font-bold">
<p className="mt-[40px] text-[#E7EEF6] text-[20px] font-bold text-right">
{data?.name}
</p>
<p className="text-[24px] mt-[20px] text-[#E7EEF6] font-bold">
<p className="text-[24px] mt-[20px] text-[#E7EEF6] font-bold text-right">
تخصص:
{data?.expertise?.map(
(item, idx) =>
@@ -1,90 +0,0 @@
export function convertTimestampToPersianDate(timestamp) {
// Convert timestamp to milliseconds (JavaScript uses milliseconds)
const date = new Date(timestamp * 1000);
// Format with Persian calendar and Tehran timezone
const options = {
timeZone: "Asia/Tehran",
calendar: "persian",
year: "numeric",
month: "2-digit",
day: "2-digit",
weekday: "long",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: true,
};
const formatter = new Intl.DateTimeFormat("fa-IR", options);
const parts = formatter.formatToParts(date);
// Extract parts
const weekday = parts.find((part) => part.type === "weekday").value;
const year = parts.find((part) => part.type === "year").value;
const month = parts.find((part) => part.type === "month").value;
const day = parts.find((part) => part.type === "day").value;
const hour = parts.find((part) => part.type === "hour").value;
const minute = parts.find((part) => part.type === "minute").value;
const second = parts.find((part) => part.type === "second").value;
const dayPeriod = parts.find((part) => part.type === "dayPeriod").value;
// Format the final string
return `${weekday}, ${year}-${month}-${day} ${hour}:${minute}:${second} ${dayPeriod}`;
}
// Alternative simpler approach using toLocaleString
export function convertTimestampToPersianDateSimple(timestamp) {
const date = new Date(timestamp * 1000);
const options = {
timeZone: "Asia/Tehran",
calendar: "persian",
year: "numeric",
month: "2-digit",
day: "2-digit",
weekday: "long",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: true,
};
return date.toLocaleString("fa-IR", options);
}
// Example usage
const timestamp = 1759132200;
console.log("Method 1 (formatToParts):");
console.log(convertTimestampToPersianDate(timestamp));
console.log("\nMethod 2 (toLocaleString):");
console.log(convertTimestampToPersianDateSimple(timestamp));
// For more control over the format, you can create a custom formatter
export function convertTimestampCustomFormat(timestamp) {
const date = new Date(timestamp * 1000);
const options = {
timeZone: "Asia/Tehran",
calendar: "persian",
year: "numeric",
month: "2-digit",
day: "2-digit",
weekday: "long",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: true,
};
const formatter = new Intl.DateTimeFormat("fa-IR", options);
const formatted = formatter.format(date);
// Add bullet point if needed
return `* ${formatted}`;
}
console.log("\nMethod 3 (with bullet point):");
console.log(convertTimestampCustomFormat(timestamp));
+31
View File
@@ -390,6 +390,12 @@ export const QueryForClinicsFilter = (searchParams) => {
}
}
// 🔹 page
if (searchParams.page) params.page = searchParams.page;
// 🔹 limit
if (searchParams.limit) params.limit = searchParams.limit;
return params;
};
@@ -493,6 +499,12 @@ export const buildClinicParams = (searchParams) => {
}
}
// 🔹 page
if (searchParams.page) {
params.page = searchParams.page;
params.limit = 12;
}
return params;
};
@@ -525,3 +537,22 @@ export function isUserLoggedIn() {
const userInfo = Cookies.get("userInfo");
return !!userInfo;
}
export function convertTimestampToPersianDateSimple(timestamp) {
const date = new Date(timestamp * 1000);
const options = {
timeZone: "Asia/Tehran",
calendar: "persian",
year: "numeric",
month: "2-digit",
day: "2-digit",
weekday: "long",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: true,
};
return date.toLocaleString("fa-IR", options);
}