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:
@@ -38,9 +38,9 @@ const nowHHMM = () => {
|
||||
const toSessionAt = (iso: string, time: string) => Math.floor(new Date(`${iso}T${time || '12:00'}:00`).getTime() / 1000);
|
||||
|
||||
// برچسب فیلد — tauri Step1Details Typography (fontWeight 500, 0.875rem, #6B7280)
|
||||
const fieldLabel: React.CSSProperties = { fontWeight: 500, lineHeight: '130%', fontSize: '0.875rem', marginBottom: 8, color: '#6B7280', 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 = { fontWeight: 500, lineHeight: '130%', fontSize: '0.875rem', marginBottom: 8, color: 'var(--text-2)', 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;
|
||||
@@ -338,13 +338,13 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel, e
|
||||
|
||||
/** ردیف شمارندهی tauri (باکس ۷۴px، +/− نارنجی #FF7A45، سطل روی تعداد ۱) */
|
||||
const counter = (qty: number, set: (q: number) => void) => (
|
||||
<div className="dark:border-[#404040]" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 74, height: 27, border: '1px solid #d1d1d1', borderRadius: 8, padding: '0 4px', gap: 4 }}>
|
||||
<button type="button" aria-label="افزایش" onClick={() => set(qty + 1)} style={{ border: 'none', background: 'transparent', color: '#FF7A45', cursor: 'pointer', display: 'flex' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 74, height: 27, border: '1px solid var(--border-2)', borderRadius: 8, padding: '0 4px', gap: 4 }}>
|
||||
<button type="button" aria-label="افزایش" onClick={() => set(qty + 1)} style={{ border: 'none', background: 'transparent', color: 'var(--accent)', cursor: 'pointer', display: 'flex' }}>
|
||||
<PlusIcon style={{ width: 16 }} />
|
||||
</button>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 13, color: '#525252', minWidth: 20, textAlign: 'center' }}>{qty}</span>
|
||||
<button type="button" aria-label="کاهش" onClick={() => set(qty - 1)} style={{ border: 'none', background: 'transparent', color: '#FF7A45', cursor: 'pointer', display: 'flex' }}>
|
||||
{qty > 1 ? <MinusIcon style={{ width: 16 }} /> : <TrashRed size={16} color="#f17732" />}
|
||||
<span style={{ fontSize: 13, color: 'var(--text)', minWidth: 20, textAlign: 'center' }}>{qty}</span>
|
||||
<button type="button" aria-label="کاهش" onClick={() => set(qty - 1)} style={{ border: 'none', background: 'transparent', color: 'var(--accent)', cursor: 'pointer', display: 'flex' }}>
|
||||
{qty > 1 ? <MinusIcon style={{ width: 16 }} /> : <TrashRed size={16} color="var(--accent)" />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -353,8 +353,8 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel, e
|
||||
<div style={{ marginTop: 16 }}>
|
||||
{/* پذیرش کننده — tauri UserTick + کاربر لاگینشده */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 28 }}>
|
||||
<UserTick color="#6B7280" size={18} />
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, color: '#616161' }}>پذیرش کننده: {userName || '—'}</span>
|
||||
<UserTick color="var(--text-2)" size={18} />
|
||||
<span style={{ fontSize: 14, color: 'var(--text-2)' }}>پذیرش کننده: {userName || '—'}</span>
|
||||
</div>
|
||||
|
||||
{/* تاریخ و ساعت پذیرش */}
|
||||
@@ -365,8 +365,8 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel, e
|
||||
</div>
|
||||
<div>
|
||||
<span style={fieldLabel}>ساعت پذیرش</span>
|
||||
<div className="bg-white dark:bg-[#222433] dark:border-[#404040]" style={{ display: 'flex', alignItems: 'center', gap: 8, height: 48, border: '1px solid #e1e1e1', borderRadius: 4, padding: '0 8px' }}>
|
||||
<ClockP color="#6B7280" size={18} />
|
||||
<div className="bg-[var(--surface)]" style={{ display: 'flex', alignItems: 'center', gap: 8, height: 48, border: '1px solid var(--border-2)', borderRadius: 4, padding: '0 8px' }}>
|
||||
<ClockP color="var(--text-2)" size={18} />
|
||||
<input
|
||||
type="time"
|
||||
className="time-input-plain"
|
||||
@@ -392,28 +392,28 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel, e
|
||||
<SearchableSelect inputId="service-select" options={itemOptions} value={itemUuid} onChange={v => setItemUuid(v ? String(v) : '')} placeholder="جستجو و انتخاب سرویس..." />
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 20, flexShrink: 0 }}>
|
||||
<FilesServiceAddCard color="#6B7280" />
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 13, color: '#3b3b3b' }}>
|
||||
<FilesServiceAddCard color="var(--text-2)" />
|
||||
<span style={{ fontSize: 13, color: 'var(--text)' }}>
|
||||
قیمت: {currentItem ? formatRial(currentItem.price_rials) : '—'}
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" aria-label="افزودن سرویس" onClick={addService} disabled={!itemUuid} style={{ minWidth: 40, height: 40, borderRadius: 8, background: '#5559CE', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', marginTop: 20 }}>
|
||||
<PlusIcon style={{ width: 18, color: '#fff' }} />
|
||||
<button type="button" aria-label="افزودن سرویس" onClick={addService} disabled={!itemUuid} style={{ minWidth: 40, height: 40, borderRadius: 8, background: 'var(--primary)', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', marginTop: 20 }}>
|
||||
<PlusIcon style={{ width: 18, color: 'var(--on-primary)' }} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* خدمات انتخاب شده */}
|
||||
{selectedServices.length > 0 && (
|
||||
<div style={{ border: '1px dashed #C7C9F4', borderRadius: 8, padding: 16, marginBottom: 16 }}>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 13, fontWeight: 600, color: '#525252', display: 'block', marginBottom: 16 }}>خدمات انتخاب شده</span>
|
||||
<div style={{ border: '1px dashed var(--primary-soft2)', borderRadius: 8, padding: 16, marginBottom: 16 }}>
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', display: 'block', marginBottom: 16 }}>خدمات انتخاب شده</span>
|
||||
{selectedServices.map(item => (
|
||||
<div key={item.uuid} style={{ display: 'flex', alignItems: 'center', gap: 24, padding: '8px 0' }}>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, color: '#525252', minWidth: 120 }}>{item.name}</span>
|
||||
<span style={{ fontSize: 14, color: 'var(--text)', minWidth: 120 }}>{item.name}</span>
|
||||
{counter(item.qty, (q) => setServiceQty(item.uuid, q))}
|
||||
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 12, fontWeight: 500, color: '#3b3b3b' }}>قیمت: {formatRial(item.price * item.qty)}</span>
|
||||
<span style={{ fontSize: 12, fontWeight: 500, color: 'var(--text)' }}>قیمت: {formatRial(item.price * item.qty)}</span>
|
||||
</div>
|
||||
))}
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 13, fontWeight: 600, color: '#525252', display: 'block', marginTop: 16 }}>مجموع قیمت خدمات: {formatRial(servicesTotal)}</span>
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', display: 'block', marginTop: 16 }}>مجموع قیمت خدمات: {formatRial(servicesTotal)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -425,36 +425,36 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel, e
|
||||
|
||||
{/* انتخاب کالای مصرفی */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, color: '#525252', display: 'block', marginBottom: 8 }}>انتخاب کالای مصرفی</span>
|
||||
<span style={{ fontSize: 14, color: 'var(--text)', display: 'block', marginBottom: 8 }}>انتخاب کالای مصرفی</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div style={{ width: 313, maxWidth: '60%' }}>
|
||||
<SearchableSelect inputId="consumable-select" options={consumableOptions} value={consumableUuid} onChange={v => setConsumableUuid(v ? String(v) : '')} placeholder="انتخاب کنید..." />
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, flexShrink: 0 }}>
|
||||
<FilesServiceAddCard color="#6B7280" />
|
||||
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 13, color: '#6B7280' }}>
|
||||
<FilesServiceAddCard color="var(--text-2)" />
|
||||
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>
|
||||
قیمت: {currentConsumable ? formatRial(currentConsumable.price) : '—'}
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" aria-label="افزودن کالا" onClick={addConsumable} disabled={!consumableUuid} style={{ minWidth: 40, height: 40, borderRadius: 8, background: '#5559CE', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<PlusIcon style={{ width: 18, color: '#fff' }} />
|
||||
<button type="button" aria-label="افزودن کالا" onClick={addConsumable} disabled={!consumableUuid} style={{ minWidth: 40, height: 40, borderRadius: 8, background: 'var(--primary)', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<PlusIcon style={{ width: 18, color: 'var(--on-primary)' }} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* کالاهای انتخاب شده — باکس خطچین tauri */}
|
||||
<div style={{ border: '1px dashed #C7C9F4', borderRadius: 8, padding: 16, marginBottom: 16 }}>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 13, fontWeight: 600, color: '#525252', display: 'block', marginBottom: 16 }}>کالاهای انتخاب شده</span>
|
||||
<div style={{ border: '1px dashed var(--primary-soft2)', borderRadius: 8, padding: 16, marginBottom: 16 }}>
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', display: 'block', marginBottom: 16 }}>کالاهای انتخاب شده</span>
|
||||
{selectedConsumables.length === 0 ? (
|
||||
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 13, color: '#6B7280' }}>کالایی انتخاب نشده است</span>
|
||||
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>کالایی انتخاب نشده است</span>
|
||||
) : selectedConsumables.map(item => (
|
||||
<div key={item.uuid} style={{ display: 'flex', alignItems: 'center', gap: 24, padding: '8px 0' }}>
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, color: '#525252', minWidth: 120 }}>{item.name}</span>
|
||||
<span style={{ fontSize: 14, color: 'var(--text)', minWidth: 120 }}>{item.name}</span>
|
||||
{counter(item.qty, (q) => setConsumableQty(item.uuid, q))}
|
||||
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 12, fontWeight: 500, color: '#3b3b3b' }}>قیمت: {formatRial(item.price * item.qty)}</span>
|
||||
<span style={{ fontSize: 12, fontWeight: 500, color: 'var(--text)' }}>قیمت: {formatRial(item.price * item.qty)}</span>
|
||||
</div>
|
||||
))}
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 13, fontWeight: 600, color: '#525252', display: 'block', marginTop: 16 }}>مجموع قیمت کالاها: {formatRial(consumablesTotal)}</span>
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', display: 'block', marginTop: 16 }}>مجموع قیمت کالاها: {formatRial(consumablesTotal)}</span>
|
||||
</div>
|
||||
|
||||
{/* انتخاب پکیج */}
|
||||
@@ -527,7 +527,7 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel, e
|
||||
</div>
|
||||
|
||||
{/* خلاصه مبلغ */}
|
||||
<div className="dark:border-[#404040]" style={{ borderTop: '1px solid var(--border)', paddingTop: 12, marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<div style={{ borderTop: '1px solid var(--border)', paddingTop: 12, marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, color: 'var(--text-3)' }}><span>ویزیت</span><span>{formatRial(afterSupp)}</span></div>
|
||||
{servicesTotal > 0 && <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, color: 'var(--text-3)' }}><span>سهم بیمار خدمات</span><span>{formatRial(servicesPatient)}</span></div>}
|
||||
{consumablesTotal > 0 && <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, color: 'var(--text-3)' }}><span>کالاهای مصرفی</span><span>{formatRial(consumablesTotal)}</span></div>}
|
||||
|
||||
Reference in New Issue
Block a user