"use client"; import { useState, useRef, useEffect } from "react"; import { TextField, Popover, IconButton, Box } from "@mui/material"; import moment from "moment-jalaali"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; moment.loadPersian({ dialect: "persian-modern", usePersianDigits: false }); const WEEKDAYS = ["ش", "ی", "د", "س", "چ", "پ", "ج"]; const MONTHS = [ "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", ]; const MIN_BIRTH_YEAR = 1332; // Parse an incoming value that may be a Jalali string ("1370/5/15"), a Date, // or a moment. Returns a valid moment, falling back to now() when invalid. const parseValue = (value) => { if (!value) return moment(); if (typeof value === "string") { const m = moment(value, "jYYYY/jM/jD"); return m.isValid() ? m : moment(); } const m = moment(value); return m.isValid() ? m : moment(); }; function JalaliDatePicker({ value, onChange, label, placeholder = "انتخاب تاریخ", error = false, helperText = "", disabled = false, className = "", textFieldProps = {}, disabledDates = null, nextIcon = null, prevIcon = null, dayLabelRender = null, isFirst = false, stepwise = false, }) { const [anchorEl, setAnchorEl] = useState(null); const [selectedDate, setSelectedDate] = useState(() => value ? parseValue(value) : null ); const [displayDate, setDisplayDate] = useState(() => parseValue(value)); const [viewMode, setViewMode] = useState(stepwise ? "year" : "day"); const nextIconRef = useRef(null); const prevIconRef = useRef(null); useEffect(() => { if (value) { const newDate = parseValue(value); setSelectedDate(newDate); setDisplayDate(newDate); } }, [value]); const handleClick = (event) => { if (!disabled) { setAnchorEl(event.currentTarget); if (stepwise) { setViewMode("year"); } } }; const handleClose = () => { setAnchorEl(null); if (stepwise) setViewMode("year"); }; const open = Boolean(anchorEl); const handleDateClick = (day) => { const newDate = moment(displayDate) .jDate(day) .jMonth(displayDate.jMonth()) .jYear(displayDate.jYear()); if (disabledDates && disabledDates(newDate)) { return; } setSelectedDate(newDate); if (onChange) { onChange({ _d: newDate.toDate() }); } handleClose(); }; const handleYearSelect = (year) => { setDisplayDate(moment(displayDate).jYear(year)); setViewMode("month"); }; const handleMonthSelect = (month) => { setDisplayDate(moment(displayDate).jMonth(month)); setViewMode("day"); }; const handlePrevMonth = () => { setDisplayDate(moment(displayDate).subtract(1, "jMonth")); }; const handleNextMonth = () => { setDisplayDate(moment(displayDate).add(1, "jMonth")); }; const getDaysInMonth = () => { const year = displayDate.jYear(); const month = displayDate.jMonth(); const daysInMonth = moment.jDaysInMonth(year, month); const firstDayOfMonth = moment([year, month, 1], "jYYYY/jMM/jDD").day(); const days = []; const startOffset = (firstDayOfMonth + 1) % 7; for (let i = 0; i < startOffset; i++) { days.push(null); } for (let day = 1; day <= daysInMonth; day++) { days.push(day); } return days; }; const formatDisplayValue = () => { if (!selectedDate) return ""; return selectedDate.format("jYYYY/jMM/jDD"); }; const renderYearView = () => { const currentYear = moment().jYear(); // newest first, down to the minimum birth year const years = Array.from( { length: currentYear - MIN_BIRTH_YEAR + 1 }, (_, i) => currentYear - i ); return ( <> انتخاب سال {years.map((year) => { const isSelected = selectedDate && selectedDate.jYear() === year; return ( handleYearSelect(year)} sx={{ textAlign: "center", py: 1.5, cursor: "pointer", borderRadius: "4px", fontSize: "14px", bgcolor: isSelected ? "primary.main" : "transparent", color: isSelected ? "primary.contrastText" : "text.primary", "&:hover": { bgcolor: isSelected ? "primary.dark" : "action.hover" }, }} > {year} ); })} ); }; const renderMonthView = () => ( <> setViewMode("year")} sx={{ fontWeight: 600, fontSize: "14px", cursor: "pointer" }} > {displayDate.jYear()} {MONTHS.map((monthName, idx) => { const isSelected = selectedDate && selectedDate.jYear() === displayDate.jYear() && selectedDate.jMonth() === idx; return ( handleMonthSelect(idx)} sx={{ textAlign: "center", py: 1.5, cursor: "pointer", borderRadius: "4px", fontSize: "14px", bgcolor: isSelected ? "primary.main" : "transparent", color: isSelected ? "primary.contrastText" : "text.primary", "&:hover": { bgcolor: isSelected ? "primary.dark" : "action.hover" }, }} > {monthName} ); })} ); const renderDayView = () => { const days = getDaysInMonth(); return ( <> {/* Header */} {nextIcon ? ( {nextIcon} ) : ( )} setViewMode("month") : undefined} sx={{ fontWeight: 600, fontSize: "14px", cursor: stepwise ? "pointer" : "default" }} > {MONTHS[displayDate.jMonth()]} {displayDate.jYear()} {prevIcon ? ( {prevIcon} ) : ( )} {/* Weekday labels */} {dayLabelRender ? ( dayLabelRender() ) : ( <> {WEEKDAYS.map((day, index) => ( {day} ))} )} {/* Days grid */} {days.map((day, index) => { if (!day) { return ; } const dayDate = moment(displayDate) .jDate(day) .jMonth(displayDate.jMonth()) .jYear(displayDate.jYear()); const isSelected = selectedDate && dayDate.jYear() === selectedDate.jYear() && dayDate.jMonth() === selectedDate.jMonth() && dayDate.jDate() === selectedDate.jDate(); const isToday = dayDate.jYear() === moment().jYear() && dayDate.jMonth() === moment().jMonth() && dayDate.jDate() === moment().jDate(); const isDisabled = disabledDates && disabledDates(dayDate); return ( !isDisabled && handleDateClick(day)} sx={{ textAlign: "center", py: 1, cursor: isDisabled ? "not-allowed" : "pointer", borderRadius: "4px", fontSize: "14px", bgcolor: isSelected ? "primary.main" : "transparent", color: isSelected ? "primary.contrastText" : isDisabled ? "text.disabled" : "text.primary", border: isToday && !isSelected ? "1px solid" : "none", borderColor: "primary.main", opacity: isDisabled ? 0.3 : 1, "&:hover": { bgcolor: isDisabled ? "transparent" : isSelected ? "primary.dark" : "action.hover", }, }} > {day} ); })} ); }; return (
{stepwise && viewMode === "year" ? renderYearView() : stepwise && viewMode === "month" ? renderMonthView() : renderDayView()}
); } export default JalaliDatePicker;