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
+50 -6
View File
@@ -13,9 +13,37 @@ import { formatNumber, formatRial, formatDateTime } from '../lib/utils';
import { useAuthStore } from '../stores/authStore';
import InviteDoctorModal from '../components/ui/InviteDoctorModal';
import { TauriDashboardView } from '../components/dashboard/TauriDashboardView';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const jalaali = require('jalaali-js') as {
toJalaali: (date: Date) => { jy: number; jm: number; jd: number };
};
import { NewAppointmentsTable } from '../components/dashboard/NewAppointmentsTable';
import { usePermissions } from '../hooks/usePermissions';
// ── Chart period (Jalali) ─────────────────────────────────────────────────
/**
* دوره‌ی نمودارهای داشبورد: نمودار بیماران روی یک ماه شمسی و نمودار درآمد روی یک
* سال شمسی. پیش‌فرض = ماه/سال جاری؛ تغییر سلکتور باعث fetch مجدد می‌شود.
*/
function useJalaliChartPeriod() {
const now = useMemo(() => jalaali.toJalaali(new Date()), []);
const [patientsMonth, setPatientsMonth] = useState(now.jm);
const [revenueYear, setRevenueYear] = useState(now.jy);
return {
currentJalaliYear: now.jy,
patientsYear: now.jy,
patientsMonth,
setPatientsMonth,
revenueYear,
setRevenueYear,
/** query string پارامترهای دوره‌ی نمودار */
query: `patients_year=${now.jy}&patients_month=${patientsMonth}&revenue_year=${revenueYear}`,
};
}
// ── Shared Status Maps ────────────────────────────────────────────────────
const APPT_LABEL: Record<string, string> = {
@@ -614,9 +642,10 @@ function ClinicDashboard() {
const [inviteOpen, setInviteOpen] = useState(false);
const preset: DatePreset = 'this_month';
const range = getRange(preset);
const chartPeriod = useJalaliChartPeriod();
const q = useQuery({
queryKey: ['dashboard-clinic', preset],
queryFn: () => api.get<ApiResponse<ClinicDashboardData>>(`/api/v1/dashboard/clinic?from=${range.from}&to=${range.to}`),
queryKey: ['dashboard-clinic', preset, chartPeriod.query],
queryFn: () => api.get<ApiResponse<ClinicDashboardData>>(`/api/v1/dashboard/clinic?from=${range.from}&to=${range.to}&${chartPeriod.query}`),
staleTime: 60_000,
});
@@ -636,11 +665,16 @@ function ClinicDashboard() {
todayAppointments: d?.stats.today_appointments ?? 0,
}}
patientBars={(d?.charts?.appointments_by_day ?? []).map(x => ({ label: x.label, value: x.count }))}
incomeLine={(d?.charts?.revenue_by_day ?? []).map(x => ({ label: x.label, value: x.amount_rials }))}
incomeLine={(d?.charts?.revenue_by_month ?? []).map(x => ({ label: x.label, value: x.amount_rials }))}
appointments={d?.today_appointments ?? []}
loading={q.isFetching}
formatNumber={formatNumber}
formatRial={formatRial}
patientsMonth={chartPeriod.patientsMonth}
onPatientsMonthChange={chartPeriod.setPatientsMonth}
revenueYear={chartPeriod.revenueYear}
onRevenueYearChange={chartPeriod.setRevenueYear}
currentJalaliYear={chartPeriod.currentJalaliYear}
/>
{/* پزشکان کلینیک — بخش عملکردی موجود clinicpro، حفظ‌شده زیر نمای منتقل‌شده */}
@@ -798,7 +832,11 @@ function DoctorClinicInvitationsCard() {
// ── Doctor Dashboard ──────────────────────────────────────────────────────
interface DashboardCharts {
/** ۷ روز اخیر — فقط برای کارت‌های آماری */
revenue_by_day: { label: string; amount_rials: number }[];
/** ۱۲ ماه سال شمسی انتخاب‌شده — نمودار «میزان درآمد» */
revenue_by_month?: { label: string; amount_rials: number }[];
/** روزهای ماه شمسی انتخاب‌شده — نمودار «تعداد بیماران» */
appointments_by_day: { label: string; count: number }[];
}
@@ -818,9 +856,10 @@ interface DoctorDashboardData {
function DoctorDashboard() {
const preset: DatePreset = 'this_month';
const range = getRange(preset);
const chartPeriod = useJalaliChartPeriod();
const q = useQuery({
queryKey: ['dashboard-doctor', preset],
queryFn: () => api.get<ApiResponse<DoctorDashboardData>>(`/api/v1/dashboard/doctor?from=${range.from}&to=${range.to}`),
queryKey: ['dashboard-doctor', preset, chartPeriod.query],
queryFn: () => api.get<ApiResponse<DoctorDashboardData>>(`/api/v1/dashboard/doctor?from=${range.from}&to=${range.to}&${chartPeriod.query}`),
staleTime: 60_000,
});
@@ -839,11 +878,16 @@ function DoctorDashboard() {
todayAppointments: d?.stats.today_appointments ?? 0,
}}
patientBars={(d?.charts?.appointments_by_day ?? []).map(x => ({ label: x.label, value: x.count }))}
incomeLine={(d?.charts?.revenue_by_day ?? []).map(x => ({ label: x.label, value: x.amount_rials }))}
incomeLine={(d?.charts?.revenue_by_month ?? []).map(x => ({ label: x.label, value: x.amount_rials }))}
appointments={d?.today_appointments ?? []}
loading={q.isFetching}
formatNumber={formatNumber}
formatRial={formatRial}
patientsMonth={chartPeriod.patientsMonth}
onPatientsMonthChange={chartPeriod.setPatientsMonth}
revenueYear={chartPeriod.revenueYear}
onRevenueYearChange={chartPeriod.setRevenueYear}
currentJalaliYear={chartPeriod.currentJalaliYear}
/>
<div className="grid-2" style={{ marginTop: 'var(--gap)' }}>