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:
hamed
2026-07-27 16:41:52 +03:30
parent c29035c21e
commit 55ab2f5dfc
73 changed files with 1485 additions and 1110 deletions
+24 -24
View File
@@ -45,15 +45,15 @@ function PatientCard({ r, onView, onEdit, canUpdate }: { r: PatientRecord; onVie
const [menu, setMenu] = useState(false);
return (
<div
className="bg-white dark:bg-[#222433] rounded-[6px] p-[14px] border border-[#EDEDED] dark:border-[#35343D] shadow-sm transition-all duration-300 hover:shadow-md hover:-translate-y-1 cursor-pointer"
className="bg-[var(--surface)] rounded-[6px] p-[14px] border border-[var(--border)] shadow-sm transition-all duration-300 hover:shadow-md hover:-translate-y-1 cursor-pointer"
onClick={(e) => { if ((e.target as HTMLElement).closest('.more-square-icon')) return; onView(); }}
>
<div className="flex items-center justify-between gap-[6px] mb-[12px] pb-[8px] border-b border-[#E0E0E0] dark:border-[#404040]">
<div className="flex items-center justify-between gap-[6px] mb-[12px] pb-[8px] border-b border-[var(--border-2)]">
<div className="flex items-center gap-[6px] flex-1 min-w-0">
<span style={{ width: 28, height: 28, borderRadius: '50%', background: 'var(--primary-soft)', display: 'grid', placeItems: 'center', flexShrink: 0 }}>
<UserIcon style={{ width: 16, color: 'var(--primary)' }} />
</span>
<h3 className="text-[#525252] dark:text-[#D7D8ED] text-[14px] font-semibold truncate">{r.user_name || '—'}</h3>
<h3 className="text-[var(--text)] text-[14px] font-semibold truncate">{r.user_name || '—'}</h3>
</div>
<div className="flex-shrink-0 more-square-icon" style={{ position: 'relative' }}>
<button
@@ -78,20 +78,20 @@ function PatientCard({ r, onView, onEdit, canUpdate }: { r: PatientRecord; onVie
<div className="mb-[12px] my-4" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<div className="flex items-start justify-between text-[15px]">
<span className="text-[#616161] dark:text-[#A1A1A1] font-medium min-w-[65px]">شماره پرونده:</span>
<span className="text-[#525252] dark:text-[#D7D8ED] truncate">{r.record_number || '—'}</span>
<span className="text-[var(--text-2)] font-medium min-w-[65px]">شماره پرونده:</span>
<span className="text-[var(--text)] truncate">{r.record_number || '—'}</span>
</div>
<div className="flex items-start justify-between text-[15px]">
<span className="text-[#616161] dark:text-[#A1A1A1] font-medium min-w-[65px]">موبایل:</span>
<span className="text-[#525252] dark:text-[#D7D8ED] truncate" dir="ltr">{r.user_mobile || '—'}</span>
<span className="text-[var(--text-2)] font-medium min-w-[65px]">موبایل:</span>
<span className="text-[var(--text)] truncate" dir="ltr">{r.user_mobile || '—'}</span>
</div>
</div>
<div
className="flex items-center pt-[8px] justify-end gap-[8px] border-t border-[#E0E0E0] dark:border-[#404040]"
className="flex items-center pt-[8px] justify-end gap-[8px] border-t border-[var(--border-2)]"
onClick={(e) => e.stopPropagation()}
>
<span className="text-[#616161] dark:text-[#A1A1A1] text-[14px] min-w-[65px]">برچسبها:</span>
<span className="text-[var(--text-2)] text-[14px] min-w-[65px]">برچسبها:</span>
<div className="flex-1 flex justify-end"><PatientTagsCell record={r} /></div>
</div>
</div>
@@ -140,12 +140,12 @@ export default function PatientsListPage() {
// نمایش جدولی/کارتی — دو دکمه با آیکون SVG سفارشی (عین tauri ViewModeToggle).
const viewToggle = (
<div className="flex border border-[#E0E0E0] dark:border-[#404040] rounded-[4px] overflow-hidden shrink-0">
<button type="button" aria-label="نمایش جدولی" onClick={() => switchView('table')} style={{ padding: 8, border: 'none', cursor: 'pointer', display: 'grid', placeItems: 'center', background: view === 'table' ? '#f4f5fd' : 'transparent' }}>
<PatientsGridView color={view === 'table' ? '#5559ce' : '#616161'} />
<div className="flex border border-[var(--border-2)] rounded-[4px] overflow-hidden shrink-0">
<button type="button" aria-label="نمایش جدولی" onClick={() => switchView('table')} style={{ padding: 8, border: 'none', cursor: 'pointer', display: 'grid', placeItems: 'center', background: view === 'table' ? 'var(--primary-soft)' : 'transparent' }}>
<PatientsGridView color={view === 'table' ? 'var(--primary)' : 'var(--text-2)'} />
</button>
<button type="button" aria-label="نمایش کارتی" onClick={() => switchView('card')} style={{ padding: 8, border: 'none', cursor: 'pointer', display: 'grid', placeItems: 'center', background: view === 'card' ? '#f4f5fd' : 'transparent' }}>
<PatientsCategoryView color={view === 'card' ? '#5559ce' : '#616161'} />
<button type="button" aria-label="نمایش کارتی" onClick={() => switchView('card')} style={{ padding: 8, border: 'none', cursor: 'pointer', display: 'grid', placeItems: 'center', background: view === 'card' ? 'var(--primary-soft)' : 'transparent' }}>
<PatientsCategoryView color={view === 'card' ? 'var(--primary)' : 'var(--text-2)'} />
</button>
</div>
);
@@ -153,7 +153,7 @@ export default function PatientsListPage() {
return (
<div className="fade-in">
{/* Head — تیتر تنها (tauri files/head) */}
<p className="text-[#525252] dark:text-[#D7D8ED] text-[16px] md:text-[18px] lg:text-[20px] font-bold">پروندهها</p>
<p className="text-[var(--text)] text-[16px] md:text-[18px] lg:text-[20px] font-bold">پروندهها</p>
{/* Inputs — ردیف کنترل‌ها (tauri files/inputs) */}
<div className="my-[16px] md:my-[20px] lg:my-[24px]">
@@ -161,16 +161,16 @@ export default function PatientsListPage() {
{/* جستجو + سوییچ نما */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 flex-1 w-full">
<div className="w-full md:w-[442px]">
<div className="flex items-center rounded-[6px] border border-[#EFEFEF] dark:border-[#35343D] bg-[#FAFAFA] dark:bg-[#222433]" style={{ height: 48, paddingInline: 4 }}>
<div className="flex items-center rounded-[6px] border border-[var(--border)] bg-[var(--bg)]" style={{ height: 48, paddingInline: 4 }}>
<input
dir="rtl"
value={search}
onChange={(e) => { setSearch(e.target.value); setPage(1); }}
placeholder="کد ملی، شماره پرونده یا نام مراجعه کننده را وارد کنید..."
className="text-[#525252] dark:text-[#D7D8ED]"
className="text-[var(--text)]"
style={{ flex: 1, minWidth: 0, background: 'transparent', border: 'none', outline: 'none', fontSize: 13, textAlign: 'left' }}
/>
<span style={{ width: 40, height: 40, marginInlineEnd: -4, flexShrink: 0, borderRadius: 6, background: '#5559CE', display: 'grid', placeItems: 'center' }}>
<span style={{ width: 40, height: 40, marginInlineEnd: -4, flexShrink: 0, borderRadius: 6, background: 'var(--primary)', display: 'grid', placeItems: 'center' }}>
<SearchHeaderP color="white" />
</span>
</div>
@@ -183,21 +183,21 @@ export default function PatientsListPage() {
<button
type="button" aria-label="فیلترها" onClick={() => setFilterOpen(true)}
className="flex items-center justify-center rounded-[4px] cursor-pointer"
style={{ width: 62, height: 48, border: '1px solid #5559ce', background: 'transparent', position: 'relative' }}
style={{ width: 62, height: 48, border: '1px solid var(--primary)', background: 'transparent', position: 'relative' }}
>
<TurnsFilter color="#5559ce" />
<TurnsFilter color="var(--primary)" />
{activeFilters > 0 && (
<span style={{ position: 'absolute', top: -6, insetInlineEnd: -6, minWidth: 16, height: 16, padding: '0 4px', borderRadius: 999, background: '#5559ce', color: '#fff', fontSize: 10, display: 'grid', placeItems: 'center' }}>{formatNumber(activeFilters)}</span>
<span style={{ position: 'absolute', top: -6, insetInlineEnd: -6, minWidth: 16, height: 16, padding: '0 4px', borderRadius: 999, background: 'var(--primary)', color: 'var(--on-primary)', fontSize: 10, display: 'grid', placeItems: 'center' }}>{formatNumber(activeFilters)}</span>
)}
</button>
{canCreate && (
<button
type="button" onClick={() => navigate('/admin/patients/new')}
className="flex items-center justify-center gap-2 rounded-[4px] cursor-pointer"
style={{ height: 48, minWidth: 137, background: '#5559ce', border: 'none', padding: '0 16px' }}
style={{ height: 48, minWidth: 137, background: 'var(--primary)', border: 'none', padding: '0 16px' }}
>
<AddTurn color="#fff" />
<span style={{ color: '#fff', fontSize: 14 }}>تشکیل پرونده</span>
<AddTurn color="var(--on-primary)" />
<span style={{ color: 'var(--on-primary)', fontSize: 14 }}>تشکیل پرونده</span>
</button>
)}
</div>