feat: Implement Jalali calendar support for dashboard charts

- Added support for Jalali calendar in the dashboard, allowing charts to display data based on the current Jalali month and year.
- Updated the API to return `patients_year`, `patients_month`, and `revenue_year` parameters for the dashboard charts.
- Refactored the dashboard controller to handle Jalali date calculations and queries.
- Modified the frontend components to utilize the new Jalali date parameters and reflect changes in the UI.
- Removed the status column from the NewAppointmentsTable as status management is now handled on the appointments page.
- Added tests to ensure the correct functioning of the new Jalali chart period features.
This commit is contained in:
hamed
2026-07-18 21:44:01 +03:30
parent 7921407f33
commit 62a9fd87c3
7 changed files with 479 additions and 100 deletions
@@ -11,15 +11,19 @@ import { TauriBarChart, TauriLineChart, type ChartPoint } from './TauriCharts';
import { NewAppointmentsTable, type ApptRow } from './NewAppointmentsTable';
import SearchableSelect from '../ui/SearchableSelect';
/** small cosmetic dropdown — mirrors source SmSelector (does not drive data) */
function SmSelector({ options }: { options: string[] }) {
const [value, setValue] = React.useState<string | number | null>(options[0] ?? null);
export interface SelectorOption {
value: number;
label: string;
}
/** dropdown دوره‌ی نمودار — مقدارش داده‌ی نمودار را عوض می‌کند */
function SmSelector({ options, value, onChange }: { options: SelectorOption[]; value: number; onChange: (v: number) => void }) {
return (
<div style={{ minWidth: 110 }}>
<SearchableSelect
options={options.map((o) => ({ value: o, label: o }))}
options={options}
value={value}
onChange={setValue}
onChange={(v) => onChange(Number(v))}
height={34}
/>
</div>
@@ -30,20 +34,41 @@ function ChartTitle({ text }: { text: string }) {
return <p className="text-[#525252] dark:text-[#D7D8ED] text-[14px] md:text-[16px] font-bold">{text}</p>;
}
function ChartCard({ title, selectorOptions, children }: { title: string; selectorOptions: string[]; children: React.ReactNode }) {
function ChartCard({
title,
selectorOptions,
selectorValue,
onSelectorChange,
children,
}: {
title: string;
selectorOptions: SelectorOption[];
selectorValue: number;
onSelectorChange: (v: number) => void;
children: React.ReactNode;
}) {
return (
<div className="w-full rounded-[8px] border border-solid border-[#EFEFEF] dark:border-transparent bg-[#FFF] dark:bg-[#222433]">
<div className="flex px-[20px] py-[12px] md:py-[14px] lg:py-[16px] items-center justify-between gap-[12px]">
<ChartTitle text={title} />
<SmSelector options={selectorOptions} />
<SmSelector options={selectorOptions} value={selectorValue} onChange={onSelectorChange} />
</div>
{children}
</div>
);
}
const MONTHS = ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'];
const YEARS = ['سال ۱۴۰۳', 'سال ۱۴۰۲', 'سال ۱۴۰۱', 'سال ۱۴۰۰'];
export const JALALI_MONTHS = ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'];
const MONTH_OPTIONS: SelectorOption[] = JALALI_MONTHS.map((label, i) => ({ value: i + 1, label }));
/** سال جاری و ۳ سال گذشته‌ی شمسی */
function yearOptions(currentYear: number): SelectorOption[] {
return [0, 1, 2, 3].map((back) => ({
value: currentYear - back,
label: `سال ${new Intl.NumberFormat('fa-IR', { useGrouping: false }).format(currentYear - back)}`,
}));
}
export interface TauriDashboardViewProps {
stats: DashboardStats;
@@ -55,6 +80,14 @@ export interface TauriDashboardViewProps {
loading: boolean;
formatNumber: (n: number) => string;
formatRial: (rial: number) => string;
/** ماه شمسی نمودار بیماران (۱..۱۲) */
patientsMonth: number;
onPatientsMonthChange: (m: number) => void;
/** سال شمسی نمودار درآمد */
revenueYear: number;
onRevenueYearChange: (y: number) => void;
/** سال شمسی جاری — مبنای گزینه‌های سلکتور سال */
currentJalaliYear: number;
}
export function TauriDashboardView({
@@ -65,7 +98,13 @@ export function TauriDashboardView({
loading,
formatNumber,
formatRial,
patientsMonth,
onPatientsMonthChange,
revenueYear,
onRevenueYearChange,
currentJalaliYear,
}: TauriDashboardViewProps) {
const yearOpts = React.useMemo(() => yearOptions(currentJalaliYear), [currentJalaliYear]);
return (
<div className="flex flex-col items-stretch gap-y-[24px] w-full">
{/* Cards */}
@@ -74,10 +113,20 @@ export function TauriDashboardView({
{/* Charts — grid-2 (1fr 1fr, collapses to 1fr on narrow) for reliable
full-width equal columns, matching the rest of the admin dashboard */}
<div className="grid-2 w-full">
<ChartCard title="نمودار تعداد بیماران" selectorOptions={MONTHS}>
<ChartCard
title="نمودار تعداد بیماران"
selectorOptions={MONTH_OPTIONS}
selectorValue={patientsMonth}
onSelectorChange={onPatientsMonthChange}
>
<TauriBarChart data={patientBars} />
</ChartCard>
<ChartCard title="میزان درآمد" selectorOptions={YEARS}>
<ChartCard
title="میزان درآمد"
selectorOptions={yearOpts}
selectorValue={revenueYear}
onSelectorChange={onRevenueYearChange}
>
<TauriLineChart data={incomeLine} />
</ChartCard>
</div>