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:
@@ -30,9 +30,9 @@ const todayISO = () => new Date().toISOString().slice(0, 10);
|
||||
/** YYYY-MM-DD → unix (ظهر همان روز تا با هر timezone یک روز بماند) */
|
||||
const isoToUnix = (iso: string) => Math.floor(new Date(`${iso}T12:00:00`).getTime() / 1000);
|
||||
|
||||
const fieldLabel: React.CSSProperties = { fontSize: 14, color: '#6B7280', marginBottom: 8, display: 'block' };
|
||||
const primaryBtn: React.CSSProperties = { background: '#5559CE', color: '#fff', border: 'none', borderRadius: 4, height: 46, fontSize: 14, fontWeight: 500, cursor: 'pointer' };
|
||||
const ghostBtn: React.CSSProperties = { background: 'transparent', color: '#5559CE', border: '1px solid #5559CE', borderRadius: 4, height: 46, fontSize: 14, fontWeight: 500, cursor: 'pointer' };
|
||||
const fieldLabel: React.CSSProperties = { fontSize: 14, color: 'var(--text-2)', marginBottom: 8, display: 'block' };
|
||||
const primaryBtn: React.CSSProperties = { background: 'var(--primary)', color: 'var(--on-primary)', border: 'none', borderRadius: 4, height: 46, fontSize: 14, fontWeight: 500, cursor: 'pointer' };
|
||||
const ghostBtn: React.CSSProperties = { background: 'transparent', color: 'var(--primary)', border: '1px solid var(--primary)', borderRadius: 4, height: 46, fontSize: 14, fontWeight: 500, cursor: 'pointer' };
|
||||
|
||||
interface Props {
|
||||
recordUuid: string;
|
||||
@@ -146,26 +146,26 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
|
||||
{/* هزینه سرویس */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, margin: '12px 0 16px' }}>
|
||||
<Step2PaymentCard />
|
||||
<span style={{ fontSize: 14, color: '#F97316', fontWeight: 700 }}>هزینه سرویس:</span>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 700, color: '#525252' }}>{formatRial(finalPrice)}</span>
|
||||
<span style={{ fontSize: 14, color: 'var(--accent)', fontWeight: 700 }}>هزینه سرویس:</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>{formatRial(finalPrice)}</span>
|
||||
</div>
|
||||
|
||||
{/* تخفیفهای پیشنهادی (قوانین تخفیف) */}
|
||||
{suggestions.length > 0 && (
|
||||
<div className="dark:border-[#35343D]" style={{ border: '1px solid #E8EBFF', background: '#F7F8FF', borderRadius: 8, padding: 12, marginBottom: 16 }}>
|
||||
<span style={{ ...fieldLabel, marginBottom: 10, fontWeight: 600, color: '#3B3F9F' }}>تخفیفهای قابل اعمال:</span>
|
||||
<div style={{ border: '1px solid var(--primary-soft2)', background: 'var(--primary-soft)', borderRadius: 8, padding: 12, marginBottom: 16 }}>
|
||||
<span style={{ ...fieldLabel, marginBottom: 10, fontWeight: 600, color: 'var(--primary-700)' }}>تخفیفهای قابل اعمال:</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{suggestions.map((s) => (
|
||||
<div key={s.rule_uuid} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 13, color: '#374151' }}>
|
||||
{s.rule_name} — <span style={{ color: '#D32F2F' }}>{formatRial(s.discount_rials)}</span>
|
||||
{' '}<span style={{ color: '#6B7280', fontSize: 12 }}>({s.discount_type === 'percent' ? `${s.value}٪` : 'مبلغ ثابت'})</span>
|
||||
<span style={{ fontSize: 13, color: 'var(--text)' }}>
|
||||
{s.rule_name} — <span style={{ color: 'var(--danger)' }}>{formatRial(s.discount_rials)}</span>
|
||||
{' '}<span style={{ color: 'var(--text-2)', fontSize: 12 }}>({s.discount_type === 'percent' ? `${s.value}٪` : 'مبلغ ثابت'})</span>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => applyRule(s.rule_uuid)}
|
||||
disabled={discountMut.isPending}
|
||||
style={{ height: 32, minWidth: 64, border: 'none', borderRadius: 6, background: '#5559CE', color: '#fff', fontSize: 12.5, fontWeight: 600, cursor: 'pointer' }}
|
||||
style={{ height: 32, minWidth: 64, border: 'none', borderRadius: 6, background: 'var(--primary)', color: 'var(--on-primary)', fontSize: 12.5, fontWeight: 600, cursor: 'pointer' }}
|
||||
>
|
||||
اعمال
|
||||
</button>
|
||||
@@ -178,8 +178,8 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
|
||||
{/* تخفیف — port of tauri DiscountInput (نوع + مقدار + ثبت) */}
|
||||
<span style={fieldLabel}>تخفیف:</span>
|
||||
<div style={{ display: 'flex', alignItems: 'stretch', gap: 8 }}>
|
||||
<div className="bg-white dark:bg-[#222433] dark:border-[#35343D]" style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', border: '1px solid #e0e0e0', borderRadius: 8, marginBottom: 16 }}>
|
||||
<div style={{ minWidth: 150, borderLeft: '1px solid #e0e0e0', padding: '0 4px' }}>
|
||||
<div className="bg-[var(--surface)]" style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', border: '1px solid var(--border-2)', borderRadius: 8, marginBottom: 16 }}>
|
||||
<div style={{ minWidth: 150, borderLeft: '1px solid var(--border-2)', padding: '0 4px' }}>
|
||||
<SearchableSelect
|
||||
options={[{ value: 'percent', label: 'درصدی' }, { value: 'fixed', label: 'مبلغ ثابت' }]}
|
||||
value={discountType}
|
||||
@@ -198,7 +198,7 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
|
||||
type="button"
|
||||
onClick={applyDiscount}
|
||||
disabled={discountMut.isPending || !discountType || discountValue <= 0}
|
||||
style={{ height: 40, minWidth: 72, border: 'none', borderRadius: '0 4px 4px 0', background: '#E8EBFF', color: '#3B3F9F', fontSize: 13, fontWeight: 600, cursor: 'pointer' }}
|
||||
style={{ height: 40, minWidth: 72, border: 'none', borderRadius: '0 4px 4px 0', background: 'var(--primary-soft2)', color: 'var(--primary-700)', fontSize: 13, fontWeight: 600, cursor: 'pointer' }}
|
||||
>
|
||||
ثبت
|
||||
</button>
|
||||
@@ -207,64 +207,64 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
|
||||
onClick={removeDiscount}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 4, flexShrink: 0, cursor: 'pointer', marginBottom: 16, minWidth: 120, justifyContent: 'center' }}
|
||||
>
|
||||
<TrashRed color="#EF4444" size={18} />
|
||||
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 13, color: '#6B7280' }}>حذف تخفیف</span>
|
||||
<TrashRed color="var(--danger)" size={18} />
|
||||
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>حذف تخفیف</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* خلاصهی مبلغ: قبل از تخفیف / تخفیف / نهایی / منبع */}
|
||||
<div className="dark:border-[#35343D]" style={{ border: '1px solid #e0e0e0', borderRadius: 8, padding: 12, marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<div style={{ border: '1px solid var(--border-2)', borderRadius: 8, padding: 12, marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{hasInsurance && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
|
||||
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>هزینه کل خدمات:</span>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ color: '#2f2f2f' }}>{formatRial(grossTotal)}</span>
|
||||
<span style={{ color: 'var(--text-2)' }}>هزینه کل خدمات:</span>
|
||||
<span style={{ color: 'var(--text)' }}>{formatRial(grossTotal)}</span>
|
||||
</div>
|
||||
{baseInsurance > 0 && (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
|
||||
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>سهم بیمه پایه:</span>
|
||||
<span style={{ color: '#2E7D32' }}>{formatRial(baseInsurance)}</span>
|
||||
<span style={{ color: 'var(--text-2)' }}>سهم بیمه پایه:</span>
|
||||
<span style={{ color: 'var(--success)' }}>{formatRial(baseInsurance)}</span>
|
||||
</div>
|
||||
)}
|
||||
{suppInsurance > 0 && (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
|
||||
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>سهم بیمه تکمیلی:</span>
|
||||
<span style={{ color: '#2E7D32' }}>{formatRial(suppInsurance)}</span>
|
||||
<span style={{ color: 'var(--text-2)' }}>سهم بیمه تکمیلی:</span>
|
||||
<span style={{ color: 'var(--success)' }}>{formatRial(suppInsurance)}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
|
||||
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>{hasInsurance ? 'سهم بیمار:' : 'مبلغ قبل از تخفیف:'}</span>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ color: '#2f2f2f' }}>{formatRial(finalPrice)}</span>
|
||||
<span style={{ color: 'var(--text-2)' }}>{hasInsurance ? 'سهم بیمار:' : 'مبلغ قبل از تخفیف:'}</span>
|
||||
<span style={{ color: 'var(--text)' }}>{formatRial(finalPrice)}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
|
||||
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>مبلغ تخفیف{appliedRuleLabel ? ` (${appliedRuleLabel})` : ''}:</span>
|
||||
<span style={{ color: '#D32F2F' }}>{formatRial(discountRials)}</span>
|
||||
<span style={{ color: 'var(--text-2)' }}>مبلغ تخفیف{appliedRuleLabel ? ` (${appliedRuleLabel})` : ''}:</span>
|
||||
<span style={{ color: 'var(--danger)' }}>{formatRial(discountRials)}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, fontWeight: 700, borderTop: '1px dashed #e0e0e0', paddingTop: 6 }}>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ color: '#2f2f2f' }}>مبلغ نهایی قابل پرداخت:</span>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ color: '#2f2f2f' }}>{formatRial(payable)}</span>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, fontWeight: 700, borderTop: '1px dashed var(--border-2)', paddingTop: 6 }}>
|
||||
<span style={{ color: 'var(--text)' }}>مبلغ نهایی قابل پرداخت:</span>
|
||||
<span style={{ color: 'var(--text)' }}>{formatRial(payable)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* تاریخ پرداخت */}
|
||||
<span className="dark:text-[#A1A1A1]" style={{ ...fieldLabel, color: '#3b3b3b' }}>تاریخ پرداخت</span>
|
||||
<span style={{ ...fieldLabel, color: 'var(--text)' }}>تاریخ پرداخت</span>
|
||||
<PersianDateInput value={paymentDate} onChange={setPaymentDate} />
|
||||
|
||||
{/* روشهای پرداخت */}
|
||||
<span className="dark:text-[#A1A1A1]" style={{ ...fieldLabel, color: '#3b3b3b', marginTop: 24 }}>انتخاب روش های پرداخت:</span>
|
||||
<span style={{ ...fieldLabel, color: 'var(--text)', marginTop: 24 }}>انتخاب روش های پرداخت:</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<FilesServiceBalanceWallet />
|
||||
<span style={{ fontSize: 14, color: '#F97316', marginBottom: 8, fontWeight: 600 }}>موجودی کیف پول:</span>
|
||||
<span style={{ fontSize: 14, color: 'var(--accent)', marginBottom: 8, fontWeight: 600 }}>موجودی کیف پول:</span>
|
||||
</div>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 600, color: '#525252', marginBottom: 4 }}>{formatRial(walletBalance)}</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)', marginBottom: 4 }}>{formatRial(walletBalance)}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: '#111827' }}>مبلغ باقیمانده:</span>
|
||||
<span className="dark:text-[#FF5252]" style={{ fontSize: 14, fontWeight: 700, color: '#d32f2f' }}>{formatRial(debt)}</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>مبلغ باقیمانده:</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--danger)' }}>{formatRial(debt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -273,15 +273,15 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
|
||||
{METHODS.map((m) => {
|
||||
const open = expanded === m.key;
|
||||
return (
|
||||
<div key={m.key} className="dark:border-[#35343D]" style={{ borderBottom: '1px solid #e0e0e0' }}>
|
||||
<div key={m.key} style={{ borderBottom: '1px solid var(--border-2)' }}>
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={open}
|
||||
onClick={() => { setExpanded(open ? null : m.key); setAmount(open ? 0 : defaultAmountFor(m.key)); }}
|
||||
style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%', padding: '14px 4px', background: 'transparent', border: 'none', cursor: 'pointer' }}
|
||||
>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 600, color: '#111827' }}>{m.label}</span>
|
||||
<ChevronDownIcon style={{ width: 16, color: '#6B7280', transform: open ? 'rotate(180deg)' : undefined, transition: 'transform .15s' }} />
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>{m.label}</span>
|
||||
<ChevronDownIcon style={{ width: 16, color: 'var(--text-2)', transform: open ? 'rotate(180deg)' : undefined, transition: 'transform .15s' }} />
|
||||
</button>
|
||||
{open && (
|
||||
<div style={{ display: 'flex', gap: 8, padding: '0 4px 14px' }}>
|
||||
@@ -309,34 +309,34 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
|
||||
</div>
|
||||
|
||||
{/* پرداخت شدهها — باکس خطچین tauri */}
|
||||
<div className="dark:border-[#404040] dark:bg-[#222433]" style={{ border: '1px dashed #C7C9F4', borderRadius: 8, padding: 16, marginTop: 16, background: '#fff' }}>
|
||||
<span className="dark:text-[#6A6AD9]" style={{ fontSize: 16, fontWeight: 500, color: '#636bd4', display: 'block', marginBottom: 16 }}>پرداخت شده ها:</span>
|
||||
<div style={{ border: '1px dashed var(--primary-soft2)', borderRadius: 8, padding: 16, marginTop: 16, background: 'var(--surface)' }}>
|
||||
<span style={{ fontSize: 16, fontWeight: 500, color: 'var(--primary)', display: 'block', marginBottom: 16 }}>پرداخت شده ها:</span>
|
||||
{payments.length === 0 ? (
|
||||
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 13, color: '#6B7280' }}>پرداختی ثبت نشده است</span>
|
||||
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>پرداختی ثبت نشده است</span>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{payments.map((p) => (
|
||||
<div key={p.uuid} className="dark:border-[#404040]" style={{ padding: '6px 0', borderBottom: '1px solid #e0e0e0' }}>
|
||||
<div key={p.uuid} style={{ padding: '6px 0', borderBottom: '1px solid var(--border-2)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span className="dark:bg-[#6A6AD9]" style={{ width: 8, height: 8, borderRadius: '50%', background: '#636bd4' }} />
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, color: '#111827' }}>{METHOD_LABELS[p.method] ?? p.method}</span>
|
||||
<span style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--primary)' }} />
|
||||
<span style={{ fontSize: 14, color: 'var(--text)' }}>{METHOD_LABELS[p.method] ?? p.method}</span>
|
||||
</span>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ flex: 1, textAlign: 'left', fontSize: 14, color: '#111827' }}>مبلغ : {formatRial(p.amount_rials)}</span>
|
||||
<span style={{ flex: 1, textAlign: 'left', fontSize: 14, color: 'var(--text)' }}>مبلغ : {formatRial(p.amount_rials)}</span>
|
||||
{p.method !== 'wallet' && (
|
||||
<span style={{ display: 'flex', gap: 6, flexShrink: 0 }}>
|
||||
<button type="button" aria-label="ویرایش پرداخت" onClick={() => setEditPay({ uuid: p.uuid, method: p.method, amountToman: rialToToman(p.amount_rials) })}
|
||||
style={{ border: 'none', background: 'transparent', cursor: 'pointer', color: '#5559ce', display: 'flex' }}>
|
||||
style={{ border: 'none', background: 'transparent', cursor: 'pointer', color: 'var(--primary)', display: 'flex' }}>
|
||||
<PencilIcon style={{ width: 16 }} />
|
||||
</button>
|
||||
<button type="button" aria-label="حذف پرداخت" onClick={() => setDelPayUuid(p.uuid)}
|
||||
style={{ border: 'none', background: 'transparent', cursor: 'pointer', color: '#EF4444', display: 'flex' }}>
|
||||
style={{ border: 'none', background: 'transparent', cursor: 'pointer', color: 'var(--danger)', display: 'flex' }}>
|
||||
<TrashIcon style={{ width: 16 }} />
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="dark:text-[#A1A1A1]" style={{ display: 'flex', gap: 12, marginTop: 4, fontSize: 12, color: '#9CA3AF', flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', gap: 12, marginTop: 4, fontSize: 12, color: 'var(--text-3)', flexWrap: 'wrap' }}>
|
||||
{(p.paid_at ?? p.created_at) && <span>{formatDateTime(p.paid_at ?? p.created_at!)}</span>}
|
||||
{p.created_by_name && <span>ثبت: {p.created_by_name}</span>}
|
||||
</div>
|
||||
@@ -345,8 +345,8 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 500, color: '#111827' }}>مبلغ باقیمانده :</span>
|
||||
<span className="dark:text-[#FF5252]" style={{ fontSize: 14, fontWeight: 600, color: '#d32f2f' }}>{formatRial(debt)}</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text)' }}>مبلغ باقیمانده :</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--danger)' }}>{formatRial(debt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user