206 lines
6.2 KiB
TypeScript
206 lines
6.2 KiB
TypeScript
import { useState } from "react";
|
||
import { Calendar, ChevronLeft, ChevronRight, X } from "lucide-react";
|
||
import { Button } from "./ui/button";
|
||
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "./ui/drawer";
|
||
import jMoment from "moment-jalaali";
|
||
|
||
interface PersianDatePickerProps {
|
||
value?: string; // Format: YYYY/MM/DD
|
||
onChange: (date: string) => void;
|
||
placeholder?: string;
|
||
label?: string;
|
||
}
|
||
|
||
const PersianDatePicker = ({
|
||
value,
|
||
onChange,
|
||
placeholder = "انتخاب تاریخ",
|
||
label,
|
||
}: PersianDatePickerProps) => {
|
||
const [isOpen, setIsOpen] = useState(false);
|
||
const [currentDate, setCurrentDate] = useState(jMoment());
|
||
const [selectedDate, setSelectedDate] = useState<string | null>(
|
||
value || null
|
||
);
|
||
|
||
const persianMonths = [
|
||
"فروردین",
|
||
"اردیبهشت",
|
||
"خرداد",
|
||
"تیر",
|
||
"مرداد",
|
||
"شهریور",
|
||
"مهر",
|
||
"آبان",
|
||
"آذر",
|
||
"دی",
|
||
"بهمن",
|
||
"اسفند",
|
||
];
|
||
|
||
const persianDaysOfWeek = ["ش", "ی", "د", "س", "چ", "پ", "ج"];
|
||
|
||
// Generate calendar days
|
||
const generateCalendarDays = () => {
|
||
const startOfMonth = currentDate.clone().startOf("jMonth");
|
||
const endOfMonth = currentDate.clone().endOf("jMonth");
|
||
const startOfWeek = startOfMonth.clone().startOf("week");
|
||
const endOfWeek = endOfMonth.clone().endOf("week");
|
||
|
||
const days = [];
|
||
const currentDay = startOfWeek.clone();
|
||
|
||
while (currentDay.isSameOrBefore(endOfWeek)) {
|
||
days.push(currentDay.clone());
|
||
currentDay.add(1, "day");
|
||
}
|
||
|
||
return days;
|
||
};
|
||
|
||
const handleDateSelect = (date: typeof jMoment) => {
|
||
const dateString = date.format("jYYYY/jMM/jDD");
|
||
setSelectedDate(dateString);
|
||
onChange(dateString);
|
||
setIsOpen(false);
|
||
};
|
||
|
||
const goToPreviousMonth = () => {
|
||
setCurrentDate(currentDate.clone().subtract(1, "jMonth"));
|
||
};
|
||
|
||
const goToNextMonth = () => {
|
||
setCurrentDate(currentDate.clone().add(1, "jMonth"));
|
||
};
|
||
|
||
const calendarDays = generateCalendarDays();
|
||
|
||
return (
|
||
<>
|
||
{label && <p className="text-R14 font-bold mb-2">{label}</p>}
|
||
<Button
|
||
variant="outline"
|
||
size="xl"
|
||
onClick={() => setIsOpen(true)}
|
||
className={`${!selectedDate && "text-GRAY"} w-full bg-WHITE relative justify-start text-right h-12 px-4`}
|
||
>
|
||
<div className="flex px-2 items-center justify-between absolute left-0 bg-BLACK h-full rounded-l-md">
|
||
<Calendar size={24} className="text-WHITE" />
|
||
</div>
|
||
{selectedDate || placeholder}
|
||
</Button>
|
||
|
||
<Drawer open={isOpen} onOpenChange={setIsOpen}>
|
||
<DrawerContent className="w-full pb-6">
|
||
<DrawerHeader className="flex flex-col py-3">
|
||
<div className="flex relative pt-2 text-B12 font-bold items-center justify-center w-full">
|
||
<X
|
||
className="absolute right-4 cursor-pointer"
|
||
size={24}
|
||
onClick={() => setIsOpen(false)}
|
||
/>
|
||
<DrawerTitle className="text-B16 font-bold">
|
||
انتخاب تاریخ
|
||
</DrawerTitle>
|
||
</div>
|
||
</DrawerHeader>
|
||
|
||
<div className="px-4">
|
||
{/* Month/Year Header */}
|
||
<div className="flex items-center justify-between mb-4">
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={goToPreviousMonth}
|
||
className="p-2"
|
||
>
|
||
<ChevronRight className="w-4 h-4" />
|
||
</Button>
|
||
|
||
<div className="text-center">
|
||
<p className="text-lg font-bold">
|
||
{persianMonths[currentDate.jMonth()]} {currentDate.jYear()}
|
||
</p>
|
||
</div>
|
||
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={goToNextMonth}
|
||
className="p-2"
|
||
>
|
||
<ChevronLeft className="w-4 h-4" />
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Days of Week Header */}
|
||
<div className="grid grid-cols-7 gap-1 mb-2">
|
||
{persianDaysOfWeek.map((day) => (
|
||
<div
|
||
key={day}
|
||
className="text-center text-sm font-bold text-GRAY py-2"
|
||
>
|
||
{day}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Calendar Days */}
|
||
<div className="grid grid-cols-7 gap-1">
|
||
{calendarDays.map((day, index) => {
|
||
const isCurrentMonth = day.jMonth() === currentDate.jMonth();
|
||
const isSelected = selectedDate === day.format("jYYYY/jMM/jDD");
|
||
const isToday = day.isSame(jMoment(), "day");
|
||
|
||
return (
|
||
<Button
|
||
key={index}
|
||
variant={isSelected ? "dark" : "ghost"}
|
||
size="sm"
|
||
onClick={() => handleDateSelect(day)}
|
||
className={`
|
||
h-10 w-full p-0 text-sm
|
||
${!isCurrentMonth ? "text-GRAY2" : ""}
|
||
${isToday && !isSelected ? "bg-blue-50 text-blue-600" : ""}
|
||
`}
|
||
disabled={!isCurrentMonth}
|
||
>
|
||
{day.jDate()}
|
||
</Button>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* Action Buttons */}
|
||
<div className="flex gap-3 mt-6">
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => setIsOpen(false)}
|
||
className="flex-1"
|
||
size="xl"
|
||
>
|
||
انصراف
|
||
</Button>
|
||
<Button
|
||
onClick={() => {
|
||
const today = jMoment().format("jYYYY/jMM/jDD");
|
||
setSelectedDate(today);
|
||
onChange(today);
|
||
setIsOpen(false);
|
||
}}
|
||
className="flex-1"
|
||
variant="secondary"
|
||
size="xl"
|
||
>
|
||
امروز
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DrawerContent>
|
||
</Drawer>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default PersianDatePicker;
|