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
+15 -15
View File
@@ -55,8 +55,8 @@ export interface SessionCardData {
function Row({ label, value, valueStyle }: { label: string; value: string; valueStyle?: CSSProperties }) {
return (
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 6, margin: '6px 0' }}>
<span style={{ fontSize: 12, color: '#616161' }} className="dark:text-[#A1A1A1]">{label}:</span>
<span style={{ fontSize: 14, color: '#525252', textAlign: 'left', ...valueStyle }} className="dark:text-[#D7D8ED]">{value}</span>
<span style={{ fontSize: 12, color: 'var(--text-2)' }}>{label}:</span>
<span style={{ fontSize: 14, color: 'var(--text)', textAlign: 'left', ...valueStyle }}>{value}</span>
</div>
);
}
@@ -99,27 +99,27 @@ export default function SessionServiceCard({ session, onSettle, onViewInvoice, o
return (
<div
className="bg-white dark:bg-[#222433] border border-[#EDEDED] dark:border-[#35343D]"
className="bg-[var(--surface)] border border-[var(--border)]"
style={{ minWidth: 277, minHeight: 331, borderRadius: 12, padding: 14, display: 'flex', flexDirection: 'column', gap: 10, overflow: 'hidden', boxShadow: '0 1px 6px rgba(15,23,42,0.06)' }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 2 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
<FilesServiceSuccess />
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 3, minWidth: 0 }}>
<span className="dark:text-[#D7D8ED]" style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 14, fontWeight: 600, color: '#525252', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 180 }}>
<span style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 14, fontWeight: 600, color: 'var(--text)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 180 }}>
{title}
{session.archived && <span className="badge" style={{ fontSize: 10, color: '#9CA3AF', border: '1px solid #E0E0E0', borderRadius: 4, padding: '1px 6px' }}>آرشیو</span>}
{session.archived && <span className="badge" style={{ fontSize: 10, color: 'var(--text-3)', border: '1px solid var(--border-2)', borderRadius: 4, padding: '1px 6px' }}>آرشیو</span>}
</span>
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 12, color: '#6B7280', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 200 }}>{subtitle}</span>
<span style={{ fontSize: 12, color: 'var(--text-2)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 200 }}>{subtitle}</span>
</div>
</div>
<div ref={menuRef} style={{ position: 'relative', flexShrink: 0 }}>
<button type="button" aria-label="عملیات" onClick={() => setMenuOpen((o) => !o)}
style={{ background: 'transparent', border: 'none', cursor: 'pointer', padding: 2, display: 'flex' }}>
<FilesServiceMore style={{ color: '#9CA3AF' }} />
<FilesServiceMore style={{ color: 'var(--text-3)' }} />
</button>
{menuOpen && (
<div className="card dark:bg-[#222433] dark:border-[#35343D]" style={{ position: 'absolute', insetInlineEnd: 0, top: '100%', marginTop: 4, minWidth: 150, zIndex: 20, padding: 4, background: '#fff', border: '1px solid var(--border)', borderRadius: 8, boxShadow: 'var(--shadow-lg)' }}>
<div className="card" style={{ position: 'absolute', insetInlineEnd: 0, top: '100%', marginTop: 4, minWidth: 150, zIndex: 20, padding: 4, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, boxShadow: 'var(--shadow-lg)' }}>
<button type="button" onClick={() => { setMenuOpen(false); onViewInvoice(session); }}
style={{ display: 'block', width: '100%', textAlign: 'right', padding: '8px 10px', fontSize: 13, background: 'transparent', border: 'none', borderRadius: 6, cursor: 'pointer', color: 'var(--text)', fontFamily: 'inherit' }}>
مشاهده فاکتور
@@ -138,7 +138,7 @@ export default function SessionServiceCard({ session, onSettle, onViewInvoice, o
)}
{onArchive && (
<button type="button" onClick={() => { setMenuOpen(false); onArchive(session, !session.archived); }}
style={{ display: 'block', width: '100%', textAlign: 'right', padding: '8px 10px', fontSize: 13, background: 'transparent', border: 'none', borderRadius: 6, cursor: 'pointer', color: session.archived ? 'var(--text)' : '#EF4444', fontFamily: 'inherit' }}>
style={{ display: 'block', width: '100%', textAlign: 'right', padding: '8px 10px', fontSize: 13, background: 'transparent', border: 'none', borderRadius: 6, cursor: 'pointer', color: session.archived ? 'var(--text)' : 'var(--danger)', fontFamily: 'inherit' }}>
{session.archived ? 'خروج از آرشیو' : 'آرشیو'}
</button>
)}
@@ -147,7 +147,7 @@ export default function SessionServiceCard({ session, onSettle, onViewInvoice, o
</div>
</div>
<div className="dark:border-[#35343D]" style={{ borderTop: '1px solid #F1F1F1' }} />
<div style={{ borderTop: '1px solid var(--border)' }} />
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, overflow: 'hidden', lineHeight: 1.9 }}>
<Row label="انجام دهنده" value={session.doctor_name || '—'} />
@@ -155,7 +155,7 @@ export default function SessionServiceCard({ session, onSettle, onViewInvoice, o
<Row label="توضیحات" value={session.notes || '—'} valueStyle={{ display: 'flex', textAlign: 'right', width: '100%' }} />
</div>
<div className="dark:border-[#35343D]" style={{ borderTop: '1px solid #F1F1F1' }} />
<div style={{ borderTop: '1px solid var(--border)' }} />
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
{session.insurance_service_category_label && (
@@ -163,10 +163,10 @@ export default function SessionServiceCard({ session, onSettle, onViewInvoice, o
)}
{session.insurance_base_name && <Row label="بیمه" value={session.insurance_base_name} />}
{(session.base_insurance_rials ?? 0) > 0 && (
<Row label="سهم بیمه" value={formatRial(session.base_insurance_rials ?? 0)} valueStyle={{ color: '#16A34A', fontWeight: 500 }} />
<Row label="سهم بیمه" value={formatRial(session.base_insurance_rials ?? 0)} valueStyle={{ color: 'var(--success)', fontWeight: 500 }} />
)}
<Row label="هزینه" value={formatRial(session.final_price_rials ?? 0)} valueStyle={{ fontWeight: 500 }} />
{!paid && <Row label="مانده بدهی" value={formatRial(debt)} valueStyle={{ color: '#EF4444', fontWeight: 500 }} />}
{!paid && <Row label="مانده بدهی" value={formatRial(debt)} valueStyle={{ color: 'var(--danger)', fontWeight: 500 }} />}
</div>
<div style={{ marginTop: 'auto' }}>
@@ -175,7 +175,7 @@ export default function SessionServiceCard({ session, onSettle, onViewInvoice, o
type="button"
disabled={issuing}
onClick={() => onViewInvoice(session)}
style={{ marginTop: 8, height: 40, width: '100%', borderRadius: 4, fontSize: 13, fontWeight: 500, cursor: 'pointer', color: '#4F46E5', background: 'transparent', border: '1px solid #5559ce' }}
style={{ marginTop: 8, height: 40, width: '100%', borderRadius: 4, fontSize: 13, fontWeight: 500, cursor: 'pointer', color: 'var(--primary)', background: 'transparent', border: '1px solid var(--primary)' }}
>
{issuing ? 'در حال صدور…' : 'مشاهده فاکتور'}
</button>
@@ -184,7 +184,7 @@ export default function SessionServiceCard({ session, onSettle, onViewInvoice, o
type="button"
disabled={settling}
onClick={() => onSettle(session.uuid)}
style={{ marginTop: 8, height: 40, width: '100%', borderRadius: 4, fontSize: 13, fontWeight: 500, cursor: 'pointer', color: '#fff', background: '#5559ce', border: '1px solid #5559ce' }}
style={{ marginTop: 8, height: 40, width: '100%', borderRadius: 4, fontSize: 13, fontWeight: 500, cursor: 'pointer', color: 'var(--on-primary)', background: 'var(--primary)', border: '1px solid var(--primary)' }}
>
تکمیل پرداخت
</button>