feat(#40): Persian (Jalali) date pickers in admin
Add react-multi-date-picker + a reusable PersianDateInput (displays Jalali, stores Gregorian ISO so DTOs are unchanged). Replace admin date inputs: UserProfileEdit birthdate + CRM date-range filters. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { PersianDateInput } from "@/components/admin/PersianDateInput";
|
||||
|
||||
interface Daily { date: string; signups: number; buyers: number; revenue_minor: number }
|
||||
interface Analytics {
|
||||
total_signups: number; buyers: number; non_buyers: number;
|
||||
@@ -51,8 +53,8 @@ export function CrmAdmin() {
|
||||
<p className="mt-1 text-sm text-gray-400">ثبتنامها، خریداران، نرخ تبدیل و درآمد در بازهٔ زمانی انتخابی.</p>
|
||||
</div>
|
||||
<div className="flex items-end gap-2">
|
||||
<div><label className="mb-1 block text-xs text-gray-500">از</label><input type="date" className={inp} value={start} onChange={(e) => setStart(e.target.value)} /></div>
|
||||
<div><label className="mb-1 block text-xs text-gray-500">تا</label><input type="date" className={inp} value={end} onChange={(e) => setEnd(e.target.value)} /></div>
|
||||
<div><label className="mb-1 block text-xs text-gray-500">از</label><PersianDateInput className={inp} value={start} onChange={setStart} /></div>
|
||||
<div><label className="mb-1 block text-xs text-gray-500">تا</label><PersianDateInput className={inp} value={end} onChange={setEnd} /></div>
|
||||
<button className={btn} onClick={load} disabled={loading}>{loading ? "..." : "بارگذاری"}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import DatePicker, { DateObject } from "react-multi-date-picker";
|
||||
import persian from "react-date-object/calendars/persian";
|
||||
import persian_fa from "react-date-object/locales/persian_fa";
|
||||
import gregorian from "react-date-object/calendars/gregorian";
|
||||
|
||||
/**
|
||||
* Jalali (Persian) date picker for the admin. Displays a Persian calendar but keeps
|
||||
* the value as a Gregorian ISO string ("YYYY-MM-DD"), so backends/DTOs stay unchanged.
|
||||
*/
|
||||
export function PersianDateInput({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
placeholder,
|
||||
}: {
|
||||
value: string; // Gregorian ISO "YYYY-MM-DD" or ""
|
||||
onChange: (iso: string) => void;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const dateObj = value
|
||||
? new DateObject({ date: value, format: "YYYY-MM-DD", calendar: gregorian }).convert(
|
||||
persian,
|
||||
persian_fa
|
||||
)
|
||||
: "";
|
||||
|
||||
return (
|
||||
<DatePicker
|
||||
calendar={persian}
|
||||
locale={persian_fa}
|
||||
calendarPosition="bottom-right"
|
||||
value={dateObj}
|
||||
onChange={(d) => {
|
||||
if (!d || Array.isArray(d)) {
|
||||
onChange("");
|
||||
return;
|
||||
}
|
||||
onChange((d as DateObject).convert(gregorian).format("YYYY-MM-DD"));
|
||||
}}
|
||||
inputClass={className}
|
||||
placeholder={placeholder ?? "انتخاب تاریخ"}
|
||||
editable={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { PersianDateInput } from "@/components/admin/PersianDateInput";
|
||||
|
||||
const inp = "w-full rounded-lg border border-[#262b40] bg-[#0c0e1a] px-3 py-2 text-sm text-gray-100 outline-none focus:border-indigo-500";
|
||||
const lbl = "mb-1 block text-xs font-medium text-gray-400";
|
||||
|
||||
@@ -92,7 +94,7 @@ export function UserProfileEdit({ row }: { row: Record<string, unknown>; reload?
|
||||
<div><label className={lbl}>وبسایت</label><input className={inp} value={f.website_name} onChange={(e) => set("website_name", e.target.value)} /></div>
|
||||
<div><label className={lbl}>کشور</label><input className={inp} value={f.country_code} onChange={(e) => set("country_code", e.target.value)} /></div>
|
||||
<div><label className={lbl}>کد ملی</label><input className={inp} value={f.national_code} onChange={(e) => set("national_code", e.target.value)} /></div>
|
||||
<div><label className={lbl}>تاریخ تولد</label><input type="date" className={inp} value={f.birth_date} onChange={(e) => set("birth_date", e.target.value)} /></div>
|
||||
<div><label className={lbl}>تاریخ تولد</label><PersianDateInput className={inp} value={f.birth_date} onChange={(v) => set("birth_date", v)} /></div>
|
||||
<div>
|
||||
<label className={lbl}>جنسیت</label>
|
||||
<select className={inp} value={f.gender} onChange={(e) => set("gender", e.target.value)}>
|
||||
|
||||
Reference in New Issue
Block a user