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:
hamed
2026-06-15 16:31:47 +03:30
co-authored by Claude Opus 4.8
parent 96f4126c0b
commit 232e7eaeb2
+67 -6
View File
@@ -1,20 +1,75 @@
"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-timezone";
import { dateToTimestamp } from "@/helper";
import { request } from "@/services/response";
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 [selectedDate, setSelectedDate] = useState(null);
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 day = moment(date).startOf("day");
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) => {
@@ -27,7 +82,7 @@ function DatePicker({ setDate, disabledDates = [] }) {
if (autoSelected) return;
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");
if (!isDisabled(candidate)) {
setSelectedDate(candidate);
@@ -37,9 +92,15 @@ function DatePicker({ setDate, disabledDates = [] }) {
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 (
<div dir="rtl" className="datePickerApt flex items-start justify-evenly gap-[24px] w-full">