feat: update user roles and passwords in QA driver, enhance documentation with error codes, and improve trial activation error handling

This commit is contained in:
hamed
2026-07-19 15:19:03 +03:30
parent 3e841b5e57
commit 7761c37a3e
10 changed files with 326 additions and 109 deletions
@@ -0,0 +1,42 @@
import { describe, it, expect } from 'vitest';
import { STATUS_META } from './AppointmentStatusDropdown';
/**
* Mirrors Appointment::STATUS_* on the backend. The dashboard used to keep its
* own copy of this map, which drifted and rendered `confirmed` / `expired` as
* raw English; every consumer now derives from STATUS_META, so this list is the
* one place that has to stay in sync with the entity.
*/
const BACKEND_STATUSES = [
'pending',
'confirmed',
'completed',
'cancelled_by_doctor',
'cancelled_by_user',
'expired',
'no_show',
'following_up',
'salon',
] as const;
describe('STATUS_META', () => {
it.each(BACKEND_STATUSES)('has a Persian label and colour for %s', (status) => {
expect(STATUS_META[status]).toBeDefined();
expect(STATUS_META[status].label.trim()).not.toBe('');
expect(STATUS_META[status].color).toMatch(/^#[0-9a-f]{6}$/i);
});
it('carries no label that is still English', () => {
const latin = Object.entries(STATUS_META)
.filter(([, v]) => /[A-Za-z]/.test(v.label))
.map(([k]) => k);
expect(latin).toEqual([]);
});
it('defines no status the backend does not know about', () => {
const unknown = Object.keys(STATUS_META).filter(
(k) => !BACKEND_STATUSES.includes(k as (typeof BACKEND_STATUSES)[number]),
);
expect(unknown).toEqual([]);
});
});
+12 -13
View File
@@ -20,6 +20,7 @@ const jalaali = require('jalaali-js') as {
import { NewAppointmentsTable } from '../components/dashboard/NewAppointmentsTable';
import DoctorAppointmentsPanel from '../components/dashboard/DoctorAppointmentsPanel';
import { usePermissions } from '../hooks/usePermissions';
import { STATUS_META } from '../components/ui/AppointmentStatusDropdown';
// ── Chart period (Jalali) ─────────────────────────────────────────────────
@@ -47,18 +48,15 @@ function useJalaliChartPeriod() {
// ── Shared Status Maps ────────────────────────────────────────────────────
const APPT_LABEL: Record<string, string> = {
waiting_for_payment: 'انتظار پرداخت', reserved: 'رزرو شده', checked_in: 'ورود به مطب',
waiting: 'صف انتظار', in_progress: 'در حال ویزیت', visited: 'ویزیت شده',
completed: 'تکمیل شده', cancelled_by_doctor: 'لغو پزشک', cancelled_by_user: 'لغو بیمار',
auto_cancel_unpaid: 'لغو خودکار', no_show: 'غیبت',
};
const APPT_COLOR: Record<string, string> = {
waiting_for_payment: '#f59e0b', reserved: '#3b82f6', checked_in: '#6366f1',
waiting: '#f97316', in_progress: '#8b5cf6', visited: '#10b981',
completed: '#22c55e', cancelled_by_doctor: '#ef4444', cancelled_by_user: '#f43f5e',
auto_cancel_unpaid: '#94a3b8', no_show: '#64748b',
};
// Derived from the canonical STATUS_META rather than kept as a second copy —
// the local map had drifted off the backend's Appointment::STATUS_* set, so
// `confirmed` and `expired` rendered as raw English on the dashboard.
const APPT_LABEL: Record<string, string> = Object.fromEntries(
Object.entries(STATUS_META).map(([k, v]) => [k, v.label]),
);
const APPT_COLOR: Record<string, string> = Object.fromEntries(
Object.entries(STATUS_META).map(([k, v]) => [k, v.color]),
);
const APPT_CLS: Record<string, string> = {
waiting_for_payment: 'amber', reserved: 'blue', checked_in: 'violet',
waiting: 'amber', in_progress: 'violet', visited: 'green', completed: 'green',
@@ -384,7 +382,8 @@ function AdminDashboard() {
{ label: 'پزشکان فعال', value: fn(stats?.active_doctors), hint: stats ? `از ${fn(stats.total_doctors)} پزشک` : '', icon: HeartIcon, color: 'var(--success)', bg: 'var(--success-bg)' },
{ label: 'کلینیک‌ها', value: fn(stats?.total_clinics), hint: '', icon: BuildingOffice2Icon, color: 'var(--info)', bg: 'var(--info-bg)' },
{ label: 'نوبت‌های امروز', value: fn(stats?.today_appointments), hint: stats ? `ماه جاری: ${fn(stats.this_month_appointments)}` : '', icon: CalendarDaysIcon, color: 'var(--warning)', bg: 'var(--warning-bg)' },
{ label: 'درآمد این ماه', value: fr(stats?.this_month_revenue), hint: 'تومان', icon: CreditCardIcon, color: 'var(--primary)', bg: 'var(--primary-soft)' },
// formatRial already suffixes « تومان» — a 'تومان' hint here renders it twice.
{ label: 'درآمد این ماه', value: fr(stats?.this_month_revenue), hint: '', icon: CreditCardIcon, color: 'var(--primary)', bg: 'var(--primary-soft)' },
{ label: 'در انتظار بررسی', value: fn(stats ? stats.pending_comments + stats.pending_settlements : undefined), hint: stats ? `${fn(stats.pending_comments)} نظر · ${fn(stats.pending_settlements)} تسویه` : '', icon: BellAlertIcon, color: 'var(--danger)', bg: 'var(--danger-bg)' },
];