Implement comprehensive dark/light mode overhaul for admin panel
- Refactor color palette in `ui-design-spec.md` to utilize CSS variables exclusively, eliminating fixed hex values and Tailwind utility classes. - Complete dark mode implementation in `uiStore.ts`, ensuring proper theme application via `applyTheme()` and `applyBrand()`. - Create `admin-theme-dark-light-audit.md` to document the transition process, outlining issues with inline styles and fixed colors. - Introduce `theme-tokens.test.ts` to enforce rules against fixed hex colors and ensure compliance with the design system. - Update various components and styles to replace inline styles and fixed colors with CSS variables, ensuring consistent theming across light and dark modes. - Ensure all changes maintain visual integrity in both light and dark modes, with a focus on accessibility and contrast standards.
This commit is contained in:
@@ -130,7 +130,7 @@ export default function ServiceSlotPicker({
|
||||
border: active ? '1px solid var(--primary)' : '1px solid var(--border-2)',
|
||||
background: active ? 'var(--primary)' : 'transparent',
|
||||
}}>
|
||||
{active && <span style={{ width: 8, height: 8, background: '#fff', borderRadius: 2 }} />}
|
||||
{active && <span style={{ width: 8, height: 8, background: 'var(--surface)', borderRadius: 2 }} />}
|
||||
</span>
|
||||
{s.name}
|
||||
</span>
|
||||
@@ -182,7 +182,7 @@ export default function ServiceSlotPicker({
|
||||
<button type="button" aria-label={`حذف ${s.name}`} onClick={() => remove(s.uuid)}
|
||||
style={{
|
||||
display: 'grid', placeItems: 'center', width: 18, height: 18, borderRadius: 999, flexShrink: 0,
|
||||
border: 'none', cursor: 'pointer', background: 'var(--primary)', color: '#fff',
|
||||
border: 'none', cursor: 'pointer', background: 'var(--primary)', color: 'var(--on-primary)',
|
||||
fontSize: 13, lineHeight: 1, fontFamily: 'inherit',
|
||||
}}>×</button>
|
||||
</div>
|
||||
@@ -211,7 +211,7 @@ export default function ServiceSlotPicker({
|
||||
style={{
|
||||
fontSize: 13, padding: '6px 12px', borderRadius: 'var(--r-sm)', cursor: 'pointer', fontFamily: 'inherit',
|
||||
border: active ? '1px solid var(--primary)' : '1px solid var(--border)',
|
||||
background: active ? 'var(--primary)' : 'var(--surface)', color: active ? '#fff' : 'var(--text)',
|
||||
background: active ? 'var(--primary)' : 'var(--surface)', color: active ? 'var(--on-primary)' : 'var(--text)',
|
||||
}}>
|
||||
{s.start_time}
|
||||
</button>
|
||||
|
||||
@@ -32,10 +32,10 @@ function BadgeIcon({ Icon, color, bg }: { Icon: React.ElementType; color: string
|
||||
|
||||
export default function TurnsStatInfo({ stats }: { stats: TurnsStats }) {
|
||||
const items: StatDef[] = [
|
||||
{ title: 'کل نوبت های امروز', value: stats.total, Icon: UsersIcon, iconColor: '#494cb3', iconBg: '#e9ebfb' },
|
||||
{ title: 'نوبت های انجام شده', value: stats.completed, Icon: UserPlusIcon, iconColor: '#0d9f43', iconBg: '#e7f6ed' },
|
||||
{ title: 'بیماران در انتظار', value: stats.waiting, Icon: ClockIcon, iconColor: '#e0a838', iconBg: '#fff2dc' },
|
||||
{ title: 'نوبت های لغو شده', value: stats.cancelled, Icon: XCircleIcon, iconColor: '#ee5c6d', iconBg: '#fad6da' },
|
||||
{ title: 'کل نوبت های امروز', value: stats.total, Icon: UsersIcon, iconColor: 'var(--primary-600)', iconBg: 'var(--primary-soft)' },
|
||||
{ title: 'نوبت های انجام شده', value: stats.completed, Icon: UserPlusIcon, iconColor: 'var(--success)', iconBg: 'var(--success-bg)' },
|
||||
{ title: 'بیماران در انتظار', value: stats.waiting, Icon: ClockIcon, iconColor: 'var(--warning)', iconBg: 'var(--warning-bg)' },
|
||||
{ title: 'نوبت های لغو شده', value: stats.cancelled, Icon: XCircleIcon, iconColor: 'var(--danger)', iconBg: 'var(--danger-bg)' },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -77,7 +77,7 @@ function EmptyCard({ slot, onBook }: { slot: TimelineSlot; onBook: (s: TimelineS
|
||||
cursor: isPast ? 'not-allowed' : 'pointer', opacity: isPast ? 0.7 : 1, minHeight: 76,
|
||||
}}
|
||||
>
|
||||
<InnerTimes start={slot.start_time} end={slot.end_time} color={isPast ? '#9E9E9E' : cfg.dotColor} />
|
||||
<InnerTimes start={slot.start_time} end={slot.end_time} color={isPast ? 'var(--text-3)' : cfg.dotColor} />
|
||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 13.5, color: isPast ? 'var(--text-3)' : cfg.textColor, fontWeight: 500 }}>
|
||||
{isPast ? 'گذشته' : 'افزودن نوبت سریع'}
|
||||
@@ -88,8 +88,8 @@ function EmptyCard({ slot, onBook }: { slot: TimelineSlot; onBook: (s: TimelineS
|
||||
{!isPast && (
|
||||
<span style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 5, padding: '3px 10px', borderRadius: 99,
|
||||
fontSize: 12, fontWeight: 700, color: cfg.textColor, background: `${cfg.dotColor}15`,
|
||||
border: `1.5px solid ${cfg.dotColor}30`, whiteSpace: 'nowrap', alignSelf: 'flex-start',
|
||||
fontSize: 12, fontWeight: 700, color: cfg.textColor, background: `color-mix(in srgb, ${cfg.dotColor} 8%, transparent)`,
|
||||
border: `1.5px solid color-mix(in srgb, ${cfg.dotColor} 19%, transparent)`, whiteSpace: 'nowrap', alignSelf: 'flex-start',
|
||||
}}>
|
||||
<span style={{ width: 7, height: 7, borderRadius: '50%', background: cfg.dotColor }} />
|
||||
نوبت جدید
|
||||
|
||||
@@ -6,85 +6,33 @@
|
||||
export interface TurnStatusConfig {
|
||||
label: string;
|
||||
bgColor: string;
|
||||
darkBgColor: string;
|
||||
borderColor: string;
|
||||
darkBorderColor: string;
|
||||
dotColor: string;
|
||||
textColor: string;
|
||||
darkTextColor: string;
|
||||
}
|
||||
|
||||
// وضعیتهای بکاند → رنگ طرح tauri.
|
||||
/** یک وضعیت = یک توکن؛ خود توکن با تم عوض میشود، پس جفت light/dark لازم نیست. */
|
||||
const of = (fg: string, bg: string): Omit<TurnStatusConfig, 'label'> => ({
|
||||
bgColor: `var(--${bg})`, borderColor: `var(--${fg})`,
|
||||
dotColor: `var(--${fg})`, textColor: `var(--${fg})`,
|
||||
});
|
||||
|
||||
// وضعیتهای بکاند → توکنهای تم (هیوی طرح tauri حفظ شده است).
|
||||
const STATUS: Record<string, TurnStatusConfig> = {
|
||||
// pending = «ثبت شده» (tauri: registered)
|
||||
pending: {
|
||||
label: 'ثبت شده',
|
||||
bgColor: '#E3F2FD', darkBgColor: 'rgba(85, 89, 206, 0.18)',
|
||||
borderColor: '#2196F3', darkBorderColor: 'rgba(199, 206, 244, 0.42)',
|
||||
dotColor: '#2196F3', textColor: '#2196F3', darkTextColor: '#C7CEF4',
|
||||
},
|
||||
// confirmed = «قطعی شده» (tauri: finalized)
|
||||
confirmed: {
|
||||
label: 'قطعی شده',
|
||||
bgColor: '#E0F2F1', darkBgColor: 'rgba(25, 191, 211, 0.14)',
|
||||
borderColor: '#009688', darkBorderColor: 'rgba(25, 191, 211, 0.44)',
|
||||
dotColor: '#009688', textColor: '#009688', darkTextColor: '#19BFD3',
|
||||
},
|
||||
// following_up = «در حال پیگیری» (tauri: in_progress)
|
||||
following_up: {
|
||||
label: 'در حال پیگیری',
|
||||
bgColor: '#FFF3E0', darkBgColor: 'rgba(241, 119, 50, 0.16)',
|
||||
borderColor: '#FF9800', darkBorderColor: 'rgba(241, 119, 50, 0.46)',
|
||||
dotColor: '#FF9800', textColor: '#d87f00', darkTextColor: '#F17732',
|
||||
},
|
||||
// salon = «سالن»
|
||||
salon: {
|
||||
label: 'سالن',
|
||||
bgColor: '#F3E5F5', darkBgColor: 'rgba(199, 206, 244, 0.16)',
|
||||
borderColor: '#9C27B0', darkBorderColor: 'rgba(199, 206, 244, 0.42)',
|
||||
dotColor: '#9C27B0', textColor: '#9C27B0', darkTextColor: '#C7CEF4',
|
||||
},
|
||||
// completed = «ویزیت شده» (tauri: success)
|
||||
completed: {
|
||||
label: 'ویزیت شده',
|
||||
bgColor: '#E8F5E9', darkBgColor: 'rgba(34, 197, 94, 0.14)',
|
||||
borderColor: '#4CAF50', darkBorderColor: 'rgba(34, 197, 94, 0.42)',
|
||||
dotColor: '#4CAF50', textColor: '#0d9f43', darkTextColor: '#4ADE80',
|
||||
},
|
||||
// cancel variants = «لغو شده» (tauri: failed)
|
||||
cancelled_by_doctor: {
|
||||
label: 'لغو شده',
|
||||
bgColor: '#fde7e9', darkBgColor: 'rgba(255, 84, 80, 0.16)',
|
||||
borderColor: '#ee5c6d', darkBorderColor: 'rgba(255, 84, 80, 0.44)',
|
||||
dotColor: '#ee5c6d', textColor: '#ee5c6d', darkTextColor: '#FF7A76',
|
||||
},
|
||||
cancelled_by_user: {
|
||||
label: 'لغو توسط بیمار',
|
||||
bgColor: '#fde7e9', darkBgColor: 'rgba(255, 84, 80, 0.16)',
|
||||
borderColor: '#ee5c6d', darkBorderColor: 'rgba(255, 84, 80, 0.44)',
|
||||
dotColor: '#ee5c6d', textColor: '#ee5c6d', darkTextColor: '#FF7A76',
|
||||
},
|
||||
// no_show / expired = خنثی (tauri: pending amber-ish → اینجا خاکستری)
|
||||
no_show: {
|
||||
label: 'غیبت',
|
||||
bgColor: '#f4f4f5', darkBgColor: 'rgba(160, 160, 160, 0.14)',
|
||||
borderColor: '#9E9E9E', darkBorderColor: 'rgba(160, 160, 160, 0.4)',
|
||||
dotColor: '#9E9E9E', textColor: '#757575', darkTextColor: '#A1A1A1',
|
||||
},
|
||||
expired: {
|
||||
label: 'منقضی',
|
||||
bgColor: '#f4f4f5', darkBgColor: 'rgba(160, 160, 160, 0.14)',
|
||||
borderColor: '#9E9E9E', darkBorderColor: 'rgba(160, 160, 160, 0.4)',
|
||||
dotColor: '#9E9E9E', textColor: '#757575', darkTextColor: '#A1A1A1',
|
||||
},
|
||||
pending: { label: 'ثبت شده', ...of('info', 'info-bg') },
|
||||
confirmed: { label: 'قطعی شده', ...of('primary', 'primary-soft') },
|
||||
following_up: { label: 'در حال پیگیری', ...of('accent', 'accent-bg') },
|
||||
salon: { label: 'سالن', ...of('violet', 'violet-bg') },
|
||||
completed: { label: 'ویزیت شده', ...of('success', 'success-bg') },
|
||||
cancelled_by_doctor: { label: 'لغو شده', ...of('danger', 'danger-bg') },
|
||||
cancelled_by_user: { label: 'لغو توسط بیمار', ...of('danger', 'danger-bg') },
|
||||
no_show: { label: 'غیبت', ...of('text-3', 'surface-2') },
|
||||
expired: { label: 'منقضی', ...of('text-3', 'surface-2') },
|
||||
};
|
||||
|
||||
// اسلات خالی (نوبت جدید) — tauri: empty.
|
||||
export const EMPTY_SLOT_CONFIG: TurnStatusConfig = {
|
||||
label: 'نوبت جدید',
|
||||
bgColor: '#E3F2FD', darkBgColor: 'rgba(85, 89, 206, 0.16)',
|
||||
borderColor: '#2196F3', darkBorderColor: 'rgba(199, 206, 244, 0.38)',
|
||||
dotColor: '#2196F3', textColor: '#5559ce', darkTextColor: '#C7CEF4',
|
||||
label: 'نوبت جدید', ...of('primary', 'primary-soft'),
|
||||
};
|
||||
|
||||
export function turnStatusConfig(status: string): TurnStatusConfig {
|
||||
|
||||
Reference in New Issue
Block a user