edited middle ware and calender component

This commit is contained in:
mouazkh
2026-08-06 23:51:50 +03:00
parent 4d1b744516
commit b9a4c6157a
5 changed files with 549 additions and 571 deletions

View File

@ -0,0 +1,246 @@
"use client";
import { useState, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { ChevronRight, ChevronLeft } from "lucide-react";
const MONTH_KEYS = [
"month.january",
"month.february",
"month.march",
"month.april",
"month.may",
"month.june",
"month.july",
"month.august",
"month.september",
"month.october",
"month.november",
"month.december",
];
const DAY_KEYS = [
"dayAbbr.sun",
"dayAbbr.mon",
"dayAbbr.tue",
"dayAbbr.wed",
"dayAbbr.thu",
"dayAbbr.fri",
"dayAbbr.sat",
];
export default function DateSelectionCalendar({
mode,
availableDates,
selectedStart,
selectedEnd,
onSelectStart,
onSelectEnd,
}) {
const { t } = useTranslation();
const [calendarMonth, setCalendarMonth] = useState(() => new Date().getMonth());
const [calendarYear, setCalendarYear] = useState(() => new Date().getFullYear());
const [rangeError, setRangeError] = useState(null);
const isPastDate = useCallback((dateStr) => {
const today = new Date();
today.setHours(0, 0, 0, 0);
return new Date(dateStr) < today;
}, []);
const isDateAvailable = useCallback(
(dateStr) => (availableDates ? availableDates.has(dateStr) : true),
[availableDates]
);
const isMonthFullyAvailable = useCallback(
(year, monthIdx) => {
const daysInMonth = new Date(year, monthIdx + 1, 0).getDate();
for (let day = 1; day <= daysInMonth; day++) {
const dateStr = `${year}-${String(monthIdx + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
if (isPastDate(dateStr) || !isDateAvailable(dateStr)) return false;
}
return true;
},
[isPastDate, isDateAvailable]
);
const isPastMonth = useCallback((year, monthIdx) => {
const now = new Date();
return (
year < now.getFullYear() ||
(year === now.getFullYear() && monthIdx < now.getMonth())
);
}, []);
const handleDayClick = (dateStr) => {
setRangeError(null);
if (!selectedStart || selectedEnd) {
onSelectStart(dateStr);
onSelectEnd(null);
return;
}
if (new Date(dateStr) <= new Date(selectedStart)) {
onSelectStart(dateStr);
onSelectEnd(null);
return;
}
const start = new Date(selectedStart);
const end = new Date(dateStr);
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
const ds = d.toISOString().split("T")[0];
if (!isDateAvailable(ds)) {
setRangeError(t("unavailableInRange", { defaultValue: "توجد أيام غير متاحة ضمن الفترة المحددة" }));
return;
}
}
onSelectEnd(dateStr);
};
const handleMonthClick = (year, monthIdx) => {
setRangeError(null);
const monthStr = `${year}-${String(monthIdx + 1).padStart(2, "0")}`;
const firstDay = `${monthStr}-01`;
if (!selectedStart || selectedEnd || monthStr <= (selectedStart || "").substring(0, 7)) {
onSelectStart(firstDay);
onSelectEnd(null);
} else {
const lastDay = new Date(year, monthIdx + 1, 0).getDate();
onSelectEnd(`${monthStr}-${String(lastDay).padStart(2, "0")}`);
}
};
const navigateMonth = (delta) => {
let month = calendarMonth + delta;
let year = calendarYear;
if (month < 0) {
month = 11;
year--;
}
if (month > 11) {
month = 0;
year++;
}
setCalendarMonth(month);
setCalendarYear(year);
};
const isEntryStep = !selectedStart || !!selectedEnd;
return (
<div className="mb-3">
{/* Step Indicator */}
<div className="flex items-center justify-center gap-2 mb-3">
<div
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${isEntryStep ? "bg-amber-100 text-amber-800" : "bg-gray-100 text-gray-500"}`}
>
<div className={`w-1.5 h-1.5 rounded-full ${isEntryStep ? "bg-amber-500" : "bg-gray-400"}`} />
{t("selectStartDate")}
</div>
<div className="text-gray-300 text-xs"></div>
<div className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${!isEntryStep ? "bg-amber-100 text-amber-800" : "bg-gray-100 text-gray-500"}`}>
<div className={`w-1.5 h-1.5 rounded-full ${!isEntryStep ? "bg-amber-500" : "bg-gray-400"}`} />
{t("selectEndDate")}
</div>
</div>
{mode === "daily" ? (
<div>
<div className="flex items-center justify-between mb-2">
<button onClick={() => navigateMonth(-1)} className="p-1 rounded-lg hover:bg-gray-100 transition-colors">
<ChevronRight className="w-4 h-4 text-gray-600" />
</button>
<span className="text-sm font-bold text-gray-900">
{t(MONTH_KEYS[calendarMonth])} {calendarYear}
</span>
<button onClick={() => navigateMonth(1)} className="p-1 rounded-lg hover:bg-gray-100 transition-colors">
<ChevronLeft className="w-4 h-4 text-gray-600" />
</button>
</div>
<div className="grid grid-cols-7 mb-1">
{DAY_KEYS.map((dk, i) => (
<div key={i} className="text-center text-[10px] text-gray-400 font-medium py-1">
{t(dk)}
</div>
))}
</div>
{(() => {
const firstDay = new Date(calendarYear, calendarMonth, 1).getDay();
const daysInMonth = new Date(calendarYear, calendarMonth + 1, 0).getDate();
const cells = [];
for (let i = 0; i < firstDay; i++) {
cells.push(<div key={`e-${i}`} />);
}
for (let day = 1; day <= daysInMonth; day++) {
const dateStr = `${calendarYear}-${String(calendarMonth + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
const past = isPastDate(dateStr);
const available = isDateAvailable(dateStr);
const isSelStart = dateStr === selectedStart;
const isSelEnd = dateStr === selectedEnd;
const inRange =
selectedStart &&
selectedEnd &&
new Date(dateStr) > new Date(selectedStart) &&
new Date(dateStr) < new Date(selectedEnd);
const disabled = past || !available;
cells.push(
<button
key={dateStr}
onClick={() => !disabled && handleDayClick(dateStr)}
disabled={disabled}
className={`text-center py-1.5 text-xs rounded-lg transition-all ${disabled ? "text-gray-300 cursor-not-allowed" : ""} ${isSelStart || isSelEnd ? "bg-amber-500 text-white font-bold shadow-sm" : ""} ${inRange ? "bg-amber-100 text-amber-800" : ""} ${!disabled && !isSelStart && !isSelEnd && !inRange && available ? "hover:bg-amber-50 text-gray-700" : ""} ${!disabled && !isSelStart && !isSelEnd && !inRange && !available ? "text-red-300" : ""}`}
>
{day}
</button>
);
}
return <div className="grid grid-cols-7 gap-0.5">{cells}</div>;
})()}
</div>
) : (
<div>
<div className="flex items-center justify-between mb-2">
<button onClick={() => setCalendarYear((prev) => prev - 1)} className="p-1 rounded-lg hover:bg-gray-100 transition-colors">
<ChevronRight className="w-4 h-4 text-gray-600" />
</button>
<span className="text-sm font-bold text-gray-900">{calendarYear}</span>
<button onClick={() => setCalendarYear((prev) => prev + 1)} className="p-1 rounded-lg hover:bg-gray-100 transition-colors">
<ChevronLeft className="w-4 h-4 text-gray-600" />
</button>
</div>
<div className="grid grid-cols-3 gap-2">
{MONTH_KEYS.map((mk, idx) => {
const monthStr = `${calendarYear}-${String(idx + 1).padStart(2, "0")}`;
const isSelStart = selectedStart && selectedStart.startsWith(monthStr);
const isSelEnd = selectedEnd && selectedEnd.startsWith(monthStr);
const inRange =
selectedStart &&
selectedEnd &&
monthStr > selectedStart.substring(0, 7) &&
monthStr < selectedEnd.substring(0, 7);
const disabled = isPastMonth(calendarYear, idx) || !isMonthFullyAvailable(calendarYear, idx);
return (
<button
key={idx}
onClick={() => !disabled && handleMonthClick(calendarYear, idx)}
disabled={disabled}
className={`p-2.5 rounded-xl text-center text-xs font-medium transition-all border ${disabled ? "text-gray-300 border-gray-100 bg-gray-50 cursor-not-allowed" : ""} ${isSelStart || isSelEnd ? "bg-amber-500 text-white border-amber-500 shadow-sm" : ""} ${!disabled && inRange ? "bg-amber-100 text-amber-800 border-amber-200" : ""} ${!disabled && !isSelStart && !isSelEnd && !inRange ? "bg-white text-gray-700 border-gray-200 hover:border-amber-300" : ""}`}
>
{t(mk)}
</button>
);
})}
</div>
</div>
)}
{rangeError && (
<div className="bg-red-50 text-red-600 p-2.5 rounded-xl text-xs mt-3">
{rangeError}
</div>
)}
</div>
);
}