feat(appointment): grey out unavailable days on the booking calendar
DatePicker now loads month-availability for the two displayed Jalali months (mapped to the Gregorian months they span, fetched once and cached per month) and disables any day in disabled_dates plus past days. Auto-select skips disabled days. When the doctor has online booking turned off, show a notice instead of the calendar. doctorUuid comes from the route params. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,75 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
import moment from "moment-jalaali";
|
import moment from "moment-jalaali";
|
||||||
import "moment-timezone";
|
import "moment-timezone";
|
||||||
import { dateToTimestamp } from "@/helper";
|
import { dateToTimestamp } from "@/helper";
|
||||||
|
import { request } from "@/services/response";
|
||||||
import InlineJalaliMonth from "@/components/common/InlineJalaliMonth";
|
import InlineJalaliMonth from "@/components/common/InlineJalaliMonth";
|
||||||
|
|
||||||
function DatePicker({ setDate, disabledDates = [] }) {
|
function gregorianMonthsOf(jalaliMonth) {
|
||||||
|
const first = moment(jalaliMonth).startOf("jMonth");
|
||||||
|
const last = moment(jalaliMonth).endOf("jMonth");
|
||||||
|
const keys = new Set();
|
||||||
|
for (const d of [first, last]) {
|
||||||
|
keys.add(`${d.year()}-${d.month() + 1}`);
|
||||||
|
}
|
||||||
|
return [...keys].map((k) => {
|
||||||
|
const [year, month] = k.split("-").map(Number);
|
||||||
|
return { year, month };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function DatePicker({ setDate }) {
|
||||||
|
const params = useParams();
|
||||||
|
const doctorUuid = params?.doctorId;
|
||||||
|
|
||||||
const [baseMonth, setBaseMonth] = useState(moment().tz("Asia/Tehran"));
|
const [baseMonth, setBaseMonth] = useState(moment().tz("Asia/Tehran"));
|
||||||
const [selectedDate, setSelectedDate] = useState(null);
|
const [selectedDate, setSelectedDate] = useState(null);
|
||||||
const [autoSelected, setAutoSelected] = useState(false);
|
const [autoSelected, setAutoSelected] = useState(false);
|
||||||
|
const [disabledSet, setDisabledSet] = useState(() => new Set());
|
||||||
|
const [onlineEnabled, setOnlineEnabled] = useState(true);
|
||||||
|
const loadedMonths = useRef(new Set());
|
||||||
|
|
||||||
|
const secondMonth = moment(baseMonth).add(1, "jMonth");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!doctorUuid) return;
|
||||||
|
|
||||||
|
const targets = [
|
||||||
|
...gregorianMonthsOf(baseMonth),
|
||||||
|
...gregorianMonthsOf(secondMonth),
|
||||||
|
].filter(({ year, month }) => !loadedMonths.current.has(`${year}-${month}`));
|
||||||
|
|
||||||
|
if (targets.length === 0) return;
|
||||||
|
|
||||||
|
targets.forEach(({ year, month }) => {
|
||||||
|
loadedMonths.current.add(`${year}-${month}`);
|
||||||
|
request
|
||||||
|
.getMonthAvailability(doctorUuid, year, month)
|
||||||
|
.then((res) => {
|
||||||
|
const data = res?.data ?? {};
|
||||||
|
if (data.online_booking_enabled === false) setOnlineEnabled(false);
|
||||||
|
const dates = Array.isArray(data.disabled_dates) ? data.disabled_dates : [];
|
||||||
|
if (dates.length) {
|
||||||
|
setDisabledSet((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
dates.forEach((d) => next.add(d));
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loadedMonths.current.delete(`${year}-${month}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [doctorUuid, baseMonth]);
|
||||||
|
|
||||||
const isDisabled = (date) => {
|
const isDisabled = (date) => {
|
||||||
const day = moment(date).startOf("day");
|
const day = moment(date).startOf("day");
|
||||||
if (day.isBefore(moment().startOf("day"))) return true;
|
if (day.isBefore(moment().startOf("day"))) return true;
|
||||||
return disabledDates.some((ts) => day.isSame(moment.unix(ts).startOf("day")));
|
return disabledSet.has(day.format("YYYY-MM-DD"));
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectDay = (date) => {
|
const selectDay = (date) => {
|
||||||
@@ -27,7 +82,7 @@ function DatePicker({ setDate, disabledDates = [] }) {
|
|||||||
if (autoSelected) return;
|
if (autoSelected) return;
|
||||||
|
|
||||||
const today = moment().tz("Asia/Tehran");
|
const today = moment().tz("Asia/Tehran");
|
||||||
for (let i = 0; i <= 30; i++) {
|
for (let i = 0; i <= 60; i++) {
|
||||||
const candidate = today.clone().add(i, "days");
|
const candidate = today.clone().add(i, "days");
|
||||||
if (!isDisabled(candidate)) {
|
if (!isDisabled(candidate)) {
|
||||||
setSelectedDate(candidate);
|
setSelectedDate(candidate);
|
||||||
@@ -37,9 +92,15 @@ function DatePicker({ setDate, disabledDates = [] }) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [disabledDates, autoSelected, setDate]);
|
}, [disabledSet, autoSelected, setDate]);
|
||||||
|
|
||||||
const secondMonth = moment(baseMonth).add(1, "jMonth");
|
if (!onlineEnabled) {
|
||||||
|
return (
|
||||||
|
<div className="w-full py-[40px] text-center text-[#525252] text-[14px] md:text-[16px] font-medium">
|
||||||
|
نوبتدهی آنلاین این پزشک در حال حاضر غیرفعال است.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div dir="rtl" className="datePickerApt flex items-start justify-evenly gap-[24px] w-full">
|
<div dir="rtl" className="datePickerApt flex items-start justify-evenly gap-[24px] w-full">
|
||||||
|
|||||||
Reference in New Issue
Block a user