Files
football-next/components/PersianDateField.tsx
2026-05-03 17:01:46 +03:30

167 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useMemo, useState } from "react";
import DatePicker from "react-multi-date-picker";
import DateObject from "react-date-object";
import persian from "react-date-object/calendars/persian";
import gregorian from "react-date-object/calendars/gregorian";
import persian_fa from "react-date-object/locales/persian_fa";
import gregorian_en from "react-date-object/locales/gregorian_en";
import {
dateValueToJalali,
formatPersianDateTime,
getDateFromJalaliDateTime,
jalaliDateTimeToUtcIso,
jalaliDateToGregorianString,
type JalaliDateParts,
} from "@/lib/persianDate";
type PersianDateFieldProps = {
label: string;
value: string;
onChange: (value: string) => void;
mode?: "date" | "datetime";
required?: boolean;
placeholder?: string;
};
function createDateObject(parts: JalaliDateParts | null) {
if (!parts) return null;
return new DateObject({
calendar: persian,
locale: persian_fa,
year: parts.year,
month: parts.month,
day: parts.day,
});
}
function getInitialTime(value: string, mode: "date" | "datetime") {
const parsedValue = dateValueToJalali(value, mode);
return parsedValue?.time || "12:00";
}
export default function PersianDateField({
label,
value,
onChange,
mode = "date",
required = false,
placeholder = "انتخاب تاریخ شمسی",
}: PersianDateFieldProps) {
const parsedValue = useMemo(() => dateValueToJalali(value, mode), [mode, value]);
const pickerValue = useMemo(() => createDateObject(parsedValue), [parsedValue]);
const [time, setTime] = useState(getInitialTime(value, mode));
useEffect(() => {
setTime(getInitialTime(value, mode));
}, [mode, value]);
function emitChange(nextDate: DateObject | null, nextTime: string) {
if (!nextDate) {
onChange("");
return;
}
const year = Number(nextDate.year);
const month = Number(nextDate.month.number);
const day = Number(nextDate.day);
if (mode === "date") {
onChange(jalaliDateToGregorianString(year, month, day));
return;
}
onChange(jalaliDateTimeToUtcIso(year, month, day, nextTime));
}
const preview = useMemo(() => {
if (!parsedValue) return "";
if (mode === "date") return "";
return formatPersianDateTime(
getDateFromJalaliDateTime(parsedValue.year, parsedValue.month, parsedValue.day, time)
);
}, [mode, parsedValue, time]);
return (
<div className="space-y-2">
<label className="block text-sm font-medium text-slate-700">{label}</label>
<DatePicker
value={pickerValue}
onChange={(selected) => emitChange((selected as DateObject | null) ?? null, time)}
calendar={persian}
locale={persian_fa}
format="YYYY/MM/DD"
calendarPosition="bottom-right"
editable={false}
required={required}
placeholder={placeholder}
inputClass="w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-right text-slate-900 shadow-sm outline-none transition focus:border-emerald-400 focus:ring-2 focus:ring-emerald-200"
containerClassName="block w-full"
weekDays={[
["شنبه", "ش"],
["یکشنبه", "ی"],
["دوشنبه", "د"],
["سه‌شنبه", "س"],
["چهارشنبه", "چ"],
["پنجشنبه", "پ"],
["جمعه", "ج"],
]}
/>
{pickerValue && (
<div className="rounded-xl bg-slate-50 px-3 py-2 text-xs text-slate-500">
{mode === "date"
? `تاریخ انتخاب‌شده: ${pickerValue.format("dddd DD MMMM YYYY")}`
: preview}
</div>
)}
{mode === "datetime" && (
<div className="grid grid-cols-2 gap-3">
<div>
<label className="mb-1 block text-xs font-medium text-slate-600">ساعت</label>
<select
value={time.split(":")[0]}
onChange={(event) => {
const nextTime = `${event.target.value}:${time.split(":")[1]}`;
setTime(nextTime);
emitChange(pickerValue, nextTime);
}}
className="w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-slate-900 outline-none transition focus:border-emerald-400 focus:ring-2 focus:ring-emerald-200"
>
{Array.from({ length: 24 }, (_, index) => String(index).padStart(2, "0")).map((hour) => (
<option key={hour} value={hour}>
{new Intl.NumberFormat("fa-IR").format(Number(hour))}
</option>
))}
</select>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-slate-600">دقیقه</label>
<select
value={time.split(":")[1]}
onChange={(event) => {
const nextTime = `${time.split(":")[0]}:${event.target.value}`;
setTime(nextTime);
emitChange(pickerValue, nextTime);
}}
className="w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-slate-900 outline-none transition focus:border-emerald-400 focus:ring-2 focus:ring-emerald-200"
>
{Array.from({ length: 60 }, (_, index) => String(index).padStart(2, "0")).map((minute) => (
<option key={minute} value={minute}>
{new Intl.NumberFormat("fa-IR").format(Number(minute))}
</option>
))}
</select>
</div>
</div>
)}
</div>
);
}