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
@@ -26,13 +26,13 @@ function InfoRow({ icon, label, value, chip = false, valueStyle }: {
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
{icon}
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 12, color: '#616161' }}>{label}:</span>
<span style={{ fontSize: 12, color: 'var(--text-2)' }}>{label}:</span>
</div>
<span
className={chip ? 'dark:bg-[#404040] dark:text-[#D7D8ED]' : 'dark:text-[#D7D8ED]'}
className={chip ? 'dark:bg-[var(--surface-2)]' : ''}
style={{
fontSize: 12, color: '#525252', textAlign: 'left',
...(chip ? { background: '#efefef', borderRadius: 8, padding: '2px 8px', color: '#2f2f2f' } : {}),
fontSize: 12, color: 'var(--text)', textAlign: 'left',
...(chip ? { background: 'var(--surface-2)', borderRadius: 8, padding: '2px 8px', color: 'var(--text)' } : {}),
...valueStyle,
}}
>
@@ -55,7 +55,7 @@ export default function AppointmentTurnCard({ appointment, queryKey }: {
const title = appointment.service_name || 'نوبت';
return (
<div
className="bg-white dark:bg-[#222433] border border-[#EDEDED] dark:border-[#35343D]"
className="bg-[var(--surface)] border border-[var(--border)]"
style={{ width: 277, maxWidth: '100%', minHeight: 249, borderRadius: 12, padding: 14, display: 'flex', flexDirection: 'column', gap: 10, boxShadow: '0 1px 6px rgba(15,23,42,0.06)' }}
>
{/* Header */}
@@ -63,13 +63,13 @@ export default function AppointmentTurnCard({ appointment, queryKey }: {
<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={{ fontSize: 14, fontWeight: 600, color: '#525252', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 180 }}>{title}</span>
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 180 }}>{title}</span>
</div>
</div>
<FilesServiceMore style={{ color: '#9CA3AF', flexShrink: 0 }} />
<FilesServiceMore style={{ color: 'var(--text-3)', flexShrink: 0 }} />
</div>
<div className="dark:border-[#35343D]" style={{ borderTop: '1px solid #F1F1F1' }} />
<div style={{ borderTop: '1px solid var(--border)' }} />
{/* Middle: date & time chips */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
@@ -77,7 +77,7 @@ export default function AppointmentTurnCard({ appointment, queryKey }: {
<InfoRow icon={<ClockP />} label="ساعت" chip value={formatTime(appointment.starts_at)} />
</div>
<div className="dark:border-[#35343D]" style={{ borderTop: '1px solid #F1F1F1' }} />
<div style={{ borderTop: '1px solid var(--border)' }} />
{/* Bottom: personnel & status */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
+2 -2
View File
@@ -79,7 +79,7 @@ export default function FreeVisitPrice({ doctorUuid, readOnly = false }: { docto
<label style={{ display: 'inline-flex', alignItems: 'center', gap: 10, fontSize: 13, cursor: 'pointer', marginTop: 16 }}>
<span style={{
position: 'relative', width: 42, height: 22, borderRadius: 999, flexShrink: 0,
background: required ? 'var(--primary)' : '#c4c4c4', transition: 'background .2s',
background: required ? 'var(--primary)' : 'var(--border-2)', transition: 'background .2s',
}}>
<input
type="checkbox" checked={required} role="switch" aria-label="الزامی کردن هزینه ویزیت"
@@ -88,7 +88,7 @@ export default function FreeVisitPrice({ doctorUuid, readOnly = false }: { docto
/>
<span style={{
position: 'absolute', top: 2, insetInlineStart: required ? 22 : 2, width: 18, height: 18,
borderRadius: 999, background: '#fff', transition: 'inset-inline-start .2s', boxShadow: '0 1px 2px rgba(0,0,0,.2)',
borderRadius: 999, background: 'var(--surface)', transition: 'inset-inline-start .2s', boxShadow: '0 1px 2px rgba(0,0,0,.2)',
}} />
</span>
الزامی کردن هزینه ویزیت
+8 -8
View File
@@ -31,14 +31,14 @@ export default function ImageCropModal({ src, fileName, onCancel, onConfirm }: I
return createPortal(
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4" dir="rtl">
<div className="w-full max-w-md rounded-2xl bg-white dark:bg-gray-900 shadow-2xl overflow-hidden">
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-100 dark:border-gray-800">
<p className="text-base font-bold text-slate-800 dark:text-slate-100">برش تصویر</p>
<div className="w-full max-w-md rounded-2xl bg-[var(--surface)] shadow-2xl overflow-hidden">
<div className="flex items-center justify-between px-5 py-4 border-b border-[var(--border)]">
<p className="text-base font-bold text-[var(--text)]">برش تصویر</p>
<button onClick={onCancel}
className="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 text-xl leading-none">×</button>
className="text-[var(--text-3)] hover:text-[var(--text-2)] text-xl leading-none">×</button>
</div>
<div className="relative w-full h-72 bg-slate-100 dark:bg-gray-800">
<div className="relative w-full h-72 bg-[var(--surface-2)]">
<Cropper
image={src}
crop={crop}
@@ -54,18 +54,18 @@ export default function ImageCropModal({ src, fileName, onCancel, onConfirm }: I
<div className="px-5 py-4 space-y-4">
<div className="flex items-center gap-3">
<span className="text-xs text-slate-500 dark:text-slate-400 shrink-0">بزرگنمایی</span>
<span className="text-xs text-[var(--text-2)] shrink-0">بزرگنمایی</span>
<input type="range" min={1} max={3} step={0.01} value={zoom}
onChange={e => setZoom(Number(e.target.value))}
className="w-full accent-indigo-600" />
</div>
<div className="flex items-center justify-end gap-3">
<button onClick={onCancel} disabled={processing}
className="px-4 py-2 rounded-xl text-sm font-medium text-slate-600 dark:text-slate-300 bg-slate-100 dark:bg-gray-800 hover:bg-slate-200 dark:hover:bg-gray-700 disabled:opacity-50">
className="px-4 py-2 rounded-xl text-sm font-medium text-[var(--text-2)] bg-[var(--surface-2)] hover:bg-[var(--surface-3)] dark:hover:bg-[var(--surface-2)] disabled:opacity-50">
انصراف
</button>
<button onClick={handleConfirm} disabled={processing || !area}
className="px-4 py-2 rounded-xl text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50">
className="px-4 py-2 rounded-xl text-sm font-medium text-[var(--on-primary)] bg-[var(--primary-600)] hover:bg-[var(--primary-700)] disabled:opacity-50">
{processing ? 'در حال برش...' : 'ذخیره'}
</button>
</div>
@@ -103,7 +103,7 @@ export default function InvoiceSummaryModal({ invoiceUuid, onClose }: { invoiceU
rows={[[
formatDate(session?.session_at ?? inv.issued_at),
session?.paid_at ? formatDate(session.paid_at) : paid ? formatDate(inv.issued_at) : '—',
<span style={{ color: remaining > 0 ? '#d32f2f' : '#388e3c', fontWeight: 600 }}>{statusLabel}</span>,
<span style={{ color: remaining > 0 ? 'var(--danger)' : 'var(--success)', fontWeight: 600 }}>{statusLabel}</span>,
]]}
/>
{(inv.base_insurance_name || inv.service_category_label) && (
@@ -168,7 +168,7 @@ export default function InvoiceSummaryModal({ invoiceUuid, onClose }: { invoiceU
cols={['مبلغ کل پرداخت شده', 'مبلغ باقی مانده']}
rows={[[
formatRial(paidAmount),
<span style={{ color: remaining > 0 ? '#d32f2f' : '#388e3c', fontWeight: 600 }}>{formatRial(remaining)}</span>,
<span style={{ color: remaining > 0 ? 'var(--danger)' : 'var(--success)', fontWeight: 600 }}>{formatRial(remaining)}</span>,
]]}
/>
</div>
@@ -309,7 +309,7 @@ export default function NewAppointmentDrawer({ doctorUuid, defaultDate, queryKey
<button key={s.start} type="button" dir="ltr" onClick={() => setPickedSlot({ start: s.start, end: s.end })}
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>
);
+11 -11
View File
@@ -10,15 +10,15 @@ interface Tag { uuid: string; name: string; color: string }
/** پرونده > {name} breadcrumb, ported from tauri BreadcrumbHeader. */
export function Breadcrumb({ name, backTo }: { name: string; backTo: string }) {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#6B7280', fontSize: 12, marginBottom: 14 }}>
<Link to={backTo} className="bg-white dark:bg-[#222433]" style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '6px 8px', borderRadius: 12, cursor: 'pointer', color: '#6B7280', textDecoration: 'none' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--text-2)', fontSize: 12, marginBottom: 14 }}>
<Link to={backTo} className="bg-[var(--surface)]" style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '6px 8px', borderRadius: 12, cursor: 'pointer', color: 'var(--text-2)', textDecoration: 'none' }}>
<ArrowLeftPH />
<span>بازگشت</span>
</Link>
<ArrowLeftD />
<span>پرونده</span>
<ArrowLeftD />
<span className="dark:text-[#D7D8ED]" style={{ color: '#111827' }}>{name}</span>
<span style={{ color: 'var(--text)' }}>{name}</span>
</div>
);
}
@@ -37,8 +37,8 @@ function TagDots({ tags }: { tags?: Tag[] }) {
const InfoLine = ({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) => (
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
{icon}
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 12, color: '#2f2f2f' }}>{label}</span>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 500, color: '#2f2f2f' }} dir="ltr">{value}</span>
<span style={{ fontSize: 12, color: 'var(--text)' }}>{label}</span>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text)' }} dir="ltr">{value}</span>
</div>
);
@@ -60,21 +60,21 @@ export default function PatientCaseBanner({ name, recordNumber, mobile, createdA
const complete = !hasDebt;
return (
<div
className="bg-white dark:bg-[#222433] border border-[#EDEDED] dark:border-[#35343D]"
className="bg-[var(--surface)] border border-[var(--border)]"
style={{ borderRadius: 16, padding: '14px 20px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 24, marginBottom: 16, minHeight: 140, flexWrap: 'wrap' }}
>
{/* right — name + tags */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
<span className="dark:text-[#D7D8ED]" style={{ fontWeight: 700, fontSize: 20, color: '#111827' }}>{name}</span>
<span style={{ fontWeight: 700, fontSize: 20, color: 'var(--text)' }}>{name}</span>
<span style={{
height: 29, minWidth: 95, borderRadius: 8, padding: '0 10px', fontSize: 13, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
background: complete ? '#ECFDF3' : 'rgba(255,192,81,0.15)', color: complete ? '#3C9A4F' : '#f59e0b',
background: complete ? 'var(--success-bg)' : 'rgba(255,192,81,0.15)', color: complete ? 'var(--success)' : 'var(--warning)',
}}>{complete ? 'تکمیل شده' : 'تکمیل نشده'}</span>
</div>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 16, color: '#525252' }}>شماره پرونده: {recordNumber || '—'}</span>
<span style={{ fontSize: 16, color: 'var(--text)' }}>شماره پرونده: {recordNumber || '—'}</span>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 16 }}>
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 16, color: '#6B7280' }}>برچسب ها:</span>
<span style={{ fontSize: 16, color: 'var(--text-2)' }}>برچسب ها:</span>
<TagDots tags={tags} />
</div>
</div>
@@ -90,7 +90,7 @@ export default function PatientCaseBanner({ name, recordNumber, mobile, createdA
<InfoLine icon={<FilesServiceNotification />} label="نوبت بعدی:" value={nextAppointment ? formatDate(nextAppointment) : '—'} />
<button
type="button" onClick={onAddNote}
style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: '#f17732', color: '#fff', border: 'none', borderRadius: 10, padding: '0 18px', height: 36, fontSize: 13, fontWeight: 600, cursor: 'pointer' }}
style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: 'var(--accent)', color: 'var(--on-primary)', border: 'none', borderRadius: 10, padding: '0 18px', height: 36, fontSize: 13, fontWeight: 600, cursor: 'pointer' }}
>
<FilesServiceMessage /> یادداشت
</button>
+1 -1
View File
@@ -87,7 +87,7 @@ export default function PatientTagsCell({ record }: { record: PatientRecord }) {
fontSize: 12, height: 24, padding: '0 8px', borderRadius: 999,
border: '1px solid var(--border)',
background: assigned ? tag.color : 'var(--surface-2)',
color: assigned ? '#fff' : 'var(--text-2)',
color: assigned ? 'var(--on-primary)' : 'var(--text-2)',
}}
>
{assigned ? <XMarkIcon style={{ width: 12 }} /> : <PlusIcon style={{ width: 12 }} />}
@@ -141,7 +141,7 @@ export default function PatientsFilterModal({ open, onClose, value, onApply }: {
style={{
fontSize: 12, height: 26, padding: '0 10px', borderRadius: 999, cursor: 'pointer',
border: '1px solid var(--border)',
background: on ? t.color : 'var(--surface-2)', color: on ? '#fff' : 'var(--text-2)',
background: on ? t.color : 'var(--surface-2)', color: on ? 'var(--on-primary)' : 'var(--text-2)',
}}
>
{t.name}
@@ -19,14 +19,14 @@ const PAYMENT_LABELS = PAYMENT_METHOD_LABELS;
/** vertical hairline divider between meta columns (tauri MUI vertical Divider). */
function VDivider({ h = 24 }: { h?: number }) {
return <span className="dark:bg-[#35343D]" style={{ width: 1, height: h, background: '#E5E7EB', flexShrink: 0, margin: '0 16px', alignSelf: 'center' }} />;
return <span style={{ width: 1, height: h, background: 'var(--surface-2)', flexShrink: 0, margin: '0 16px', alignSelf: 'center' }} />;
}
function Meta({ label, value }: { label: string; value: string }) {
return (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 13 }}>
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>{label}:</span>
<span className="dark:text-[#D7D8ED]" style={{ color: '#525252', fontWeight: 500 }}>{value}</span>
<span style={{ color: 'var(--text-2)' }}>{label}:</span>
<span style={{ color: 'var(--text)', fontWeight: 500 }}>{value}</span>
</span>
);
}
@@ -49,16 +49,16 @@ export default function SessionPaymentAccordion({ session, expanded, onToggle }:
const finalPrice = formatRial(session.final_price_rials ?? 0);
return (
<div className="dark:border-[#35343D]" style={{ border: '1px solid #E5E7EB', borderRadius: 8, marginBottom: 16, overflow: 'hidden' }}>
<div style={{ border: '1px solid var(--border)', borderRadius: 8, marginBottom: 16, overflow: 'hidden' }}>
{/* summary */}
<button
type="button"
onClick={onToggle}
aria-expanded={expanded}
className="bg-white dark:bg-[#2B2D3A]"
className="bg-[var(--surface)]"
style={{ display: 'flex', alignItems: 'center', gap: 4, width: '100%', padding: '12px 16px', border: 'none', cursor: 'pointer', textAlign: 'start', flexWrap: 'wrap' }}
>
<span className="dark:text-[#D7D8ED]" style={{ minWidth: 150, color: '#2f2f2f', fontWeight: 500, fontSize: 14 }}>{name}</span>
<span style={{ minWidth: 150, color: 'var(--text)', fontWeight: 500, fontSize: 14 }}>{name}</span>
<VDivider />
<Meta label="تاریخ" value={session.created_at ? formatDate(session.created_at) : '—'} />
<VDivider />
@@ -66,26 +66,26 @@ export default function SessionPaymentAccordion({ session, expanded, onToggle }:
<span style={{ flex: 1 }} />
<VDivider />
<span style={{
color: '#fff', padding: '3px 16px', borderRadius: 4, fontSize: 12, fontWeight: 600,
textAlign: 'center', minWidth: 90, background: paid ? '#10B981' : '#F59E0B',
color: 'var(--on-primary)', padding: '3px 16px', borderRadius: 4, fontSize: 12, fontWeight: 600,
textAlign: 'center', minWidth: 90, background: paid ? 'var(--success)' : 'var(--warning)',
}}>
{paid ? 'پرداخت شده' : 'تسویه نشده'}
</span>
<ChevronDownIcon className="dark:text-[#D7D8ED]" style={{ width: 20, height: 20, color: '#525252', marginInlineStart: 8, transition: 'transform .2s', transform: expanded ? 'rotate(180deg)' : 'none' }} />
<ChevronDownIcon style={{ width: 20, height: 20, color: 'var(--text)', marginInlineStart: 8, transition: 'transform .2s', transform: expanded ? 'rotate(180deg)' : 'none' }} />
</button>
{/* details */}
{expanded && (
<div className="dark:border-[#35343D] dark:bg-[#222433]" style={{ borderTop: '1px solid #E5E7EB' }}>
<div className="dark:bg-[var(--surface)]" style={{ borderTop: '1px solid var(--border)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '12px 16px 8px' }}>
<FilesServicePaymentsCheck color="#6B7280" size={18} style={{ transform: 'rotate(180deg)' }} />
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280', fontWeight: 600, fontSize: 13 }}>پرداختیها</span>
<FilesServicePaymentsCheck color="var(--text-2)" size={18} style={{ transform: 'rotate(180deg)' }} />
<span style={{ color: 'var(--text-2)', fontWeight: 600, fontSize: 13 }}>پرداختیها</span>
</div>
<div style={{ padding: '0 16px 16px' }}>
{paid ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '12px 0', flexWrap: 'wrap' }}>
<FilesServicePaymentsCheck color="#6B7280" size={20} />
<FilesServicePaymentsCheck color="var(--text-2)" size={20} />
<Meta label="تاریخ" value={formatDate(session.updated_at ?? session.created_at ?? 0)} />
<VDivider h={32} />
<Meta label="مبلغ" value={finalPrice} />
@@ -95,7 +95,7 @@ export default function SessionPaymentAccordion({ session, expanded, onToggle }:
<Meta label="پرسنل" value={session.doctor_name || '—'} />
</div>
) : (
<div className="dark:text-[#A1A1A1]" style={{ padding: 16, textAlign: 'center', color: '#6B7280', fontSize: 13 }}>هیچ پرداختی ثبت نشده است.</div>
<div style={{ padding: 16, textAlign: 'center', color: 'var(--text-2)', fontSize: 13 }}>هیچ پرداختی ثبت نشده است.</div>
)}
</div>
</div>
+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>
@@ -23,9 +23,9 @@ describe('SessionStepper', () => {
it('کانکتور گام‌های طی‌شده بنفش و بقیه خاکستری', () => {
render(<SessionStepper steps={STEPS} activeStep={1} />);
// jsdom رنگ hex را به rgb نرمال می‌کند
expect(screen.getByTestId('step-connector-1').style.background).toContain('rgb(85, 89, 206)');
expect(screen.getByTestId('step-connector-2').style.background).toContain('rgb(234, 234, 240)');
// رنگ‌ها از توکن‌های تم می‌آیند تا در دارک‌مود هم درست باشند
expect(screen.getByTestId('step-connector-1').style.background).toContain('var(--primary)');
expect(screen.getByTestId('step-connector-2').style.background).toContain('var(--surface-2)');
});
it('استپر خالی: بدون کرش و بدون کانکتور', () => {
+4 -4
View File
@@ -21,8 +21,8 @@ function StepIcon({ index, active, completed }: { index: number; active: boolean
if (completed && !active) {
// دایره سفید با تیک سبز (tauri ColorlibStepIconRoot completed state)
return (
<div style={{ width: 52, height: 52, borderRadius: '50%', background: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg width="32" height="32" viewBox="0 0 24 24" fill="#22C55E" xmlns="http://www.w3.org/2000/svg">
<div style={{ width: 52, height: 52, borderRadius: '50%', background: 'var(--surface)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg width="32" height="32" viewBox="0 0 24 24" style={{ fill: 'var(--success)' }} xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
</svg>
</div>
@@ -49,12 +49,12 @@ export default function SessionStepper({ activeStep = 0, steps = [] }: { activeS
style={{
position: 'absolute', top: 22, left: 'calc(50% + 40px)', right: 'calc(-50% + 40px)',
height: 3, borderRadius: 1,
background: i <= activeStep ? 'linear-gradient(95deg, #5559ce 0%, #5559ce 100%)' : '#eaeaf0',
background: i <= activeStep ? 'linear-gradient(95deg, var(--primary) 0%, var(--primary) 100%)' : 'var(--surface-2)',
}}
/>
)}
<StepIcon index={ICON_BY_LABEL[label] ?? i + 1} active={i === activeStep} completed={i < activeStep} />
<span className="dark:text-[#D7D8ED]" style={{ marginTop: 8, fontSize: '0.875rem', fontWeight: 600, color: '#3b3b3b' }}>
<span style={{ marginTop: 8, fontSize: '0.875rem', fontWeight: 600, color: 'var(--text)' }}>
{label}
</span>
</div>
@@ -382,7 +382,7 @@ function StatusToggle({ contract, onToggle, disabled }: { contract: Contract; on
background: contract.is_active ? 'var(--primary)' : 'var(--border)',
}}>
<span style={{
position: 'absolute', top: 2, width: 16, height: 16, borderRadius: 999, background: '#fff', transition: 'inset-inline .2s',
position: 'absolute', top: 2, width: 16, height: 16, borderRadius: 999, background: 'var(--surface)', transition: 'inset-inline .2s',
insetInlineStart: contract.is_active ? 18 : 2,
}} />
</span>
@@ -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 {
@@ -56,20 +56,20 @@ export function NewAppointmentsTable({ rows, loading, queryKey, emptyText }: Pro
}
if (!rows.length) {
return (
<p className="text-center py-[32px] text-[13.5px] text-[#7E7E7E] dark:text-[#A1A1A1]">
<p className="text-center py-[32px] text-[13.5px] text-[var(--text-2)]">
{emptyText ?? 'نوبتی برای امروز ثبت نشده'}
</p>
);
}
return (
<div className="w-full overflow-x-auto rounded-[8px] border border-solid border-[#E7E7E7] dark:border-[#35343D]">
<div className="w-full overflow-x-auto rounded-[8px] border border-solid border-[var(--border)]">
<table className="w-full border-collapse">
<thead>
<tr className="bg-[#EFEFEF] dark:bg-[#35343D]">
<tr className="bg-[var(--surface-2)]">
{HEAD.map((h, i) => (
<th
key={h}
className={`text-[#616161] dark:text-[#D7D8ED] text-[14px] font-normal py-[10px] px-[18px] whitespace-nowrap ${i === HEAD.length - 1 ? 'text-center' : 'text-start'}`}
className={`text-[var(--text-2)] dark:text-[var(--text)] text-[14px] font-normal py-[10px] px-[18px] whitespace-nowrap ${i === HEAD.length - 1 ? 'text-center' : 'text-start'}`}
>
{h}
</th>
@@ -80,7 +80,7 @@ export function NewAppointmentsTable({ rows, loading, queryKey, emptyText }: Pro
{rows.map((r, idx) => (
<tr
key={r.uuid}
className="border-b border-[#DBDBDB] dark:border-transparent hover:bg-[#f4f5fd] dark:hover:bg-[#2a2c3d] transition-colors"
className="border-b border-[var(--border-2)] dark:border-transparent hover:bg-[var(--primary-soft)] dark:hover:bg-[var(--surface-2)] transition-colors"
>
<Cell>{new Intl.NumberFormat('fa-IR').format(idx + 1)}</Cell>
<Cell>{r.patient_name || '—'}</Cell>
@@ -97,7 +97,7 @@ export function NewAppointmentsTable({ rows, loading, queryKey, emptyText }: Pro
<td className="py-[10px] px-[18px] text-center">
<Link
to={isoDay(r.slot_start) ? `/admin/appointments?date=${isoDay(r.slot_start)}` : '/admin/appointments'}
className="text-[#5559ce] text-[12px] font-medium hover:underline whitespace-nowrap"
className="text-[var(--primary)] text-[12px] font-medium hover:underline whitespace-nowrap"
>
مشاهده
</Link>
@@ -112,7 +112,7 @@ export function NewAppointmentsTable({ rows, loading, queryKey, emptyText }: Pro
/** نمایش فقط‌خواندنی وضعیت — وقتی version یا queryKey در دسترس نیست. */
function StatusPill({ status }: { status: string }) {
const meta = STATUS_META[status] ?? { label: status, color: '#9ca3af' };
const meta = STATUS_META[status] ?? { label: status, color: 'var(--text-3)' };
return (
<span
className="inline-flex items-center gap-[5px] px-[10px] py-[3px] rounded-full text-[12px] font-bold whitespace-nowrap"
@@ -128,7 +128,7 @@ function StatusPill({ status }: { status: string }) {
function Cell({ children, className = '' }: { children: React.ReactNode; className?: string }) {
return (
<td
className={`text-[#616161] dark:text-[#A1A1A1] text-[16px] font-medium py-[10px] px-[18px] whitespace-nowrap text-start ${className}`}
className={`text-[var(--text-2)] text-[16px] font-medium py-[10px] px-[18px] whitespace-nowrap text-start ${className}`}
>
{children}
</td>
@@ -31,7 +31,7 @@ describe('TauriBarChart', () => {
it('renders one bar per day of a full Jalali month', () => {
const month: ChartPoint[] = Array.from({ length: 31 }, (_, i) => ({ label: String(i + 1), value: i % 5 }));
const { container } = render(<TauriBarChart data={month} />);
expect(container.querySelectorAll('.bg-\\[\\#5559CE\\]')).toHaveLength(31);
expect(container.querySelectorAll('[class*="bg-[var(--primary)]"]')).toHaveLength(31);
});
/**
@@ -44,7 +44,7 @@ describe('TauriBarChart', () => {
const month: ChartPoint[] = Array.from({ length: 31 }, (_, i) => ({ label: String(i + 1), value: i % 5 }));
const { container } = render(<TauriBarChart data={month} />);
const bar = container.querySelector('.bg-\\[\\#5559CE\\]') as HTMLElement;
const bar = container.querySelector('[class*="bg-[var(--primary)]"]') as HTMLElement;
// کسری از ستون، نه تمام آن — وگرنه میله‌ها بدون فاصله به هم می‌چسبند
expect(bar.className).toMatch(/\bw-\[\d+%\]/);
expect(bar.className.split(/\s+/)).not.toContain('w-full');
@@ -91,7 +91,7 @@ function ChartFrame({
<div className="flex-1 flex min-h-0" style={{ alignItems: 'stretch' }}>
{/* y-axis ticks, aligned to gridlines */}
<div
className="flex flex-col justify-between text-[14px] font-normal text-[#858D9D] text-left pl-[4px] shrink-0"
className="flex flex-col justify-between text-[14px] font-normal text-[var(--text-2)] text-left pl-[4px] shrink-0"
style={{ width: yWidth, alignItems: 'flex-start' }}
>
{ticks.map((t, i) => (
@@ -106,7 +106,7 @@ function ChartFrame({
{ticks.map((_, i) => (
<div
key={i}
className="absolute left-0 right-0 border-t border-dashed border-[#E7E7E7] dark:border-[#35343D]"
className="absolute left-0 right-0 border-t border-dashed border-[var(--border)]"
style={{ top: `${(i / (ticks.length - 1)) * 100}%` }}
/>
))}
@@ -116,7 +116,7 @@ function ChartFrame({
{/* x-axis labels */}
<div className="flex pt-[8px]" style={{ paddingRight: yWidth, alignItems: 'flex-start' }}>
{labels.map((l, i) => (
<span key={i} className="flex-1 min-w-0 overflow-hidden text-center text-[10px] font-normal text-[#7E7E7E] whitespace-nowrap">
<span key={i} className="flex-1 min-w-0 overflow-hidden text-center text-[10px] font-normal text-[var(--text-2)] whitespace-nowrap">
{l}
</span>
))}
@@ -145,7 +145,7 @@ export function TauriBarChart({ data }: { data: ChartPoint[] }) {
<div key={i} className="flex-1 min-w-0 flex items-end justify-center" style={{ alignItems: 'flex-end' }}>
<div
title={faNum.format(d.value)}
className="w-[60%] max-w-[24px] rounded-t-[3px] bg-[#5559CE]"
className="w-[60%] max-w-[24px] rounded-t-[3px] bg-[var(--primary)]"
style={{
height: `${(d.value / top) * 100}%`,
minHeight: d.value > 0 ? 4 : 0,
@@ -278,7 +278,7 @@ export function TauriLineChart({ data, actualCount }: { data: ChartPoint[]; actu
top: `${(pts[i][1] / H) * 100}%`,
transform: 'translate(-50%, calc(-100% - 12px))',
background: isForecast ? 'var(--accent)' : 'var(--text)',
color: '#fff',
color: 'var(--on-primary)',
boxShadow: 'var(--shadow)',
}}
>
@@ -302,7 +302,7 @@ export function TauriLineChart({ data, actualCount }: { data: ChartPoint[]; actu
function EmptyChart() {
return (
<div className="h-[300px] w-full grid place-items-center text-[13px] text-[#7E7E7E]">
<div className="h-[300px] w-full grid place-items-center text-[13px] text-[var(--text-2)]">
دادهای برای نمایش نیست
</div>
);
@@ -31,7 +31,7 @@ function SmSelector({ options, value, onChange }: { options: SelectorOption[]; v
}
function ChartTitle({ text }: { text: string }) {
return <p className="text-[#525252] dark:text-[#D7D8ED] text-[14px] md:text-[16px] font-bold">{text}</p>;
return <p className="text-[var(--text)] text-[14px] md:text-[16px] font-bold">{text}</p>;
}
function ChartCard({
@@ -48,7 +48,7 @@ function ChartCard({
children: React.ReactNode;
}) {
return (
<div className="w-full rounded-[8px] border border-solid border-[#EFEFEF] dark:border-transparent bg-[#FFF] dark:bg-[#222433]">
<div className="w-full rounded-[8px] border border-solid border-[var(--border)] dark:border-transparent bg-[var(--surface)]">
<div className="flex px-[20px] py-[12px] md:py-[14px] lg:py-[16px] items-center justify-between gap-[12px]">
<ChartTitle text={title} />
<SmSelector options={selectorOptions} value={selectorValue} onChange={onSelectorChange} />
@@ -141,12 +141,12 @@ export function TauriDashboardView({
</div>
{/* New appointments list */}
<div className="w-full rounded-[8px] border border-solid border-transparent md:border-[#EFEFEF] dark:border-transparent bg-transparent md:bg-[#FFF] md:dark:bg-[#222433]">
<div className="w-full rounded-[8px] border border-solid border-transparent md:border-[var(--border)] dark:border-transparent bg-transparent md:bg-[var(--surface)] md:dark:bg-[var(--surface)]">
<div className="md:pt-[18px] md:px-[24px] md:pb-[20px] flex items-center justify-between">
<p className="text-[#525252] dark:text-[#D7D8ED] text-[14px] md:text-[16px] font-bold">لیست نوبتهای جدید</p>
<p className="text-[var(--text)] text-[14px] md:text-[16px] font-bold">لیست نوبتهای جدید</p>
<Link
to="/admin/appointments"
className="flex items-center gap-[4px] p-1 text-[12px] md:text-[14px] text-[#616161] dark:text-[#A1A1A1] font-normal hover:text-[#5559ce]"
className="flex items-center gap-[4px] p-1 text-[12px] md:text-[14px] text-[var(--text-2)] font-normal hover:text-[var(--primary)]"
>
نوبتها
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
@@ -19,7 +19,7 @@ export interface StatCardModel {
function TauriStatCard({ data }: { data: StatCardModel }) {
return (
<li
className={`px-[8px] md:px-[16px] lg:px-[24px] py-[18px] sm:py-[20px] md:py-[22px] lg:py-[24px] rounded-[8px] flex items-center justify-start gap-[4px] md:gap-[8px] lg:gap-[12px] ${data.cardColor} dark:bg-[#222433]`}
className={`px-[8px] md:px-[16px] lg:px-[24px] py-[18px] sm:py-[20px] md:py-[22px] lg:py-[24px] rounded-[8px] flex items-center justify-start gap-[4px] md:gap-[8px] lg:gap-[12px] ${data.cardColor} dark:bg-[var(--surface)]`}
>
<div
className={`p-[8px] w-[calc(18px+8px)] sm:w-[calc(20px+8px)] md:w-[calc(22px+8px)] lg:w-[calc(24px+8px)] min-w-[calc(18px+8px)] sm:min-w-[calc(20px+8px)] md:min-w-[calc(22px+8px)] lg:min-w-[calc(24px+8px)] h-[calc(18px+8px)] sm:h-[calc(20px+8px)] md:h-[calc(22px+8px)] lg:h-[calc(24px+8px)] grid place-items-center rounded-full ${data.iconColor}`}
@@ -27,10 +27,10 @@ function TauriStatCard({ data }: { data: StatCardModel }) {
{data.icon}
</div>
<div className="flex flex-col w-full items-start justify-center gap-[8px]">
<p className="text-[#3B3B3B] text-wrap dark:text-[#D7D8ED] text-[14px] sm:text-[16px] md:text-[18px] lg:text-[20px] font-medium">
<p className="text-[var(--text)] text-wrap text-[14px] sm:text-[16px] md:text-[18px] lg:text-[20px] font-medium">
{data.value}
</p>
<p className="text-[#616161] dark:text-[#A1A1A1] text-[12px] md:text-[14px] lg:text-[16px] font-normal">
<p className="text-[var(--text-2)] text-[12px] md:text-[14px] lg:text-[16px] font-normal">
{data.label}
</p>
</div>
@@ -65,28 +65,28 @@ export function TauriStatCards({
const cards: StatCardModel[] = [
{
cardColor: 'bg-[rgba(241,119,50,0.10)]',
iconColor: 'bg-[#F17732]',
iconColor: 'bg-[var(--accent)]',
icon: <UserAddIcon />,
value: `${formatNumber(stats.totalPatients)}+`,
label: 'تعداد کل مراجعین',
},
{
cardColor: 'bg-[rgba(0,157,121,0.10)]',
iconColor: 'bg-[#009D79]',
iconColor: 'bg-[var(--success)]',
icon: <CardIcon />,
value: formatRial(stats.totalPaymentsRials),
label: 'کل پرداختی‌ها',
},
{
cardColor: 'bg-[rgba(85,89,206,0.08)]',
iconColor: 'bg-[#5559CE]',
iconColor: 'bg-[var(--primary)]',
icon: <CardTickIcon />,
value: formatRial(stats.todayPaymentsRials),
label: 'پرداختی‌های امروز',
},
{
cardColor: 'bg-[rgba(255,192,81,0.11)]',
iconColor: 'bg-[#FFC051]',
iconColor: 'bg-[var(--warning)]',
icon: <CalendarIcon />,
value: `${formatNumber(stats.todayAppointments)}+`,
label: 'تعداد نوبت‌های امروز',
@@ -68,10 +68,10 @@ export function CalendarIcon() {
}
/** فلش رو به پایین — status chip chevron (source: ArrowDownBlueP) */
export function ChevronDownIcon({ color = '#5559CE' }: { color?: string }) {
export function ChevronDownIcon({ color = 'currentColor' }: { color?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M16.6004 7.4585L11.1671 12.8918C10.5254 13.5335 9.47539 13.5335 8.83372 12.8918L3.40039 7.4585" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" style={{ color }}>
<path d="M16.6004 7.4585L11.1671 12.8918C10.5254 13.5335 9.47539 13.5335 8.83372 12.8918L3.40039 7.4585" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
@@ -8,22 +8,22 @@ type IconProps = { color?: string; style?: CSSProperties };
/* ── Service card ─────────────────────────────────────────────────────────── */
export function FilesServiceSuccess({ style }: { style?: CSSProperties }) {
export function FilesServiceSuccess({ style }: { style?: CSSProperties }){
return (
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none" style={style}>
<rect width="36" height="36" rx="8" fill="#0D9F43" fillOpacity="0.15" />
<path d="M17.9993 26.3332C22.5827 26.3332 26.3327 22.5832 26.3327 17.9998C26.3327 13.4165 22.5827 9.6665 17.9993 9.6665C13.416 9.6665 9.66602 13.4165 9.66602 17.9998C9.66602 22.5832 13.416 26.3332 17.9993 26.3332Z" stroke="#3C9A4F" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.459 17.9999L16.8173 20.3583L21.5423 15.6416" stroke="#3C9A4F" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<rect width="36" height="36" rx="8" style={{ fill: 'var(--success)', fillOpacity: 0.15 }}/>
<path d="M17.9993 26.3332C22.5827 26.3332 26.3327 22.5832 26.3327 17.9998C26.3327 13.4165 22.5827 9.6665 17.9993 9.6665C13.416 9.6665 9.66602 13.4165 9.66602 17.9998C9.66602 22.5832 13.416 26.3332 17.9993 26.3332Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--success)' }}/>
<path d="M14.459 17.9999L16.8173 20.3583L21.5423 15.6416" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--success)' }}/>
</svg>
);
}
export function FilesServiceMore({ style }: { style?: CSSProperties }) {
export function FilesServiceMore({ style }: { style?: CSSProperties }){
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" style={style}>
<path d="M4.16667 8.3335C3.25 8.3335 2.5 9.0835 2.5 10.0002C2.5 10.9168 3.25 11.6668 4.16667 11.6668C5.08333 11.6668 5.83333 10.9168 5.83333 10.0002C5.83333 9.0835 5.08333 8.3335 4.16667 8.3335Z" fill="#3B3B3B" stroke="#3B3B3B" />
<path d="M15.8327 8.3335C14.916 8.3335 14.166 9.0835 14.166 10.0002C14.166 10.9168 14.916 11.6668 15.8327 11.6668C16.7493 11.6668 17.4993 10.9168 17.4993 10.0002C17.4993 9.0835 16.7493 8.3335 15.8327 8.3335Z" fill="#3B3B3B" stroke="#3B3B3B" />
<path d="M10.0007 8.3335C9.08398 8.3335 8.33398 9.0835 8.33398 10.0002C8.33398 10.9168 9.08398 11.6668 10.0007 11.6668C10.9173 11.6668 11.6673 10.9168 11.6673 10.0002C11.6673 9.0835 10.9173 8.3335 10.0007 8.3335Z" fill="#3B3B3B" stroke="#3B3B3B" />
<path d="M4.16667 8.3335C3.25 8.3335 2.5 9.0835 2.5 10.0002C2.5 10.9168 3.25 11.6668 4.16667 11.6668C5.08333 11.6668 5.83333 10.9168 5.83333 10.0002C5.83333 9.0835 5.08333 8.3335 4.16667 8.3335Z" style={{ fill: 'var(--text)', stroke: 'var(--text)' }}/>
<path d="M15.8327 8.3335C14.916 8.3335 14.166 9.0835 14.166 10.0002C14.166 10.9168 14.916 11.6668 15.8327 11.6668C16.7493 11.6668 17.4993 10.9168 17.4993 10.0002C17.4993 9.0835 16.7493 8.3335 15.8327 8.3335Z" style={{ fill: 'var(--text)', stroke: 'var(--text)' }}/>
<path d="M10.0007 8.3335C9.08398 8.3335 8.33398 9.0835 8.33398 10.0002C8.33398 10.9168 9.08398 11.6668 10.0007 11.6668C10.9173 11.6668 11.6673 10.9168 11.6673 10.0002C11.6673 9.0835 10.9173 8.3335 10.0007 8.3335Z" style={{ fill: 'var(--text)', stroke: 'var(--text)' }}/>
</svg>
);
}
@@ -33,10 +33,10 @@ export function FilesServiceMore({ style }: { style?: CSSProperties }) {
export function FilesServicePhone() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none">
<rect width="36" height="36" rx="18" fill="#FFD8C5" fillOpacity="0.6" />
<path d="M26.3077 23.2748C26.3077 23.5748 26.241 23.8832 26.0993 24.1832C25.9577 24.4832 25.7744 24.7665 25.5327 25.0332C25.1244 25.4832 24.6743 25.8082 24.166 26.0165C23.666 26.2248 23.1244 26.3332 22.541 26.3332C21.691 26.3332 20.7827 26.1332 19.8243 25.7248C18.866 25.3165 17.9077 24.7665 16.9577 24.0748C15.9993 23.3748 15.091 22.5998 14.2243 21.7415C13.366 20.8748 12.591 19.9665 11.8993 19.0165C11.216 18.0665 10.666 17.1165 10.266 16.1748C9.86602 15.2248 9.66602 14.3165 9.66602 13.4498C9.66602 12.8832 9.76602 12.3415 9.96602 11.8415C10.166 11.3332 10.4827 10.8665 10.9243 10.4498C11.4577 9.92484 12.041 9.6665 12.6577 9.6665C12.891 9.6665 13.1243 9.7165 13.3327 9.8165C13.5493 9.9165 13.741 10.0665 13.891 10.2832L15.8243 13.0082C15.9743 13.2165 16.0827 13.4082 16.1577 13.5915C16.2327 13.7665 16.2743 13.9415 16.2743 14.0998C16.2743 14.2998 16.216 14.4998 16.0993 14.6915C15.991 14.8832 15.8327 15.0832 15.6327 15.2832L14.9993 15.9415C14.9077 16.0332 14.866 16.1415 14.866 16.2748C14.866 16.3415 14.8743 16.3998 14.891 16.4665C14.916 16.5332 14.941 16.5832 14.9577 16.6332C15.1077 16.9082 15.366 17.2665 15.7327 17.6998C16.1077 18.1332 16.5077 18.5748 16.941 19.0165C17.391 19.4582 17.8243 19.8665 18.266 20.2415C18.6993 20.6082 19.0577 20.8582 19.341 21.0082C19.3827 21.0248 19.4327 21.0498 19.491 21.0748C19.5577 21.0998 19.6243 21.1082 19.6993 21.1082C19.841 21.1082 19.9493 21.0582 20.041 20.9665L20.6743 20.3415C20.8827 20.1332 21.0827 19.9748 21.2743 19.8748C21.466 19.7582 21.6577 19.6998 21.866 19.6998C22.0243 19.6998 22.191 19.7332 22.3743 19.8082C22.5577 19.8832 22.7494 19.9915 22.9577 20.1332L25.716 22.0915C25.9327 22.2415 26.0827 22.4165 26.1743 22.6248C26.2577 22.8332 26.3077 23.0415 26.3077 23.2748Z" stroke="#F17732" strokeWidth="1.5" strokeMiterlimit="10" />
<path d="M23.4167 15.5002C23.4167 15.0002 23.025 14.2335 22.4417 13.6085C21.9083 13.0335 21.2 12.5835 20.5 12.5835" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M26.3333 15.4998C26.3333 12.2748 23.725 9.6665 20.5 9.6665" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<rect width="36" height="36" rx="18" style={{ fill: 'var(--accent-bg)', fillOpacity: 0.6 }}/>
<path d="M26.3077 23.2748C26.3077 23.5748 26.241 23.8832 26.0993 24.1832C25.9577 24.4832 25.7744 24.7665 25.5327 25.0332C25.1244 25.4832 24.6743 25.8082 24.166 26.0165C23.666 26.2248 23.1244 26.3332 22.541 26.3332C21.691 26.3332 20.7827 26.1332 19.8243 25.7248C18.866 25.3165 17.9077 24.7665 16.9577 24.0748C15.9993 23.3748 15.091 22.5998 14.2243 21.7415C13.366 20.8748 12.591 19.9665 11.8993 19.0165C11.216 18.0665 10.666 17.1165 10.266 16.1748C9.86602 15.2248 9.66602 14.3165 9.66602 13.4498C9.66602 12.8832 9.76602 12.3415 9.96602 11.8415C10.166 11.3332 10.4827 10.8665 10.9243 10.4498C11.4577 9.92484 12.041 9.6665 12.6577 9.6665C12.891 9.6665 13.1243 9.7165 13.3327 9.8165C13.5493 9.9165 13.741 10.0665 13.891 10.2832L15.8243 13.0082C15.9743 13.2165 16.0827 13.4082 16.1577 13.5915C16.2327 13.7665 16.2743 13.9415 16.2743 14.0998C16.2743 14.2998 16.216 14.4998 16.0993 14.6915C15.991 14.8832 15.8327 15.0832 15.6327 15.2832L14.9993 15.9415C14.9077 16.0332 14.866 16.1415 14.866 16.2748C14.866 16.3415 14.8743 16.3998 14.891 16.4665C14.916 16.5332 14.941 16.5832 14.9577 16.6332C15.1077 16.9082 15.366 17.2665 15.7327 17.6998C16.1077 18.1332 16.5077 18.5748 16.941 19.0165C17.391 19.4582 17.8243 19.8665 18.266 20.2415C18.6993 20.6082 19.0577 20.8582 19.341 21.0082C19.3827 21.0248 19.4327 21.0498 19.491 21.0748C19.5577 21.0998 19.6243 21.1082 19.6993 21.1082C19.841 21.1082 19.9493 21.0582 20.041 20.9665L20.6743 20.3415C20.8827 20.1332 21.0827 19.9748 21.2743 19.8748C21.466 19.7582 21.6577 19.6998 21.866 19.6998C22.0243 19.6998 22.191 19.7332 22.3743 19.8082C22.5577 19.8832 22.7494 19.9915 22.9577 20.1332L25.716 22.0915C25.9327 22.2415 26.0827 22.4165 26.1743 22.6248C26.2577 22.8332 26.3077 23.0415 26.3077 23.2748Z" strokeWidth="1.5" strokeMiterlimit="10" style={{ stroke: 'var(--accent)' }}/>
<path d="M23.4167 15.5002C23.4167 15.0002 23.025 14.2335 22.4417 13.6085C21.9083 13.0335 21.2 12.5835 20.5 12.5835" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M26.3333 15.4998C26.3333 12.2748 23.725 9.6665 20.5 9.6665" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
</svg>
);
}
@@ -44,16 +44,16 @@ export function FilesServicePhone() {
export function FilesServiceCalendar() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none">
<rect width="36" height="36" rx="18" fill="#FFD8C5" fillOpacity="0.6" />
<path d="M14.666 9.6665V12.1665" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M21.334 9.6665V12.1665" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M23.1667 25.8333C24.6394 25.8333 25.8333 24.6394 25.8333 23.1667C25.8333 21.6939 24.6394 20.5 23.1667 20.5C21.6939 20.5 20.5 21.6939 20.5 23.1667C20.5 24.6394 21.6939 25.8333 23.1667 25.8333Z" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M26.3333 26.3333L25.5 25.5" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10.916 15.5752H25.0827" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M19.1417 26.3332H14.6667C11.75 26.3332 10.5 24.6665 10.5 22.1665V15.0832C10.5 12.5832 11.75 10.9165 14.6667 10.9165H21.3333C24.25 10.9165 25.5 12.5832 25.5 15.0832V18.8332" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M17.9961 19.4167H18.0036" stroke="#F17732" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.9121 19.4167H14.9196" stroke="#F17732" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.9121 21.9167H14.9196" stroke="#F17732" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<rect width="36" height="36" rx="18" style={{ fill: 'var(--accent-bg)', fillOpacity: 0.6 }}/>
<path d="M14.666 9.6665V12.1665" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M21.334 9.6665V12.1665" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M23.1667 25.8333C24.6394 25.8333 25.8333 24.6394 25.8333 23.1667C25.8333 21.6939 24.6394 20.5 23.1667 20.5C21.6939 20.5 20.5 21.6939 20.5 23.1667C20.5 24.6394 21.6939 25.8333 23.1667 25.8333Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M26.3333 26.3333L25.5 25.5" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M10.916 15.5752H25.0827" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M19.1417 26.3332H14.6667C11.75 26.3332 10.5 24.6665 10.5 22.1665V15.0832C10.5 12.5832 11.75 10.9165 14.6667 10.9165H21.3333C24.25 10.9165 25.5 12.5832 25.5 15.0832V18.8332" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M17.9961 19.4167H18.0036" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M14.9121 19.4167H14.9196" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M14.9121 21.9167H14.9196" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
</svg>
);
}
@@ -61,9 +61,9 @@ export function FilesServiceCalendar() {
export function FilesServiceNotification() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12.0199 20.5299C9.68987 20.5299 7.35987 20.1599 5.14987 19.4199C4.30987 19.1299 3.66987 18.5399 3.38987 17.7699C3.09987 16.9999 3.19987 16.1499 3.65987 15.3899L4.80987 13.4799C5.04987 13.0799 5.26987 12.2799 5.26987 11.8099V8.91992C5.26987 5.19992 8.29987 2.16992 12.0199 2.16992C15.7399 2.16992 18.7699 5.19992 18.7699 8.91992V11.8099C18.7699 12.2699 18.9899 13.0799 19.2299 13.4899L20.3699 15.3899C20.7999 16.1099 20.8799 16.9799 20.5899 17.7699C20.2999 18.5599 19.6699 19.1599 18.8799 19.4199C16.6799 20.1599 14.3499 20.5299 12.0199 20.5299Z" stroke="#F17732" strokeWidth="1.5" />
<path d="M13.8806 3.94018C13.8106 3.94018 13.7406 3.93018 13.6706 3.91018C13.3806 3.83018 13.1006 3.77018 12.8306 3.73018C11.9806 3.62018 11.1606 3.68018 10.3906 3.91018C10.1106 4.00018 9.8106 3.91018 9.6206 3.70018C9.4306 3.49018 9.3706 3.19018 9.4806 2.92018C9.8906 1.87018 10.8906 1.18018 12.0306 1.18018C13.1706 1.18018 14.1706 1.86018 14.5806 2.92018C14.6806 3.19018 14.6306 3.49018 14.4406 3.70018C14.2906 3.86018 14.0806 3.94018 13.8806 3.94018Z" stroke="#F17732" strokeWidth="1.5" />
<path d="M12.0195 22.8101C11.0295 22.8101 10.0695 22.4101 9.36953 21.7101C8.66953 21.0101 8.26953 20.0501 8.26953 19.0601H9.76953C9.76953 19.6501 10.0095 20.2301 10.4295 20.6501C10.8495 21.0701 11.4295 21.3101 12.0195 21.3101C13.2595 21.3101 14.2695 20.3001 14.2695 19.0601H15.7695C15.7695 21.1301 14.0895 22.8101 12.0195 22.8101Z" stroke="#F17732" strokeWidth="1.5" />
<path d="M12.0199 20.5299C9.68987 20.5299 7.35987 20.1599 5.14987 19.4199C4.30987 19.1299 3.66987 18.5399 3.38987 17.7699C3.09987 16.9999 3.19987 16.1499 3.65987 15.3899L4.80987 13.4799C5.04987 13.0799 5.26987 12.2799 5.26987 11.8099V8.91992C5.26987 5.19992 8.29987 2.16992 12.0199 2.16992C15.7399 2.16992 18.7699 5.19992 18.7699 8.91992V11.8099C18.7699 12.2699 18.9899 13.0799 19.2299 13.4899L20.3699 15.3899C20.7999 16.1099 20.8799 16.9799 20.5899 17.7699C20.2999 18.5599 19.6699 19.1599 18.8799 19.4199C16.6799 20.1599 14.3499 20.5299 12.0199 20.5299Z" strokeWidth="1.5" style={{ stroke: 'var(--accent)' }}/>
<path d="M13.8806 3.94018C13.8106 3.94018 13.7406 3.93018 13.6706 3.91018C13.3806 3.83018 13.1006 3.77018 12.8306 3.73018C11.9806 3.62018 11.1606 3.68018 10.3906 3.91018C10.1106 4.00018 9.8106 3.91018 9.6206 3.70018C9.4306 3.49018 9.3706 3.19018 9.4806 2.92018C9.8906 1.87018 10.8906 1.18018 12.0306 1.18018C13.1706 1.18018 14.1706 1.86018 14.5806 2.92018C14.6806 3.19018 14.6306 3.49018 14.4406 3.70018C14.2906 3.86018 14.0806 3.94018 13.8806 3.94018Z" strokeWidth="1.5" style={{ stroke: 'var(--accent)' }}/>
<path d="M12.0195 22.8101C11.0295 22.8101 10.0695 22.4101 9.36953 21.7101C8.66953 21.0101 8.26953 20.0501 8.26953 19.0601H9.76953C9.76953 19.6501 10.0095 20.2301 10.4295 20.6501C10.8495 21.0701 11.4295 21.3101 12.0195 21.3101C13.2595 21.3101 14.2695 20.3001 14.2695 19.0601H15.7695C15.7695 21.1301 14.0895 22.8101 12.0195 22.8101Z" strokeWidth="1.5" style={{ stroke: 'var(--accent)' }}/>
</svg>
);
}
@@ -71,17 +71,17 @@ export function FilesServiceNotification() {
export function FilesServiceMessage() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M7.08268 15.8332H6.66602C3.33268 15.8332 1.66602 14.9998 1.66602 10.8332V6.6665C1.66602 3.33317 3.33268 1.6665 6.66602 1.6665H13.3327C16.666 1.6665 18.3327 3.33317 18.3327 6.6665V10.8332C18.3327 14.1665 16.666 15.8332 13.3327 15.8332H12.916C12.6577 15.8332 12.4077 15.9582 12.2493 16.1665L10.9993 17.8332C10.4493 18.5665 9.54935 18.5665 8.99935 17.8332L7.74935 16.1665C7.61602 15.9832 7.30768 15.8332 7.08268 15.8332Z" stroke="#FFFFFF" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5.83398 6.6665H14.1673" stroke="#FFFFFF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5.83398 10.8335H10.834" stroke="#FFFFFF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M7.08268 15.8332H6.66602C3.33268 15.8332 1.66602 14.9998 1.66602 10.8332V6.6665C1.66602 3.33317 3.33268 1.6665 6.66602 1.6665H13.3327C16.666 1.6665 18.3327 3.33317 18.3327 6.6665V10.8332C18.3327 14.1665 16.666 15.8332 13.3327 15.8332H12.916C12.6577 15.8332 12.4077 15.9582 12.2493 16.1665L10.9993 17.8332C10.4493 18.5665 9.54935 18.5665 8.99935 17.8332L7.74935 16.1665C7.61602 15.9832 7.30768 15.8332 7.08268 15.8332Z" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--on-primary)' }}/>
<path d="M5.83398 6.6665H14.1673" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--on-primary)' }}/>
<path d="M5.83398 10.8335H10.834" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--on-primary)' }}/>
</svg>
);
}
export function ArrowLeftPH({ style }: { style?: CSSProperties }) {
export function ArrowLeftPH({ style }: { style?: CSSProperties }){
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" style={{ width: 18, height: 18, transform: 'rotate(180deg)', ...style }}>
<path d="M14.9973 19.9201L8.47728 13.4001C7.70728 12.6301 7.70728 11.3701 8.47728 10.6001L14.9973 4.08008" stroke="#616161" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.9973 19.9201L8.47728 13.4001C7.70728 12.6301 7.70728 11.3701 8.47728 10.6001L14.9973 4.08008" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--text-2)' }}/>
</svg>
);
}
@@ -89,163 +89,163 @@ export function ArrowLeftPH({ style }: { style?: CSSProperties }) {
export function ArrowLeftD() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M9.57 5.93018L3.5 12.0002L9.57 18.0702" stroke="#616161" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M20.4999 12H3.66992" stroke="#616161" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9.57 5.93018L3.5 12.0002L9.57 18.0702" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--text-2)' }}/>
<path d="M20.4999 12H3.66992" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--text-2)' }}/>
</svg>
);
}
/* ── Tab icons (color = active #5559ce / inactive #616161) ─────────────────── */
/* ── Tab icons (رنگ از توکن والد؛ فعال var(--primary) / غیرفعال var(--text-2)) ── */
export function TabServices({ color = '#616161', style }: IconProps) {
export function TabServices({ color = 'currentColor', style }: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={style}>
<path d="M7.9668 15.0002V10.8335" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10.0007 12.9165H5.83398" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M6.66602 1.6665V4.1665" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.334 1.6665V4.1665" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.1758 2.8501C15.9592 2.9501 17.3675 3.9751 17.4508 7.89176L17.5592 13.0334C17.6258 16.4668 16.8342 18.1918 12.6675 18.2834L7.66751 18.3834C3.50084 18.4668 2.63417 16.7668 2.56751 13.3418L2.45084 8.19176C2.36751 4.2751 3.74251 3.19176 6.50917 2.98343L13.1758 2.8501Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M7.9668 15.0002V10.8335" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10.0007 12.9165H5.83398" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M6.66602 1.6665V4.1665" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.334 1.6665V4.1665" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.1758 2.8501C15.9592 2.9501 17.3675 3.9751 17.4508 7.89176L17.5592 13.0334C17.6258 16.4668 16.8342 18.1918 12.6675 18.2834L7.66751 18.3834C3.50084 18.4668 2.63417 16.7668 2.56751 13.3418L2.45084 8.19176C2.36751 4.2751 3.74251 3.19176 6.50917 2.98343L13.1758 2.8501Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function TabInfo({ color = '#616161', style }: IconProps) {
export function TabInfo({ color = 'currentColor', style }: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={style}>
<path d="M6.66602 1.6665V4.1665" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.334 1.6665V4.1665" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5.83398 10.8335H12.5007" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5.83398 14.1665H10.0007" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.3333 2.9165C16.1083 3.0665 17.5 4.12484 17.5 8.0415V13.1915C17.5 16.6248 16.6667 18.3415 12.5 18.3415H7.5C3.33333 18.3415 2.5 16.6248 2.5 13.1915V8.0415C2.5 4.12484 3.89167 3.07484 6.66667 2.9165H13.3333Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M6.66602 1.6665V4.1665" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.334 1.6665V4.1665" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5.83398 10.8335H12.5007" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5.83398 14.1665H10.0007" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.3333 2.9165C16.1083 3.0665 17.5 4.12484 17.5 8.0415V13.1915C17.5 16.6248 16.6667 18.3415 12.5 18.3415H7.5C3.33333 18.3415 2.5 16.6248 2.5 13.1915V8.0415C2.5 4.12484 3.89167 3.07484 6.66667 2.9165H13.3333Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function TabCalendar({ color = '#616161', style }: IconProps) {
export function TabCalendar({ color = 'currentColor', style }: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" style={style}>
<path d="M8 2V5" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M16 2V5" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.5 9.08984H20.5" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M21 8.5V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V8.5C3 5.5 4.5 3.5 8 3.5H16C19.5 3.5 21 5.5 21 8.5Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.6947 13.7002H15.7037" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.9955 13.7002H12.0045" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.29431 13.7002H8.30329" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" style={{ color, ...style }}>
<path d="M8 2V5" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M16 2V5" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.5 9.08984H20.5" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M21 8.5V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V8.5C3 5.5 4.5 3.5 8 3.5H16C19.5 3.5 21 5.5 21 8.5Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.6947 13.7002H15.7037" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.9955 13.7002H12.0045" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.29431 13.7002H8.30329" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function TabCard({ color = '#616161', style }: IconProps) {
export function TabCard({ color = 'currentColor', style }: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={style}>
<path d="M3.27539 13.2327L13.2337 3.27441" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9.25195 15.2324L10.252 14.2324" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.4941 12.9907L13.4858 10.999" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.00041 8.53258L8.53374 2.99925C10.3004 1.23258 11.1837 1.22425 12.9337 2.97425L17.0254 7.06591C18.7754 8.81591 18.7671 9.69925 17.0004 11.4659L11.4671 16.9992C9.70041 18.7659 8.81707 18.7742 7.06707 17.0242L2.97541 12.9326C1.22541 11.1826 1.22541 10.3076 3.00041 8.53258Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M1.66602 18.332H18.3327" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M3.27539 13.2327L13.2337 3.27441" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9.25195 15.2324L10.252 14.2324" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.4941 12.9907L13.4858 10.999" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.00041 8.53258L8.53374 2.99925C10.3004 1.23258 11.1837 1.22425 12.9337 2.97425L17.0254 7.06591C18.7754 8.81591 18.7671 9.69925 17.0004 11.4659L11.4671 16.9992C9.70041 18.7659 8.81707 18.7742 7.06707 17.0242L2.97541 12.9326C1.22541 11.1826 1.22541 10.3076 3.00041 8.53258Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M1.66602 18.332H18.3327" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function TabWallet({ color = '#616161', style }: IconProps) {
export function TabWallet({ color = 'currentColor', style }: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={style}>
<path d="M10.834 9.2915H5.83398" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M1.66602 9.2917V5.4417C1.66602 3.7417 3.04102 2.3667 4.74102 2.3667H9.42435C11.1243 2.3667 12.4993 3.42503 12.4993 5.12503" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.566 10.1667C14.1493 10.5667 13.9494 11.1833 14.116 11.8166C14.3244 12.5916 15.091 13.0833 15.891 13.0833H16.666V14.2917C16.666 16.1333 15.1743 17.625 13.3327 17.625H4.99935C3.15768 17.625 1.66602 16.1333 1.66602 14.2917V8.45833C1.66602 6.61667 3.15768 5.125 4.99935 5.125H13.3327C15.166 5.125 16.666 6.625 16.666 8.45833V9.66663H15.766C15.2993 9.66663 14.8743 9.84999 14.566 10.1665Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M18.3329 10.5165V12.2332C18.3329 12.6999 17.9495 13.0832 17.4745 13.0832H15.8662C14.9662 13.0832 14.1412 12.4249 14.0662 11.5249C14.0162 10.9999 14.2162 10.5082 14.5662 10.1665C14.8745 9.84987 15.2995 9.6665 15.7662 9.6665H17.4745C17.9495 9.6665 18.3329 10.0499 18.3329 10.5165Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M10.834 9.2915H5.83398" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M1.66602 9.2917V5.4417C1.66602 3.7417 3.04102 2.3667 4.74102 2.3667H9.42435C11.1243 2.3667 12.4993 3.42503 12.4993 5.12503" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.566 10.1667C14.1493 10.5667 13.9494 11.1833 14.116 11.8166C14.3244 12.5916 15.091 13.0833 15.891 13.0833H16.666V14.2917C16.666 16.1333 15.1743 17.625 13.3327 17.625H4.99935C3.15768 17.625 1.66602 16.1333 1.66602 14.2917V8.45833C1.66602 6.61667 3.15768 5.125 4.99935 5.125H13.3327C15.166 5.125 16.666 6.625 16.666 8.45833V9.66663H15.766C15.2993 9.66663 14.8743 9.84999 14.566 10.1665Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M18.3329 10.5165V12.2332C18.3329 12.6999 17.9495 13.0832 17.4745 13.0832H15.8662C14.9662 13.0832 14.1412 12.4249 14.0662 11.5249C14.0162 10.9999 14.2162 10.5082 14.5662 10.1665C14.8745 9.84987 15.2995 9.6665 15.7662 9.6665H17.4745C17.9495 9.6665 18.3329 10.0499 18.3329 10.5165Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function TabSMS({ color = '#616161', style }: IconProps) {
export function TabSMS({ color = 'currentColor', style }: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={style}>
<path d="M14.166 17.0832H5.83268C3.33268 17.0832 1.66602 15.8332 1.66602 12.9165V7.08317C1.66602 4.1665 3.33268 2.9165 5.83268 2.9165H14.166C16.666 2.9165 18.3327 4.1665 18.3327 7.08317V12.9165C18.3327 15.8332 16.666 17.0832 14.166 17.0832Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.1673 7.5L11.559 9.58333C10.7006 10.2667 9.29231 10.2667 8.43398 9.58333L5.83398 7.5" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M14.166 17.0832H5.83268C3.33268 17.0832 1.66602 15.8332 1.66602 12.9165V7.08317C1.66602 4.1665 3.33268 2.9165 5.83268 2.9165H14.166C16.666 2.9165 18.3327 4.1665 18.3327 7.08317V12.9165C18.3327 15.8332 16.666 17.0832 14.166 17.0832Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.1673 7.5L11.559 9.58333C10.7006 10.2667 9.29231 10.2667 8.43398 9.58333L5.83398 7.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function TabCall({ color = '#616161', style }: IconProps) {
export function TabCall({ color = 'currentColor', style }: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={style}>
<path d="M18.3077 15.2748C18.3077 15.5748 18.241 15.8832 18.0993 16.1832C17.9577 16.4832 17.7744 16.7665 17.5327 17.0332C17.1244 17.4832 16.6743 17.8082 16.166 18.0165C15.666 18.2248 15.1244 18.3332 14.541 18.3332C13.691 18.3332 12.7827 18.1332 11.8243 17.7248C10.866 17.3165 9.90768 16.7665 8.95768 16.0748C7.99935 15.3748 7.09102 14.5998 6.22435 13.7415C5.36602 12.8748 4.59102 11.9665 3.89935 11.0165C3.21602 10.0665 2.66602 9.1165 2.26602 8.17484C1.86602 7.22484 1.66602 6.3165 1.66602 5.44984C1.66602 4.88317 1.76602 4.3415 1.96602 3.8415C2.16602 3.33317 2.48268 2.8665 2.92435 2.44984C3.45768 1.92484 4.04102 1.6665 4.65768 1.6665C4.89102 1.6665 5.12435 1.7165 5.33268 1.8165C5.54935 1.9165 5.74102 2.0665 5.89102 2.28317L7.82435 5.00817C7.97435 5.2165 8.08268 5.40817 8.15768 5.5915C8.23268 5.7665 8.27435 5.9415 8.27435 6.09984C8.27435 6.29984 8.21602 6.49984 8.09935 6.6915C7.99102 6.88317 7.83268 7.08317 7.63268 7.28317L6.99935 7.9415C6.90768 8.03317 6.86602 8.1415 6.86602 8.27484C6.86602 8.3415 6.87435 8.39984 6.89102 8.4665C6.91602 8.53317 6.94102 8.58317 6.95768 8.63317C7.10768 8.90817 7.36602 9.2665 7.73268 9.69984C8.10768 10.1332 8.50768 10.5748 8.94102 11.0165C9.39102 11.4582 9.82435 11.8665 10.266 12.2415C10.6993 12.6082 11.0577 12.8582 11.341 13.0082C11.3827 13.0248 11.4327 13.0498 11.491 13.0748C11.5577 13.0998 11.6243 13.1082 11.6993 13.1082C11.841 13.1082 11.9493 13.0582 12.041 12.9665L12.6743 12.3415C12.8827 12.1332 13.0827 11.9748 13.2743 11.8748C13.466 11.7582 13.6577 11.6998 13.866 11.6998C14.0243 11.6998 14.191 11.7332 14.3743 11.8082C14.5577 11.8832 14.7494 11.9915 14.9577 12.1332L17.716 14.0915C17.9327 14.2415 18.0827 14.4165 18.1743 14.6248C18.2577 14.8332 18.3077 15.0415 18.3077 15.2748Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" />
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M18.3077 15.2748C18.3077 15.5748 18.241 15.8832 18.0993 16.1832C17.9577 16.4832 17.7744 16.7665 17.5327 17.0332C17.1244 17.4832 16.6743 17.8082 16.166 18.0165C15.666 18.2248 15.1244 18.3332 14.541 18.3332C13.691 18.3332 12.7827 18.1332 11.8243 17.7248C10.866 17.3165 9.90768 16.7665 8.95768 16.0748C7.99935 15.3748 7.09102 14.5998 6.22435 13.7415C5.36602 12.8748 4.59102 11.9665 3.89935 11.0165C3.21602 10.0665 2.66602 9.1165 2.26602 8.17484C1.86602 7.22484 1.66602 6.3165 1.66602 5.44984C1.66602 4.88317 1.76602 4.3415 1.96602 3.8415C2.16602 3.33317 2.48268 2.8665 2.92435 2.44984C3.45768 1.92484 4.04102 1.6665 4.65768 1.6665C4.89102 1.6665 5.12435 1.7165 5.33268 1.8165C5.54935 1.9165 5.74102 2.0665 5.89102 2.28317L7.82435 5.00817C7.97435 5.2165 8.08268 5.40817 8.15768 5.5915C8.23268 5.7665 8.27435 5.9415 8.27435 6.09984C8.27435 6.29984 8.21602 6.49984 8.09935 6.6915C7.99102 6.88317 7.83268 7.08317 7.63268 7.28317L6.99935 7.9415C6.90768 8.03317 6.86602 8.1415 6.86602 8.27484C6.86602 8.3415 6.87435 8.39984 6.89102 8.4665C6.91602 8.53317 6.94102 8.58317 6.95768 8.63317C7.10768 8.90817 7.36602 9.2665 7.73268 9.69984C8.10768 10.1332 8.50768 10.5748 8.94102 11.0165C9.39102 11.4582 9.82435 11.8665 10.266 12.2415C10.6993 12.6082 11.0577 12.8582 11.341 13.0082C11.3827 13.0248 11.4327 13.0498 11.491 13.0748C11.5577 13.0998 11.6243 13.1082 11.6993 13.1082C11.841 13.1082 11.9493 13.0582 12.041 12.9665L12.6743 12.3415C12.8827 12.1332 13.0827 11.9748 13.2743 11.8748C13.466 11.7582 13.6577 11.6998 13.866 11.6998C14.0243 11.6998 14.191 11.7332 14.3743 11.8082C14.5577 11.8832 14.7494 11.9915 14.9577 12.1332L17.716 14.0915C17.9327 14.2415 18.0827 14.4165 18.1743 14.6248C18.2577 14.8332 18.3077 15.0415 18.3077 15.2748Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" />
</svg>
);
}
export function TabAttach({ color = '#616161', style }: IconProps) {
export function TabAttach({ color = 'currentColor', style }: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={style}>
<path d="M10.1664 9.83352L8.99138 11.0085C8.34138 11.6585 8.34138 12.7169 8.99138 13.3669C9.64138 14.0169 10.6997 14.0169 11.3497 13.3669L13.1998 11.5169C14.4998 10.2169 14.4998 8.10853 13.1998 6.8002C11.8998 5.5002 9.79139 5.5002 8.48306 6.8002L6.46641 8.81685C5.34974 9.93351 5.34974 11.7419 6.46641 12.8585" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M7.49935 18.3332H12.4993C16.666 18.3332 18.3327 16.6665 18.3327 12.4998V7.49984C18.3327 3.33317 16.666 1.6665 12.4993 1.6665H7.49935C3.33268 1.6665 1.66602 3.33317 1.66602 7.49984V12.4998C1.66602 16.6665 3.33268 18.3332 7.49935 18.3332Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M10.1664 9.83352L8.99138 11.0085C8.34138 11.6585 8.34138 12.7169 8.99138 13.3669C9.64138 14.0169 10.6997 14.0169 11.3497 13.3669L13.1998 11.5169C14.4998 10.2169 14.4998 8.10853 13.1998 6.8002C11.8998 5.5002 9.79139 5.5002 8.48306 6.8002L6.46641 8.81685C5.34974 9.93351 5.34974 11.7419 6.46641 12.8585" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M7.49935 18.3332H12.4993C16.666 18.3332 18.3327 16.6665 18.3327 12.4998V7.49984C18.3327 3.33317 16.666 1.6665 12.4993 1.6665H7.49935C3.33268 1.6665 1.66602 3.33317 1.66602 7.49984V12.4998C1.66602 16.6665 3.33268 18.3332 7.49935 18.3332Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function TabBody({ color = '#616161', style }: IconProps) {
export function TabBody({ color = 'currentColor', style }: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={style}>
<path d="M5 6.6665C3.75 6.66651 3.75 8.33317 5 8.33317L6.6671 8.33317C7.12734 8.33317 7.5 8.70357 7.5 9.16381V17.0832C7.5 18.3332 9.16667 18.3332 9.16667 17.0832C9.16667 15.8332 9.16667 13.3332 10 13.3332C10.8333 13.3332 10.8333 15.8332 10.8333 17.0832C10.8333 18.3332 12.5 18.3332 12.5 17.0832V9.1665C12.5 8.70627 12.8731 8.33317 13.3333 8.33317H15C16.25 8.33317 16.25 6.6665 15 6.6665H5Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.6673 3.75016C11.6673 4.67064 10.9211 5.41683 10.0007 5.41683C9.08018 5.41683 8.33398 4.67064 8.33398 3.75016C8.33398 2.82969 9.08018 2.0835 10.0007 2.0835C10.9211 2.0835 11.6673 2.82969 11.6673 3.75016Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M5 6.6665C3.75 6.66651 3.75 8.33317 5 8.33317L6.6671 8.33317C7.12734 8.33317 7.5 8.70357 7.5 9.16381V17.0832C7.5 18.3332 9.16667 18.3332 9.16667 17.0832C9.16667 15.8332 9.16667 13.3332 10 13.3332C10.8333 13.3332 10.8333 15.8332 10.8333 17.0832C10.8333 18.3332 12.5 18.3332 12.5 17.0832V9.1665C12.5 8.70627 12.8731 8.33317 13.3333 8.33317H15C16.25 8.33317 16.25 6.6665 15 6.6665H5Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.6673 3.75016C11.6673 4.67064 10.9211 5.41683 10.0007 5.41683C9.08018 5.41683 8.33398 4.67064 8.33398 3.75016C8.33398 2.82969 9.08018 2.0835 10.0007 2.0835C10.9211 2.0835 11.6673 2.82969 11.6673 3.75016Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
/** Payment check icon (tauri FilesServicePaymentsCheckIcon) — rounded-square tick. */
export function FilesServicePaymentsCheck({ color = '#2F2F2F', size = 20, style }: IconProps & { size?: number }) {
export function FilesServicePaymentsCheck({ color = 'currentColor', size = 20, style }: IconProps & { size?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none" style={style}>
<path d="M7.49984 18.3332H12.4998C16.6665 18.3332 18.3332 16.6665 18.3332 12.4998V7.49984C18.3332 3.33317 16.6665 1.6665 12.4998 1.6665H7.49984C3.33317 1.6665 1.6665 3.33317 1.6665 7.49984V12.4998C1.6665 16.6665 3.33317 18.3332 7.49984 18.3332Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M6.4585 9.99993L8.81683 12.3583L13.5418 7.6416" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M7.49984 18.3332H12.4998C16.6665 18.3332 18.3332 16.6665 18.3332 12.4998V7.49984C18.3332 3.33317 16.6665 1.6665 12.4998 1.6665H7.49984C3.33317 1.6665 1.6665 3.33317 1.6665 7.49984V12.4998C1.6665 16.6665 3.33317 18.3332 7.49984 18.3332Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M6.4585 9.99993L8.81683 12.3583L13.5418 7.6416" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
/* ── Turn card info-row icons (tauri CalendarD / ClockP / UserD / status) ───── */
export function CalendarD({ color = '#616161', size = 20, style }: IconProps & { size?: number }) {
export function CalendarD({ color = 'currentColor', size = 20, style }: IconProps & { size?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<path d="M8 2V5" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M16 2V5" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.5 9.08984H20.5" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M21 8.5V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V8.5C3 5.5 4.5 3.5 8 3.5H16C19.5 3.5 21 5.5 21 8.5Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.6947 13.7002H15.7037" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.6947 16.7002H15.7037" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.9955 13.7002H12.0045" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.9955 16.7002H12.0045" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.29431 13.7002H8.30329" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.29431 16.7002H8.30329" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" style={{ color, ...style }}>
<path d="M8 2V5" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M16 2V5" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.5 9.08984H20.5" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M21 8.5V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V8.5C3 5.5 4.5 3.5 8 3.5H16C19.5 3.5 21 5.5 21 8.5Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.6947 13.7002H15.7037" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.6947 16.7002H15.7037" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.9955 13.7002H12.0045" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.9955 16.7002H12.0045" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.29431 13.7002H8.30329" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.29431 16.7002H8.30329" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function ClockP({ color = '#525252', size = 20, style }: IconProps & { size?: number }) {
export function ClockP({ color = 'currentColor', size = 20, style }: IconProps & { size?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none" style={style}>
<path d="M18.3337 10.0003C18.3337 14.6003 14.6003 18.3337 10.0003 18.3337C5.40033 18.3337 1.66699 14.6003 1.66699 10.0003C1.66699 5.40033 5.40033 1.66699 10.0003 1.66699C14.6003 1.66699 18.3337 5.40033 18.3337 10.0003Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.0914 12.6505L10.5081 11.1088C10.0581 10.8421 9.69141 10.2005 9.69141 9.67546V6.25879" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M18.3337 10.0003C18.3337 14.6003 14.6003 18.3337 10.0003 18.3337C5.40033 18.3337 1.66699 14.6003 1.66699 10.0003C1.66699 5.40033 5.40033 1.66699 10.0003 1.66699C14.6003 1.66699 18.3337 5.40033 18.3337 10.0003Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.0914 12.6505L10.5081 11.1088C10.0581 10.8421 9.69141 10.2005 9.69141 9.67546V6.25879" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function UserD({ color = '#616161', size = 20, style }: IconProps & { size?: number }) {
export function UserD({ color = 'currentColor', size = 20, style }: IconProps & { size?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<path d="M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M20.5899 22C20.5899 18.13 16.7399 15 11.9999 15C7.25991 15 3.40991 18.13 3.40991 22" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" style={{ color, ...style }}>
<path d="M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M20.5899 22C20.5899 18.13 16.7399 15 11.9999 15C7.25991 15 3.40991 18.13 3.40991 22" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function StatusGlobe({ color = '#616161', size = 20, style }: IconProps & { size?: number }) {
export function StatusGlobe({ color = 'currentColor', size = 20, style }: IconProps & { size?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none" style={style}>
<path d="M2.04166 12.4751C2.93332 15.3418 5.33333 17.5501 8.31666 18.1584" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M1.70834 9.14984C2.13334 4.9415 5.68334 1.6665 10 1.6665C14.3167 1.6665 17.8667 4.94984 18.2917 9.14984" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.675 18.1666C14.65 17.5583 17.0417 15.3749 17.95 12.5166" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none" style={{ color, ...style }}>
<path d="M2.04166 12.4751C2.93332 15.3418 5.33333 17.5501 8.31666 18.1584" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M1.70834 9.14984C2.13334 4.9415 5.68334 1.6665 10 1.6665C14.3167 1.6665 17.8667 4.94984 18.2917 9.14984" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.675 18.1666C14.65 17.5583 17.0417 15.3749 17.95 12.5166" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
@@ -255,58 +255,58 @@ export function StatusGlobe({ color = '#616161', size = 20, style }: IconProps &
type StepIconProps = { size?: number; active?: boolean };
/** Step «ایجاد سرویس/ویرایش» icon — tauri FilesServiceAddServiceStep, verbatim. */
export function FilesServiceAddServiceStep({ size = 52, active = false }: StepIconProps) {
const circleStrokeColor = active ? '#5559CE' : '#E1E1E1';
const bgColor = active ? '#5559CE' : '#E1E1E1';
const iconColor = active ? '#FAFAFA' : '#9B9B9B';
export function FilesServiceAddServiceStep({ size = 52, active = false }: StepIconProps){
const circleStrokeColor = active ? 'var(--primary)' : 'var(--border-2)';
const bgColor = active ? 'var(--primary)' : 'var(--border-2)';
const iconColor = active ? 'var(--on-primary)' : 'var(--text-3)';
return (
<svg width={size} height={size} viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ display: 'block' }}>
<circle cx="26" cy="26" r="25" stroke={circleStrokeColor} strokeWidth="2" strokeDasharray="16 2" />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" fill={bgColor} />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" stroke={bgColor} />
<path d="M23.5605 32V27" stroke={iconColor} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M26 29.5H21" stroke={iconColor} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M22 16V19" stroke={iconColor} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M30 16V19" stroke={iconColor} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M29.8098 17.4199C33.1498 17.5399 34.8398 18.7699 34.9398 23.4699L35.0698 29.6399C35.1498 33.7599 34.1998 35.8299 29.1998 35.9399L23.1998 36.0599C18.1998 36.1599 17.1598 34.1199 17.0798 30.0099L16.9398 23.8299C16.8398 19.1299 18.4898 17.8299 21.8098 17.5799L29.8098 17.4199Z" stroke={iconColor} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="26" cy="26" r="25" strokeWidth="2" strokeDasharray="16 2" style={{ stroke: circleStrokeColor }} />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" style={{ fill: bgColor }} />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" style={{ stroke: bgColor }} />
<path d="M23.5605 32V27" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M26 29.5H21" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M22 16V19" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M30 16V19" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M29.8098 17.4199C33.1498 17.5399 34.8398 18.7699 34.9398 23.4699L35.0698 29.6399C35.1498 33.7599 34.1998 35.8299 29.1998 35.9399L23.1998 36.0599C18.1998 36.1599 17.1598 34.1199 17.0798 30.0099L16.9398 23.8299C16.8398 19.1299 18.4898 17.8299 21.8098 17.5799L29.8098 17.4199Z" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
</svg>
);
}
/** Step «پرداخت» icon — tauri FilesAddServicePaymentStep, verbatim. */
export function FilesAddServicePaymentStep({ size = 52, active = false }: StepIconProps) {
const circleStrokeColor = active ? '#5559CE' : '#E1E1E1';
const bgColor = active ? '#5559CE' : '#E1E1E1';
const iconColor = active ? '#FAFAFA' : '#9B9B9B';
export function FilesAddServicePaymentStep({ size = 52, active = false }: StepIconProps){
const circleStrokeColor = active ? 'var(--primary)' : 'var(--border-2)';
const bgColor = active ? 'var(--primary)' : 'var(--border-2)';
const iconColor = active ? 'var(--on-primary)' : 'var(--text-3)';
return (
<svg width={size} height={size} viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ display: 'block' }}>
<circle cx="26" cy="26" r="25" stroke={circleStrokeColor} strokeWidth="2" strokeDasharray="16 2" />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" fill={bgColor} />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" stroke={bgColor} />
<path d="M17.4297 29.8792L29.3797 17.9292" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M24.6016 32.2791L25.8016 31.0791" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M27.293 29.5887L29.683 27.1987" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M17.1013 24.239L23.7413 17.599C25.8613 15.479 26.9213 15.469 29.0213 17.569L33.9313 22.479C36.0313 24.579 36.0213 25.639 33.9013 27.759L27.2613 34.399C25.1413 36.519 24.0813 36.529 21.9813 34.429L17.0713 29.519C14.9713 27.419 14.9713 26.369 17.1013 24.239Z" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.5 35.9985H35.5" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="26" cy="26" r="25" strokeWidth="2" strokeDasharray="16 2" style={{ stroke: circleStrokeColor }} />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" style={{ fill: bgColor }} />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" style={{ stroke: bgColor }} />
<path d="M17.4297 29.8792L29.3797 17.9292" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M24.6016 32.2791L25.8016 31.0791" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M27.293 29.5887L29.683 27.1987" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M17.1013 24.239L23.7413 17.599C25.8613 15.479 26.9213 15.469 29.0213 17.569L33.9313 22.479C36.0313 24.579 36.0213 25.639 33.9013 27.759L27.2613 34.399C25.1413 36.519 24.0813 36.529 21.9813 34.429L17.0713 29.519C14.9713 27.419 14.9713 26.369 17.1013 24.239Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M15.5 35.9985H35.5" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
</svg>
);
}
/** Step «جزییات» icon — tauri FilesServiceDetailsStep, verbatim. */
export function FilesServiceDetailsStep({ size = 52, active = false }: StepIconProps) {
const circleStrokeColor = active ? '#5559CE' : '#E1E1E1';
const bgColor = active ? '#5559CE' : '#E1E1E1';
const iconColor = active ? '#FAFAFA' : '#9B9B9B';
export function FilesServiceDetailsStep({ size = 52, active = false }: StepIconProps){
const circleStrokeColor = active ? 'var(--primary)' : 'var(--border-2)';
const bgColor = active ? 'var(--primary)' : 'var(--border-2)';
const iconColor = active ? 'var(--on-primary)' : 'var(--text-3)';
return (
<svg width={size} height={size} viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ display: 'block' }}>
<circle cx="26" cy="26" r="25" stroke={circleStrokeColor} strokeWidth="2" strokeDasharray="16 2" />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" fill={bgColor} />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" stroke={bgColor} />
<path d="M22 16V19" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M30 16V19" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M21 27H29" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M21 31H26" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M30 17.5C33.33 17.68 35 18.95 35 23.65V29.83C35 33.95 34 36.01 29 36.01H23C18 36.01 17 33.95 17 29.83V23.65C17 18.95 18.67 17.69 22 17.5H30Z" stroke={iconColor} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="26" cy="26" r="25" strokeWidth="2" strokeDasharray="16 2" style={{ stroke: circleStrokeColor }} />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" style={{ fill: bgColor }} />
<rect x="6.5" y="6.5" width="39" height="39" rx="19.5" style={{ stroke: bgColor }} />
<path d="M22 16V19" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M30 16V19" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M21 27H29" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M21 31H26" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
<path d="M30 17.5C33.33 17.68 35 18.95 35 23.65V29.83C35 33.95 34 36.01 29 36.01H23C18 36.01 17 33.95 17 29.83V23.65C17 18.95 18.67 17.69 22 17.5H30Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: iconColor }} />
</svg>
);
}
@@ -314,38 +314,38 @@ export function FilesServiceDetailsStep({ size = 52, active = false }: StepIconP
/* ── Payment step widgets (tauri Step2Payment) ───────────────────────────── */
/** Orange «هزینه سرویس» badge — tauri Step2PaymentCard, verbatim. */
export function Step2PaymentCard({ size = 40 }: { size?: number }) {
export function Step2PaymentCard({ size = 40 }: { size?: number }){
return (
<svg width={size} height={size} viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="20" fill="#FFF4E5" />
<path d="M27 23.5C28.9339 23.5 30.5 25.0661 30.5 27C30.5 27.6563 30.316 28.2758 29.9941 28.7979L29.9902 28.8047C29.387 29.8186 28.2772 30.5 27 30.5C25.8025 30.5 24.7525 29.9008 24.1289 28.9902L24.0098 28.8047L24.0059 28.7979L23.8916 28.5977C23.6407 28.1204 23.5 27.5743 23.5 27C23.5 25.0661 25.0661 23.5 27 23.5ZM29.4756 25.1689C29.0107 24.6708 28.216 24.6333 27.7109 25.1025L26.4629 26.2559L26.3232 26.1162C25.838 25.6313 25.0418 25.6313 24.5566 26.1162C24.0714 26.6015 24.0714 27.3985 24.5566 27.8838L25.5469 28.874C25.7954 29.1223 26.1145 29.2402 26.4297 29.2402C26.7311 29.2402 27.0438 29.1255 27.2793 28.9072L29.4092 26.9375L29.418 26.9297C29.9048 26.459 29.9501 25.6774 29.4756 25.1689Z" fill="#F17732" stroke="#F17732" />
<path d="M14.1396 11.8999H25.8496C27.8635 11.8999 29.5 13.5364 29.5 15.5503V15.9995C29.5 16.2734 29.2739 16.4995 29 16.4995H11C10.7261 16.4995 10.5 16.2734 10.5 15.9995V15.5396C10.5002 13.526 12.1261 11.9001 14.1396 11.8999Z" fill="#F17732" stroke="#F17732" />
<path d="M29 18.9902C29.2739 18.9902 29.5 19.2164 29.5 19.4902V20.8799C29.5 21.2157 29.1673 21.4608 28.8301 21.3467H28.8291C27.6616 20.9539 26.3422 20.9004 24.9873 21.334C24.1629 21.5978 23.4064 22.0584 22.792 22.6611C21.4095 24.003 20.8496 25.6792 20.9111 27.2607L20.9316 27.5762C20.9516 27.831 20.7192 28.1004 20.4004 28.1006H14.1396C12.126 28.1004 10.5 26.4737 10.5 24.46V19.5C10.5 19.2239 10.7239 19 11 18.9902H29ZM14 23.25C13.3139 23.25 12.75 23.814 12.75 24.5C12.75 25.1861 13.3139 25.75 14 25.75H16C16.6861 25.75 17.25 25.1861 17.25 24.5C17.25 23.814 16.6861 23.25 16 23.25H14Z" fill="#F17732" stroke="#F17732" />
<rect width="40" height="40" rx="20" style={{ fill: 'var(--warning-bg)' }}/>
<path d="M27 23.5C28.9339 23.5 30.5 25.0661 30.5 27C30.5 27.6563 30.316 28.2758 29.9941 28.7979L29.9902 28.8047C29.387 29.8186 28.2772 30.5 27 30.5C25.8025 30.5 24.7525 29.9008 24.1289 28.9902L24.0098 28.8047L24.0059 28.7979L23.8916 28.5977C23.6407 28.1204 23.5 27.5743 23.5 27C23.5 25.0661 25.0661 23.5 27 23.5ZM29.4756 25.1689C29.0107 24.6708 28.216 24.6333 27.7109 25.1025L26.4629 26.2559L26.3232 26.1162C25.838 25.6313 25.0418 25.6313 24.5566 26.1162C24.0714 26.6015 24.0714 27.3985 24.5566 27.8838L25.5469 28.874C25.7954 29.1223 26.1145 29.2402 26.4297 29.2402C26.7311 29.2402 27.0438 29.1255 27.2793 28.9072L29.4092 26.9375L29.418 26.9297C29.9048 26.459 29.9501 25.6774 29.4756 25.1689Z" style={{ fill: 'var(--accent)', stroke: 'var(--accent)' }}/>
<path d="M14.1396 11.8999H25.8496C27.8635 11.8999 29.5 13.5364 29.5 15.5503V15.9995C29.5 16.2734 29.2739 16.4995 29 16.4995H11C10.7261 16.4995 10.5 16.2734 10.5 15.9995V15.5396C10.5002 13.526 12.1261 11.9001 14.1396 11.8999Z" style={{ fill: 'var(--accent)', stroke: 'var(--accent)' }}/>
<path d="M29 18.9902C29.2739 18.9902 29.5 19.2164 29.5 19.4902V20.8799C29.5 21.2157 29.1673 21.4608 28.8301 21.3467H28.8291C27.6616 20.9539 26.3422 20.9004 24.9873 21.334C24.1629 21.5978 23.4064 22.0584 22.792 22.6611C21.4095 24.003 20.8496 25.6792 20.9111 27.2607L20.9316 27.5762C20.9516 27.831 20.7192 28.1004 20.4004 28.1006H14.1396C12.126 28.1004 10.5 26.4737 10.5 24.46V19.5C10.5 19.2239 10.7239 19 11 18.9902H29ZM14 23.25C13.3139 23.25 12.75 23.814 12.75 24.5C12.75 25.1861 13.3139 25.75 14 25.75H16C16.6861 25.75 17.25 25.1861 17.25 24.5C17.25 23.814 16.6861 23.25 16 23.25H14Z" style={{ fill: 'var(--accent)', stroke: 'var(--accent)' }}/>
</svg>
);
}
/** «موجودی کیف پول» icon — tauri FilesServiceBalanceWallet, verbatim. */
export function FilesServiceBalanceWallet({ size = 20 }: { size?: number }) {
export function FilesServiceBalanceWallet({ size = 20 }: { size?: number }){
return (
<svg width={size} height={size} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.833 9.2915H5.83301" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M1.66699 9.2917V5.4417C1.66699 3.7417 3.04199 2.3667 4.74199 2.3667H9.42532C11.1253 2.3667 12.5003 3.42503 12.5003 5.12503" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.567 10.1667C14.1503 10.5667 13.9503 11.1833 14.117 11.8166C14.3253 12.5916 15.092 13.0833 15.892 13.0833H16.667V14.2917C16.667 16.1333 15.1753 17.625 13.3337 17.625H5.00033C3.15866 17.625 1.66699 16.1333 1.66699 14.2917V8.45833C1.66699 6.61667 3.15866 5.125 5.00033 5.125H13.3337C15.167 5.125 16.667 6.625 16.667 8.45833V9.66663H15.767C15.3003 9.66663 14.8753 9.84999 14.567 10.1667Z" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M18.3338 10.5165V12.2332C18.3338 12.6999 17.9505 13.0832 17.4755 13.0832H15.8672C14.9672 13.0832 14.1422 12.4249 14.0672 11.5249C14.0172 10.9999 14.2172 10.5082 14.5672 10.1665C14.8755 9.84987 15.3005 9.6665 15.7672 9.6665H17.4755C17.9505 9.6665 18.3338 10.0499 18.3338 10.5165Z" stroke="#F17732" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10.833 9.2915H5.83301" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M1.66699 9.2917V5.4417C1.66699 3.7417 3.04199 2.3667 4.74199 2.3667H9.42532C11.1253 2.3667 12.5003 3.42503 12.5003 5.12503" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M14.567 10.1667C14.1503 10.5667 13.9503 11.1833 14.117 11.8166C14.3253 12.5916 15.092 13.0833 15.892 13.0833H16.667V14.2917C16.667 16.1333 15.1753 17.625 13.3337 17.625H5.00033C3.15866 17.625 1.66699 16.1333 1.66699 14.2917V8.45833C1.66699 6.61667 3.15866 5.125 5.00033 5.125H13.3337C15.167 5.125 16.667 6.625 16.667 8.45833V9.66663H15.767C15.3003 9.66663 14.8753 9.84999 14.567 10.1667Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
<path d="M18.3338 10.5165V12.2332C18.3338 12.6999 17.9505 13.0832 17.4755 13.0832H15.8672C14.9672 13.0832 14.1422 12.4249 14.0672 11.5249C14.0172 10.9999 14.2172 10.5082 14.5672 10.1665C14.8755 9.84987 15.3005 9.6665 15.7672 9.6665H17.4755C17.9505 9.6665 18.3338 10.0499 18.3338 10.5165Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ stroke: 'var(--accent)' }}/>
</svg>
);
}
/** Red trash icon (حذف تخفیف) — tauri TrashRed, verbatim. */
export function TrashRed({ size = 20, color = '#FF5450' }: { size?: number; color?: string }) {
export function TrashRed({ size = 20, color = 'currentColor' }: { size?: number; color?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none">
<path d="M17.5 4.98356C14.725 4.70856 11.9333 4.56689 9.15 4.56689C7.5 4.56689 5.85 4.65023 4.2 4.81689L2.5 4.98356" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M7.08301 4.1415L7.26634 3.04984C7.39967 2.25817 7.49967 1.6665 8.90801 1.6665H11.0913C12.4997 1.6665 12.608 2.2915 12.733 3.05817L12.9163 4.1415" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.7087 7.6167L15.167 16.0084C15.0753 17.3167 15.0003 18.3334 12.6753 18.3334H7.32533C5.00033 18.3334 4.92533 17.3167 4.83366 16.0084L4.29199 7.6167" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.6084 13.75H11.3834" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M7.91699 10.4165H12.0837" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none" style={{ color }}>
<path d="M17.5 4.98356C14.725 4.70856 11.9333 4.56689 9.15 4.56689C7.5 4.56689 5.85 4.65023 4.2 4.81689L2.5 4.98356" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M7.08301 4.1415L7.26634 3.04984C7.39967 2.25817 7.49967 1.6665 8.90801 1.6665H11.0913C12.4997 1.6665 12.608 2.2915 12.733 3.05817L12.9163 4.1415" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.7087 7.6167L15.167 16.0084C15.0753 17.3167 15.0003 18.3334 12.6753 18.3334H7.32533C5.00033 18.3334 4.92533 17.3167 4.83366 16.0084L4.29199 7.6167" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.6084 13.75H11.3834" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M7.91699 10.4165H12.0837" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
@@ -354,33 +354,33 @@ export function TrashRed({ size = 20, color = '#FF5450' }: { size?: number; colo
export function CloseModalD() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 17 17" fill="none">
<path className="fill-[#7E7E7E] dark:fill-[#FAFAFA]" d="M12.5947 4.20135C12.533 4.13954 12.4597 4.09051 12.3791 4.05706C12.2984 4.0236 12.212 4.00638 12.1247 4.00638C12.0374 4.00638 11.9509 4.0236 11.8703 4.05706C11.7896 4.09051 11.7164 4.13954 11.6547 4.20135L8.39468 7.45468L5.13468 4.19468C5.07296 4.13296 4.99969 4.084 4.91904 4.0506C4.8384 4.01719 4.75197 4 4.66468 4C4.57739 4 4.49096 4.01719 4.41032 4.0506C4.32968 4.084 4.2564 4.13296 4.19468 4.19468C4.13296 4.2564 4.084 4.32968 4.0506 4.41032C4.01719 4.49096 4 4.57739 4 4.66468C4 4.75197 4.01719 4.8384 4.0506 4.91904C4.084 4.99969 4.13296 5.07296 4.19468 5.13468L7.45468 8.39468L4.19468 11.6547C4.13296 11.7164 4.084 11.7897 4.0506 11.8703C4.01719 11.951 4 12.0374 4 12.1247C4 12.212 4.01719 12.2984 4.0506 12.379C4.084 12.4597 4.13296 12.533 4.19468 12.5947C4.2564 12.6564 4.32968 12.7054 4.41032 12.7388C4.49096 12.7722 4.57739 12.7894 4.66468 12.7894C4.75197 12.7894 4.8384 12.7722 4.91904 12.7388C4.99969 12.7054 5.07296 12.6564 5.13468 12.5947L8.39468 9.33468L11.6547 12.5947C11.7164 12.6564 11.7897 12.7054 11.8703 12.7388C11.951 12.7722 12.0374 12.7894 12.1247 12.7894C12.212 12.7894 12.2984 12.7722 12.379 12.7388C12.4597 12.7054 12.533 12.6564 12.5947 12.5947C12.6564 12.533 12.7054 12.4597 12.7388 12.379C12.7722 12.2984 12.7894 12.212 12.7894 12.1247C12.7894 12.0374 12.7722 11.951 12.7388 11.8703C12.7054 11.7897 12.6564 11.7164 12.5947 11.6547L9.33468 8.39468L12.5947 5.13468C12.848 4.88135 12.848 4.45468 12.5947 4.20135Z" fill="#7E7E7E" />
<path className="fill-[var(--text-2)]" d="M12.5947 4.20135C12.533 4.13954 12.4597 4.09051 12.3791 4.05706C12.2984 4.0236 12.212 4.00638 12.1247 4.00638C12.0374 4.00638 11.9509 4.0236 11.8703 4.05706C11.7896 4.09051 11.7164 4.13954 11.6547 4.20135L8.39468 7.45468L5.13468 4.19468C5.07296 4.13296 4.99969 4.084 4.91904 4.0506C4.8384 4.01719 4.75197 4 4.66468 4C4.57739 4 4.49096 4.01719 4.41032 4.0506C4.32968 4.084 4.2564 4.13296 4.19468 4.19468C4.13296 4.2564 4.084 4.32968 4.0506 4.41032C4.01719 4.49096 4 4.57739 4 4.66468C4 4.75197 4.01719 4.8384 4.0506 4.91904C4.084 4.99969 4.13296 5.07296 4.19468 5.13468L7.45468 8.39468L4.19468 11.6547C4.13296 11.7164 4.084 11.7897 4.0506 11.8703C4.01719 11.951 4 12.0374 4 12.1247C4 12.212 4.01719 12.2984 4.0506 12.379C4.084 12.4597 4.13296 12.533 4.19468 12.5947C4.2564 12.6564 4.32968 12.7054 4.41032 12.7388C4.49096 12.7722 4.57739 12.7894 4.66468 12.7894C4.75197 12.7894 4.8384 12.7722 4.91904 12.7388C4.99969 12.7054 5.07296 12.6564 5.13468 12.5947L8.39468 9.33468L11.6547 12.5947C11.7164 12.6564 11.7897 12.7054 11.8703 12.7388C11.951 12.7722 12.0374 12.7894 12.1247 12.7894C12.212 12.7894 12.2984 12.7722 12.379 12.7388C12.4597 12.7054 12.533 12.6564 12.5947 12.5947C12.6564 12.533 12.7054 12.4597 12.7388 12.379C12.7722 12.2984 12.7894 12.212 12.7894 12.1247C12.7894 12.0374 12.7722 11.951 12.7388 11.8703C12.7054 11.7897 12.6564 11.7164 12.5947 11.6547L9.33468 8.39468L12.5947 5.13468C12.848 4.88135 12.848 4.45468 12.5947 4.20135Z" style={{ fill: 'var(--text-2)' }}/>
</svg>
);
}
/** «پذیرش کننده» — tauri UserTick, verbatim. */
export function UserTick({ color = '#0D9F43', size = 24 }: { color?: string; size?: number }) {
export function UserTick({ color = 'currentColor', size = 24 }: { color?: string; size?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none">
<path d="M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.41016 22C3.41016 18.13 7.26015 15 12.0002 15C12.9602 15 13.8902 15.13 14.7602 15.37" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M22 18C22 18.75 21.79 19.46 21.42 20.06C21.21 20.42 20.94 20.74 20.63 21C19.93 21.63 19.01 22 18 22C16.54 22 15.27 21.22 14.58 20.06C14.21 19.46 14 18.75 14 18C14 16.74 14.58 15.61 15.5 14.88C16.19 14.33 17.06 14 18 14C20.21 14 22 15.79 22 18Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M16.4395 18L17.4294 18.99L19.5594 17.02" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" style={{ color }}>
<path d="M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.41016 22C3.41016 18.13 7.26015 15 12.0002 15C12.9602 15 13.8902 15.13 14.7602 15.37" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M22 18C22 18.75 21.79 19.46 21.42 20.06C21.21 20.42 20.94 20.74 20.63 21C19.93 21.63 19.01 22 18 22C16.54 22 15.27 21.22 14.58 20.06C14.21 19.46 14 18.75 14 18C14 16.74 14.58 15.61 15.5 14.88C16.19 14.33 17.06 14 18 14C20.21 14 22 15.79 22 18Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M16.4395 18L17.4294 18.99L19.5594 17.02" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
/** آیکون «قیمت» کنار سرویس/کالا — tauri FilesServiceAddCard, verbatim. */
export function FilesServiceAddCard({ color = '#616161' }: { color?: string }) {
export function FilesServiceAddCard({ color = 'currentColor' }: { color?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M1.66699 7.0835H11.2503" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5 13.75H6.66667" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.75 13.75H12.0833" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M18.3337 10.0248V13.4248C18.3337 16.3498 17.592 17.0832 14.6337 17.0832H5.36699C2.40866 17.0832 1.66699 16.3498 1.66699 13.4248V6.57484C1.66699 3.64984 2.40866 2.9165 5.36699 2.9165H11.2503" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.75 5.2085H18.3333" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<path d="M16.042 7.49984V2.9165" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" style={{ color }}>
<path d="M1.66699 7.0835H11.2503" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5 13.75H6.66667" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.75 13.75H12.0833" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M18.3337 10.0248V13.4248C18.3337 16.3498 17.592 17.0832 14.6337 17.0832H5.36699C2.40866 17.0832 1.66699 16.3498 1.66699 13.4248V6.57484C1.66699 3.64984 2.40866 2.9165 5.36699 2.9165H11.2503" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M13.75 5.2085H18.3333" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path d="M16.042 7.49984V2.9165" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
}
@@ -3,56 +3,56 @@
* پرونده‌ها header matches the tauri /files page pixel-for-pixel.
*/
export function SearchHeaderP({ color = '#7E7E7E' }: { color?: string }) {
export function SearchHeaderP({ color = 'currentColor' }: { color?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M22 22L20 20" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" style={{ color }}>
<path d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M22 22L20 20" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function TurnsFilter({ color = '#616161' }: { color?: string }) {
export function TurnsFilter({ color = 'currentColor' }: { color?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="none">
<path d="M11.191 17.9756H4.36523" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.2363 7.47539H21.0621" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path fillRule="evenodd" clipRule="evenodd" d="M9.45413 7.41677C9.45413 6.01315 8.30779 4.875 6.89406 4.875C5.48033 4.875 4.33398 6.01315 4.33398 7.41677C4.33398 8.8204 5.48033 9.95855 6.89406 9.95855C8.30779 9.95855 9.45413 8.8204 9.45413 7.41677Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path fillRule="evenodd" clipRule="evenodd" d="M21.667 17.9331C21.667 16.5295 20.5216 15.3914 19.1078 15.3914C17.6932 15.3914 16.5469 16.5295 16.5469 17.9331C16.5469 19.3368 17.6932 20.4749 19.1078 20.4749C20.5216 20.4749 21.667 19.3368 21.667 17.9331Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="none" style={{ color }}>
<path d="M11.191 17.9756H4.36523" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.2363 7.47539H21.0621" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path fillRule="evenodd" clipRule="evenodd" d="M9.45413 7.41677C9.45413 6.01315 8.30779 4.875 6.89406 4.875C5.48033 4.875 4.33398 6.01315 4.33398 7.41677C4.33398 8.8204 5.48033 9.95855 6.89406 9.95855C8.30779 9.95855 9.45413 8.8204 9.45413 7.41677Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path fillRule="evenodd" clipRule="evenodd" d="M21.667 17.9331C21.667 16.5295 20.5216 15.3914 19.1078 15.3914C17.6932 15.3914 16.5469 16.5295 16.5469 17.9331C16.5469 19.3368 17.6932 20.4749 19.1078 20.4749C20.5216 20.4749 21.667 19.3368 21.667 17.9331Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
/** Table-view toggle icon (tauri `PatientsGridView`). */
export function PatientsGridView({ width = 24, height = 24, color = '#5559CE' }: { width?: number; height?: number; color?: string }) {
export function PatientsGridView({ width = 24, height = 24, color = 'currentColor' }: { width?: number; height?: number; color?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" fill="none">
<path d="M9 22H15C20 22 22 20 22 15V9C22 4 20 2 15 2H9C4 2 2 4 2 9V15C2 20 4 22 9 22Z" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M2.0293 8.5H21.9993" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M2.0293 15.5H21.9993" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.50977 21.9898V2.00977" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.5098 21.9898V2.00977" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" fill="none" style={{ color }}>
<path d="M9 22H15C20 22 22 20 22 15V9C22 4 20 2 15 2H9C4 2 2 4 2 9V15C2 20 4 22 9 22Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M2.0293 8.5H21.9993" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M2.0293 15.5H21.9993" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.50977 21.9898V2.00977" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.5098 21.9898V2.00977" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
/** Card-view toggle icon (tauri `PatientsCategoryView`). */
export function PatientsCategoryView({ width = 24, height = 24, color = '#616161' }: { width?: number; height?: number; color?: string }) {
export function PatientsCategoryView({ width = 24, height = 24, color = 'currentColor' }: { width?: number; height?: number; color?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" fill="none">
<path d="M5 10H7C9 10 10 9 10 7V5C10 3 9 2 7 2H5C3 2 2 3 2 5V7C2 9 3 10 5 10Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M17 10H19C21 10 22 9 22 7V5C22 3 21 2 19 2H17C15 2 14 3 14 5V7C14 9 15 10 17 10Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M17 22H19C21 22 22 21 22 19V17C22 15 21 14 19 14H17C15 14 14 15 14 17V19C14 21 15 22 17 22Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5 22H7C9 22 10 21 10 19V17C10 15 9 14 7 14H5C3 14 2 15 2 17V19C2 21 3 22 5 22Z" stroke={color} strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" fill="none" style={{ color }}>
<path d="M5 10H7C9 10 10 9 10 7V5C10 3 9 2 7 2H5C3 2 2 3 2 5V7C2 9 3 10 5 10Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M17 10H19C21 10 22 9 22 7V5C22 3 21 2 19 2H17C15 2 14 3 14 5V7C14 9 15 10 17 10Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M17 22H19C21 22 22 21 22 19V17C22 15 21 14 19 14H17C15 14 14 15 14 17V19C14 21 15 22 17 22Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5 22H7C9 22 10 21 10 19V17C10 15 9 14 7 14H5C3 14 2 15 2 17V19C2 21 3 22 5 22Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function AddTurn({ color = '#E1E1E1' }: { color?: string }) {
export function AddTurn({ color = 'currentColor' }: { color?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M5 10H15" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<path d="M10 15V5" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" style={{ color }}>
<path d="M5 10H15" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path d="M10 15V5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
}
@@ -101,7 +101,7 @@ export default function AddPackageModal({ open, editing, items, saving, onClose,
<div
style={{
flex: '1 1 300px', minHeight: 320, borderRadius: 8, padding: 24,
border: '1px dashed #636ed0', display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
border: '1px dashed var(--primary)', display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
}}
>
<div>
@@ -114,9 +114,9 @@ export default function AddPackageModal({ open, editing, items, saving, onClose,
<div key={idx} style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<span style={{ flex: 1, minWidth: 0, fontSize: 14, fontWeight: 600, color: 'var(--text-2)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{l.name}</span>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: 97, height: 40, borderRadius: 4, border: '1px solid var(--border-2)', padding: '0 8px' }}>
<button type="button" onClick={() => changeAmount(idx, 1)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#f0753b', fontSize: 18, fontWeight: 600 }}>+</button>
<span style={{ color: '#f0753b', fontSize: 14, fontWeight: 500 }}>{l.amount}</span>
<button type="button" onClick={() => changeAmount(idx, -1)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#f0753b', fontSize: 18, fontWeight: 600 }}>-</button>
<button type="button" onClick={() => changeAmount(idx, 1)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--accent)', fontSize: 18, fontWeight: 600 }}>+</button>
<span style={{ color: 'var(--accent)', fontSize: 14, fontWeight: 500 }}>{l.amount}</span>
<button type="button" onClick={() => changeAmount(idx, -1)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--accent)', fontSize: 18, fontWeight: 600 }}>-</button>
</div>
<span style={{ fontSize: 14, color: 'var(--text-2)', whiteSpace: 'nowrap' }}>{formatRial(l.price)}</span>
<button type="button" onClick={() => removeLine(idx)} aria-label="حذف" style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--danger)' }}>
@@ -55,7 +55,7 @@ export default function InventoryActionsMenu({ item, onEdit, onDelete }: Props)
style={{
display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer',
borderRadius: 6, padding: '4px 8px', background: 'none',
color: '#5559ce', fontSize: 12, fontWeight: 500,
color: 'var(--primary)', fontSize: 12, fontWeight: 500,
}}
>
<EllipsisHorizontalCircleIcon style={{ width: 18, height: 18 }} />
@@ -24,7 +24,7 @@ export default function InventoryItemsTable({ items, onEdit, onDelete }: Props)
{/* Desktop table */}
<div
className="inv-desktop"
style={{ border: '1px solid #E7E7E7', borderRadius: 8, overflow: 'hidden' }}
style={{ border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden' }}
>
<table className="inv-table">
<thead>
@@ -26,22 +26,22 @@ function PackageCard({ pkg, onEdit, onDelete }: { pkg: InventoryPackage } & Pick
const canUpdate = can('inventory', 'update');
const canDelete = can('inventory', 'delete');
const [expanded, setExpanded] = useState(false);
const divider = <div style={{ height: 1, background: '#d7d7d7', margin: '8px 0' }} className="inv-divider" />;
const divider = <div style={{ height: 1, background: 'var(--border-2)', margin: '8px 0' }} className="inv-divider" />;
return (
<div style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, padding: 16 }}>
{/* Header */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ width: 20, height: 20, background: '#f0753b', borderRadius: '50%', flexShrink: 0 }} />
<span style={{ width: 20, height: 20, background: 'var(--accent)', borderRadius: '50%', flexShrink: 0 }} />
<span style={{ fontWeight: 500, fontSize: 16, color: 'var(--text)' }}>{pkg.title}</span>
</div>
<span
style={{
width: 122, height: 34, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
borderRadius: 4, fontWeight: 600, fontSize: 16,
color: pkg.available ? '#3f9954' : '#d32f2f',
background: pkg.available ? '#f0faf2' : '#fdebed',
color: pkg.available ? 'var(--success)' : 'var(--danger)',
background: pkg.available ? 'var(--success-bg)' : 'var(--danger-bg)',
}}
>
{pkg.available ? 'موجودی کافی' : 'موجودی ناکافی'}
@@ -115,7 +115,7 @@ export default function PurchaseSubscriptionSidebar({ active }: { active: string
padding: '0 14px', fontSize: 16, fontWeight: isActive ? 700 : 500,
lineHeight: 1, textAlign: 'right',
background: isActive ? 'var(--accent)' : 'transparent',
color: isActive ? '#FFFFFF' : 'var(--text-2)',
color: isActive ? 'var(--on-primary)' : 'var(--text-2)',
cursor: item.to ? 'pointer' : 'not-allowed',
transition: 'background .14s',
};
+1 -1
View File
@@ -122,7 +122,7 @@ export default function Topbar({ onMobileMenuOpen }: { onMobileMenuOpen?: () =>
width: 40, height: 40, borderRadius: 10,
background: hue.fixed?.primary ?? `oklch(0.55 ${hue.c} ${hue.h})`,
border: brandHue.h === hue.h ? '3px solid var(--text)' : '3px solid transparent',
display: 'grid', placeItems: 'center', color: '#fff', cursor: 'pointer',
display: 'grid', placeItems: 'center', color: 'var(--on-primary)', cursor: 'pointer',
}}
>
{brandHue.h === hue.h && <CheckIcon style={{ width: 16, height: 16 }} />}
@@ -169,30 +169,30 @@ function PersianDateInput({ value, onChange, minDate, placeholder }: {
<div>
<button ref={btnRef} type="button" onClick={openPicker}
className="cp-input h-11 flex items-center justify-between w-full cursor-pointer">
<span className={value ? 'text-slate-800 dark:text-slate-200' : 'text-slate-400 dark:text-slate-500'}>
<span className={value ? 'text-[var(--text)] dark:text-[var(--text)]' : 'text-[var(--text-3)] dark:text-[var(--text-2)]'}>
{value ? formatPersianDate(value) : (placeholder ?? 'انتخاب تاریخ')}
</span>
<CalendarIcon className="w-4 h-4 text-slate-400 shrink-0 mr-2" />
<CalendarIcon className="w-4 h-4 text-[var(--text-3)] shrink-0 mr-2" />
</button>
{open && createPortal(
<div ref={dropRef} style={dropStyle}
className="bg-white dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded-2xl shadow-2xl overflow-hidden">
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-100 dark:border-gray-800">
className="bg-[var(--surface)] border border-[var(--border)] rounded-2xl shadow-2xl overflow-hidden">
<div className="flex items-center justify-between px-4 py-3 border-b border-[var(--border)]">
<button type="button" onClick={nextMonth}
className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-slate-100 dark:hover:bg-gray-800 text-slate-500 transition-colors">
className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)] text-[var(--text-2)] transition-colors">
<ChevronRightIcon className="w-4 h-4" />
</button>
<span className="text-sm font-semibold text-slate-800 dark:text-slate-100" dir="rtl">
<span className="text-sm font-semibold text-[var(--text)]" dir="rtl">
{viewJm > 0 ? JALALI_MONTHS[viewJm - 1] : ''} {toPersianNums(viewJy)}
</span>
<button type="button" onClick={prevMonth}
className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-slate-100 dark:hover:bg-gray-800 text-slate-500 transition-colors">
className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)] text-[var(--text-2)] transition-colors">
<ChevronLeftIcon className="w-4 h-4" />
</button>
</div>
<div className="grid grid-cols-7 px-3 pt-2 pb-1">
{JALALI_DAYS_SHORT.map(d => (
<div key={d} className="text-center text-xs font-medium text-slate-400 dark:text-slate-500 py-1">{d}</div>
<div key={d} className="text-center text-xs font-medium text-[var(--text-3)] py-1">{d}</div>
))}
</div>
<div className="grid grid-cols-7 gap-0.5 px-3 pb-3">
@@ -205,10 +205,10 @@ function PersianDateInput({ value, onChange, minDate, placeholder }: {
return (
<button key={jd} type="button" disabled={dis} onClick={() => selectDay(jd)}
className={`w-full aspect-square flex items-center justify-center rounded-lg text-sm transition-colors
${dis ? 'text-slate-300 dark:text-gray-600 cursor-not-allowed'
: sel ? 'bg-primary-500 text-white font-semibold shadow-sm'
${dis ? 'text-[var(--text-3)] dark:text-[var(--text-2)] cursor-not-allowed'
: sel ? 'bg-primary-500 text-[var(--on-primary)] font-semibold shadow-sm'
: tod ? 'ring-1 ring-primary-400 text-primary-700 dark:text-primary-300 font-medium'
: 'text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-gray-800'}`}>
: 'text-[var(--text)] dark:text-[var(--text-3)] hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)]'}`}>
{toPersianNums(jd)}
</button>
);
@@ -331,10 +331,10 @@ function SlotEditor({ slots, onChange }: {
<div className="space-y-2">
{slots.map((slot, i) => (
<div key={i} className="flex items-center gap-2 flex-wrap">
<span className="text-xs text-slate-500 dark:text-slate-400 shrink-0">از</span>
<span className="text-xs text-[var(--text-2)] shrink-0">از</span>
<input type="time" value={slot.start} onChange={e => update(i, 'start', e.target.value)}
className="cp-input h-8 w-28 text-sm font-mono text-center px-2" />
<span className="text-xs text-slate-500 dark:text-slate-400 shrink-0">تا</span>
<span className="text-xs text-[var(--text-2)] shrink-0">تا</span>
<input type="time" value={slot.end} onChange={e => update(i, 'end', e.target.value)}
className="cp-input h-8 w-28 text-sm font-mono text-center px-2" />
<div style={{ width: 110 }}>
@@ -346,7 +346,7 @@ function SlotEditor({ slots, onChange }: {
/>
</div>
<button type="button" onClick={() => remove(i)}
className="w-7 h-7 flex items-center justify-center rounded-lg text-slate-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-500/10 transition-colors shrink-0">
className="w-7 h-7 flex items-center justify-center rounded-lg text-[var(--text-3)] hover:text-[var(--danger)] hover:bg-[var(--danger-bg)] dark:hover:bg-[var(--danger)]/10 transition-colors shrink-0">
<XMarkIcon className="w-3.5 h-3.5" />
</button>
</div>
@@ -441,7 +441,7 @@ function SessionEditor({ session, onChange, onRemove, addresses, serviceMode = f
}}>
<span style={{
position: 'absolute', top: 3, borderRadius: '50%',
width: 16, height: 16, background: '#fff',
width: 16, height: 16, background: 'var(--surface)',
boxShadow: '0 1px 3px rgba(0,0,0,.25)',
insetInlineStart: session.has_rest ? 20 : 3,
}} />
@@ -511,12 +511,12 @@ function NoLocationsNotice({ clinicUuid }: { clinicUuid?: string | null }) {
: ['ابتدا آدرس مطب را ثبت کنید', 'برنامه کاری نیاز به حداقل یک مکان نوبت دارد'];
return (
<div className="flex flex-col items-center justify-center gap-3 py-12 text-center">
<div className="w-12 h-12 rounded-full bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/30 flex items-center justify-center">
<MapPinIcon className="w-6 h-6 text-amber-500" />
<div className="w-12 h-12 rounded-full bg-[var(--warning-bg)] dark:bg-amber-500/10 border border-[var(--warning)] dark:border-amber-500/30 flex items-center justify-center">
<MapPinIcon className="w-6 h-6 text-[var(--warning)]" />
</div>
<div>
<p className="text-sm font-medium text-slate-700 dark:text-slate-200">{title}</p>
<p className="text-xs text-slate-400 dark:text-slate-500 mt-1">{hint}</p>
<p className="text-sm font-medium text-[var(--text)]">{title}</p>
<p className="text-xs text-[var(--text-3)] mt-1">{hint}</p>
</div>
</div>
);
@@ -627,8 +627,8 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
return (
<div className="space-y-2">
{totalSlots > 0 && (
<div className="flex items-center gap-2 mb-3 p-3 rounded-xl bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/30">
<CalendarIcon className="w-4 h-4 text-emerald-600 dark:text-emerald-400 shrink-0" />
<div className="flex items-center gap-2 mb-3 p-3 rounded-xl bg-[var(--success-bg)] dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/30">
<CalendarIcon className="w-4 h-4 text-[var(--success)] dark:text-emerald-400 shrink-0" />
<span className="text-sm text-emerald-700 dark:text-emerald-300">
مجموع <strong>{totalSlots}</strong> نوبت در هفته
</span>
@@ -638,11 +638,11 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
const activeSessions = (scheduleMap[day.key]?.sessions ?? []).filter(s => s.active);
const daySlots = activeSessions.reduce((sum, s) => sum + calcSlotCount(s), 0);
return (
<div key={day.key} className="flex items-center gap-3 px-4 py-3 rounded-xl border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-800/40">
<div key={day.key} className="flex items-center gap-3 px-4 py-3 rounded-xl border border-[var(--border)] bg-[var(--surface)] dark:bg-[var(--surface)]/40">
<b style={{ fontSize: 14, minWidth: 56, flexShrink: 0 }}>{day.label}</b>
<div className="flex-1 flex gap-2 items-center flex-wrap min-w-0">
{activeSessions.length > 0 ? activeSessions.map((s, i) => (
<span key={i} className="text-xs px-2 py-1 rounded-lg bg-slate-100 dark:bg-gray-700 text-slate-700 dark:text-slate-200" style={{ fontFamily: 'monospace', direction: 'ltr', whiteSpace: 'nowrap' }}>
<span key={i} className="text-xs px-2 py-1 rounded-lg bg-[var(--surface-2)] text-[var(--text)]" style={{ fontFamily: 'monospace', direction: 'ltr', whiteSpace: 'nowrap' }}>
{s.start_time} {s.end_time}
</span>
)) : (
@@ -665,8 +665,8 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
<div className="space-y-2">
{/* ─ خلاصه کل هفته */}
{totalSlots > 0 && (
<div className="flex items-center gap-2 mb-3 p-3 rounded-xl bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/30">
<CalendarIcon className="w-4 h-4 text-emerald-600 dark:text-emerald-400 shrink-0" />
<div className="flex items-center gap-2 mb-3 p-3 rounded-xl bg-[var(--success-bg)] dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/30">
<CalendarIcon className="w-4 h-4 text-[var(--success)] dark:text-emerald-400 shrink-0" />
<span className="text-sm text-emerald-700 dark:text-emerald-300">
مجموع <strong>{totalSlots}</strong> نوبت در هفته
</span>
@@ -674,9 +674,9 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
)}
{/* ─ روش نوبت‌دهی */}
<div className="mb-3 rounded-xl border border-slate-200 dark:border-gray-700 overflow-hidden">
<div className="px-4 py-3 bg-slate-50 dark:bg-gray-800/50">
<span className="text-sm font-medium text-slate-700 dark:text-slate-200">روش نوبتدهی</span>
<div className="mb-3 rounded-xl border border-[var(--border)] overflow-hidden">
<div className="px-4 py-3 bg-[var(--surface-2)] dark:bg-[var(--surface)]/50">
<span className="text-sm font-medium text-[var(--text)]">روش نوبتدهی</span>
</div>
<div className="px-4 py-3 space-y-3">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
@@ -694,48 +694,48 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
className={`text-right p-3 rounded-lg border transition-colors ${
selected
? 'border-[var(--primary)] bg-[var(--primary)]/5'
: 'border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-900'
: 'border-[var(--border)] dark:border-[var(--border)] bg-[var(--surface)] dark:bg-[var(--bg)]'
} ${modeLocked ? 'opacity-70 cursor-not-allowed' : 'hover:border-[var(--primary)]'}`}
>
<div className="flex items-center gap-2 mb-1">
<span className={`w-3.5 h-3.5 rounded-full border shrink-0 ${selected ? 'border-[var(--primary)] bg-[var(--primary)]' : 'border-slate-300 dark:border-gray-600'}`} />
<span className="text-sm font-medium text-slate-700 dark:text-slate-200">{lbl}</span>
<span className={`w-3.5 h-3.5 rounded-full border shrink-0 ${selected ? 'border-[var(--primary)] bg-[var(--primary)]' : 'border-[var(--border-2)] dark:border-gray-600'}`} />
<span className="text-sm font-medium text-[var(--text)]">{lbl}</span>
</div>
<p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed">{desc}</p>
<p className="text-xs text-[var(--text-2)] leading-relaxed">{desc}</p>
</button>
);
})}
</div>
{modeLocked ? (
<p className="text-xs text-slate-500 dark:text-slate-400 flex items-center gap-1.5">
<p className="text-xs text-[var(--text-2)] flex items-center gap-1.5">
<LockClosedIcon className="w-3.5 h-3.5 shrink-0" />
نوع نوبتدهی ثبت شده و دیگر قابل تغییر نیست.
</p>
) : (
<p className="text-xs text-amber-600 dark:text-amber-400 leading-relaxed">
<p className="text-xs text-[var(--warning)] dark:text-amber-400 leading-relaxed">
توجه: نوع نوبتدهی پس از اولین ثبت <span className="font-medium">بههیچعنوان قابل تغییر نیست</span>. پیش از ذخیره با دقت انتخاب کنید.
</p>
)}
{meta.booking_mode === 'service' ? (
<>
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm text-slate-600 dark:text-slate-400">فاصله بین نوبتها</span>
<span className="text-sm text-[var(--text-2)]">فاصله بین نوبتها</span>
<input
type="text"
inputMode="numeric"
dir="ltr"
value={meta.buffer_minutes}
onChange={(e) => setMeta(m => ({ ...m, buffer_minutes: Math.max(0, Number(digitsOnly(e.target.value)) || 0) }))}
className="w-16 text-center text-sm rounded-lg border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 focus:outline-none focus:ring-0"
className="w-16 text-center text-sm rounded-lg border border-[var(--border)] bg-[var(--surface)] px-2 py-1.5 focus:outline-none focus:ring-0"
/>
<span className="text-sm text-slate-600 dark:text-slate-400">دقیقه</span>
<span className="text-sm text-[var(--text-2)]">دقیقه</span>
</div>
<p className="text-xs text-slate-400 dark:text-slate-500 leading-relaxed">
<p className="text-xs text-[var(--text-3)] leading-relaxed">
مدت هر نوبت از «مدت سرویس» انتخابشده تعیین میشود. لازم است حداقل یک سرویس با «نمایش در نوبتدهی» در بخش <span className="font-medium">{clinicUuid ? 'سرویس‌های کلینیک' : 'سرویس‌ها'}</span> تعریف کنید، وگرنه ذخیره نمیشود.
</p>
</>
) : (
<p className="text-xs text-slate-400 dark:text-slate-500 leading-relaxed">
<p className="text-xs text-[var(--text-3)] leading-relaxed">
مدت هر نوبت از «زمان هر نوبت» در شیفتهای زیر تعیین میشود.
</p>
)}
@@ -743,12 +743,12 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
</div>
{/* ─ نوبت‌دهی آنلاین */}
<div className="mb-3 rounded-xl border border-slate-200 dark:border-gray-700 overflow-hidden">
<div className="mb-3 rounded-xl border border-[var(--border)] overflow-hidden">
{/* header + toggle */}
<label className="flex items-center justify-between gap-3 px-4 py-3 cursor-pointer bg-slate-50 dark:bg-gray-800/50">
<label className="flex items-center justify-between gap-3 px-4 py-3 cursor-pointer bg-[var(--surface-2)] dark:bg-[var(--surface)]/50">
<div className="flex items-center gap-2">
<GlobeAltIcon className="w-4 h-4 text-slate-500 dark:text-slate-400 shrink-0" />
<span className="text-sm font-medium text-slate-700 dark:text-slate-200">نوبتدهی آنلاین</span>
<GlobeAltIcon className="w-4 h-4 text-[var(--text-2)] shrink-0" />
<span className="text-sm font-medium text-[var(--text)]">نوبتدهی آنلاین</span>
</div>
<span className="relative inline-flex shrink-0">
<input
@@ -758,14 +758,14 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
onChange={(e) => setMeta(m => ({ ...m, online_booking_enabled: e.target.checked }))}
/>
<span className="w-10 h-6 rounded-full bg-slate-300 dark:bg-gray-600 transition-colors peer-checked:bg-emerald-500" />
<span className="absolute top-0.5 right-0.5 w-5 h-5 rounded-full bg-white shadow transition-transform peer-checked:-translate-x-4" />
<span className="absolute top-0.5 right-0.5 w-5 h-5 rounded-full bg-[var(--surface)] shadow transition-transform peer-checked:-translate-x-4" />
</span>
</label>
{/* booking window control */}
<div className={`px-4 py-3 transition-opacity ${meta.online_booking_enabled ? '' : 'opacity-50 pointer-events-none'}`}>
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm text-slate-600 dark:text-slate-400">رزرو آنلاین تا</span>
<span className="text-sm text-[var(--text-2)]">رزرو آنلاین تا</span>
<div className="flex items-stretch gap-2">
<input
type="text"
@@ -774,7 +774,7 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
value={meta.booking_window_value}
disabled={!meta.online_booking_enabled}
onChange={(e) => setMeta(m => ({ ...m, booking_window_value: Math.max(1, Number(digitsOnly(e.target.value)) || 1) }))}
className="w-14 text-center text-sm rounded-lg border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-900 focus:outline-none focus:ring-0 px-2 py-1.5"
className="w-14 text-center text-sm rounded-lg border border-[var(--border)] bg-[var(--surface)] focus:outline-none focus:ring-0 px-2 py-1.5"
/>
<div style={{ width: 110 }}>
<GlobalSearchableSelect
@@ -786,9 +786,9 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
/>
</div>
</div>
<span className="text-sm text-slate-600 dark:text-slate-400">آینده</span>
<span className="text-sm text-[var(--text-2)]">آینده</span>
</div>
<p className="text-xs text-slate-400 dark:text-slate-500 mt-2 leading-relaxed">
<p className="text-xs text-[var(--text-3)] mt-2 leading-relaxed">
بیمار فقط تا این بازه میتواند آنلاین نوبت بگیرد؛ روزهای بعد از آن روی تقویم غیرفعالاند.
</p>
</div>
@@ -864,7 +864,7 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
<div className="flex items-center justify-between pt-3">
{(hasAnyOverlap || missingLocation) && (
<p className="text-xs text-red-500 dark:text-red-400 flex items-center gap-1">
<p className="text-xs text-[var(--danger)] dark:text-red-400 flex items-center gap-1">
<ExclamationTriangleIcon className="w-3.5 h-3.5" />
{hasAnyOverlap ? 'تداخل زمانی در برنامه وجود دارد' : 'مکان مطب برای همه بازه‌ها الزامی است'}
</p>
@@ -956,17 +956,17 @@ function DateOverrideModal({ open, onClose, existing, doctorUuid, clinicUuid, on
>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">تاریخ</label>
<label className="block text-sm font-medium text-[var(--text)] mb-1.5">تاریخ</label>
<PersianDateInput value={dateStr} onChange={setDateStr} placeholder="انتخاب تاریخ" />
</div>
{/* ─ نوع override */}
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">نوع این روز</label>
<label className="block text-sm font-medium text-[var(--text)] mb-2">نوع این روز</label>
<div className="grid grid-cols-2 gap-2">
<button type="button"
onClick={() => setOverrideType('closed')}
className={`flex items-center gap-2 p-3 rounded-xl border-2 text-sm transition-colors text-right ${overrideType === 'closed' ? 'border-red-400 bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-300' : 'border-slate-200 dark:border-gray-700 text-slate-600 dark:text-slate-400 hover:border-slate-300'}`}>
className={`flex items-center gap-2 p-3 rounded-xl border-2 text-sm transition-colors text-right ${overrideType === 'closed' ? 'border-red-400 bg-[var(--danger-bg)] dark:bg-[var(--danger)]/10 text-[var(--danger)] dark:text-red-300' : 'border-[var(--border)] dark:border-[var(--border)] text-[var(--text-2)] dark:text-[var(--text-3)] hover:border-[var(--border-2)]'}`}>
<XCircleIcon className="w-5 h-5 shrink-0" />
<div>
<p className="font-medium leading-tight">تعطیل است</p>
@@ -975,7 +975,7 @@ function DateOverrideModal({ open, onClose, existing, doctorUuid, clinicUuid, on
</button>
<button type="button"
onClick={() => setOverrideType('custom')}
className={`flex items-center gap-2 p-3 rounded-xl border-2 text-sm transition-colors text-right ${overrideType === 'custom' ? 'border-amber-400 bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-300' : 'border-slate-200 dark:border-gray-700 text-slate-600 dark:text-slate-400 hover:border-slate-300'}`}>
className={`flex items-center gap-2 p-3 rounded-xl border-2 text-sm transition-colors text-right ${overrideType === 'custom' ? 'border-amber-400 bg-[var(--warning-bg)] dark:bg-amber-500/10 text-amber-700 dark:text-amber-300' : 'border-[var(--border)] dark:border-[var(--border)] text-[var(--text-2)] dark:text-[var(--text-3)] hover:border-[var(--border-2)]'}`}>
<CalendarIcon className="w-5 h-5 shrink-0" />
<div>
<p className="font-medium leading-tight">ساعات خاص</p>
@@ -986,7 +986,7 @@ function DateOverrideModal({ open, onClose, existing, doctorUuid, clinicUuid, on
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">دلیل (اختیاری)</label>
<label className="block text-sm font-medium text-[var(--text)] mb-1.5">دلیل (اختیاری)</label>
<input type="text" value={reason} onChange={e => setReason(e.target.value)}
placeholder={overrideType === 'closed' ? 'مثال: سفر، مریضی، کنگره...' : 'مثال: شیفت اضطراری، ویزیت خاص...'}
className="input" />
@@ -1020,9 +1020,9 @@ function DateOverrideModal({ open, onClose, existing, doctorUuid, clinicUuid, on
)}
{overrideType === 'closed' && (
<div className="flex items-start gap-2 p-3 rounded-xl bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30">
<ExclamationTriangleIcon className="w-4 h-4 text-red-500 shrink-0 mt-0.5" />
<p className="text-xs text-red-700 dark:text-red-400">
<div className="flex items-start gap-2 p-3 rounded-xl bg-[var(--danger-bg)] dark:bg-[var(--danger)]/10 border border-[var(--danger)] dark:border-red-500/30">
<ExclamationTriangleIcon className="w-4 h-4 text-[var(--danger)] shrink-0 mt-0.5" />
<p className="text-xs text-[var(--danger)] dark:text-red-400">
در این روز هیچ نوبتی داده نخواهد شد، حتی اگر در برنامه هفتگی ساعات کاری داشته باشید.
</p>
</div>
@@ -1072,37 +1072,37 @@ function DateOverridesTab({ doctorUuid, clinicUuid, addresses, readOnly = false
</div>
)}
{overrides.length === 0 ? (
<div className="text-center py-10 text-slate-400 dark:text-slate-500">
<div className="text-center py-10 text-[var(--text-3)]">
<CalendarIcon className="w-10 h-10 mx-auto mb-2 opacity-30" />
<p className="text-sm">هیچ تاریخ خاصی تنظیم نشده</p>
</div>
) : (
<div className="space-y-2">
{overrides.map(ov => (
<div key={ov.uuid} className="flex items-center gap-3 p-3.5 rounded-xl border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-800/60">
<div key={ov.uuid} className="flex items-center gap-3 p-3.5 rounded-xl border border-[var(--border)] bg-[var(--surface)] dark:bg-[var(--surface)]/60">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm font-medium text-slate-800 dark:text-slate-200">{formatPersianDate(tsToDate(ov.date))}</span>
<span className="text-sm font-medium text-[var(--text)]">{formatPersianDate(tsToDate(ov.date))}</span>
{ov.active ? (
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-amber-100 dark:bg-amber-500/20 text-amber-700 dark:text-amber-300">
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-[var(--warning-bg)] dark:bg-amber-500/20 text-amber-700 dark:text-amber-300">
{ov.custom_slots.length > 0 ? `${ov.custom_slots.length} بازه سفارشی` : 'ساعات خاص'}
</span>
) : (
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-red-100 dark:bg-red-500/20 text-red-600 dark:text-red-400">
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-[var(--danger-bg)] dark:bg-[var(--danger)]/20 text-[var(--danger)] dark:text-red-400">
تعطیل
</span>
)}
</div>
{ov.reason && <p className="text-xs text-slate-400 dark:text-slate-500 mt-0.5">{ov.reason}</p>}
{ov.reason && <p className="text-xs text-[var(--text-3)] mt-0.5">{ov.reason}</p>}
</div>
{!readOnly && (
<div className="flex items-center gap-1 shrink-0">
<button onClick={() => { setEditing(ov); setModalOpen(true); }}
className="w-8 h-8 flex items-center justify-center rounded-lg text-slate-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-500/10 transition-colors">
className="w-8 h-8 flex items-center justify-center rounded-lg text-[var(--text-3)] hover:text-[var(--info)] hover:bg-[var(--info-bg)] dark:hover:bg-blue-500/10 transition-colors">
<PencilIcon className="w-3.5 h-3.5" />
</button>
<button onClick={() => setDeletingUuid(ov.uuid)}
className="w-8 h-8 flex items-center justify-center rounded-lg text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-500/10 transition-colors">
className="w-8 h-8 flex items-center justify-center rounded-lg text-[var(--text-3)] hover:text-[var(--danger)] hover:bg-[var(--danger-bg)] dark:hover:bg-[var(--danger)]/10 transition-colors">
<TrashIcon className="w-3.5 h-3.5" />
</button>
</div>
@@ -1173,19 +1173,19 @@ function HolidayModal({ open, onClose, existing, doctorUuid, clinicUuid, onSaved
<div className="space-y-4">
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">از تاریخ</label>
<label className="block text-sm font-medium text-[var(--text)] mb-1.5">از تاریخ</label>
<PersianDateInput value={startDate} onChange={v => { setStartDate(v); if (endDate && v > endDate) setEndDate(v); }} />
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">تا تاریخ</label>
<label className="block text-sm font-medium text-[var(--text)] mb-1.5">تا تاریخ</label>
<PersianDateInput value={endDate} onChange={setEndDate} minDate={startDate} />
</div>
</div>
{endDate && startDate && endDate < startDate && (
<p className="text-xs text-red-500">تاریخ پایان نمیتواند قبل از تاریخ شروع باشد</p>
<p className="text-xs text-[var(--danger)]">تاریخ پایان نمیتواند قبل از تاریخ شروع باشد</p>
)}
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">دلیل (اختیاری)</label>
<label className="block text-sm font-medium text-[var(--text)] mb-1.5">دلیل (اختیاری)</label>
<input type="text" value={reason} onChange={e => setReason(e.target.value)}
placeholder="مثال: سفر، کنگره پزشکی..." className="input" />
</div>
@@ -1238,7 +1238,7 @@ function HolidaysTab({ doctorUuid, clinicUuid, readOnly = false }: { doctorUuid:
</div>
)}
{holidays.length === 0 ? (
<div className="text-center py-10 text-slate-400 dark:text-slate-500">
<div className="text-center py-10 text-[var(--text-3)]">
<CalendarIcon className="w-10 h-10 mx-auto mb-2 opacity-30" />
<p className="text-sm">هیچ تعطیلاتی ثبت نشده</p>
</div>
@@ -1250,39 +1250,39 @@ function HolidaysTab({ doctorUuid, clinicUuid, readOnly = false }: { doctorUuid:
const isCurrent = h.start_date <= now && h.end_date >= now;
const sameDay = tsToDate(h.start_date) === tsToDate(h.end_date);
return (
<div key={h.uuid} className={`flex items-center gap-3 p-3.5 rounded-xl border transition-colors ${isCurrent ? 'border-red-200 dark:border-red-500/30 bg-red-50/40 dark:bg-red-500/5' : isPast ? 'border-slate-200 dark:border-gray-700 bg-slate-50/40 dark:bg-gray-800/30 opacity-70' : 'border-orange-200 dark:border-orange-500/30 bg-orange-50/30 dark:bg-orange-500/5'}`}>
<div key={h.uuid} className={`flex items-center gap-3 p-3.5 rounded-xl border transition-colors ${isCurrent ? 'border-[var(--danger)] dark:border-red-500/30 bg-[var(--danger-bg)]/40 dark:bg-[var(--danger)]/5' : isPast ? 'border-[var(--border)] dark:border-[var(--border)] bg-[var(--surface-2)]/40 dark:bg-[var(--surface)]/30 opacity-70' : 'border-orange-200 dark:border-orange-500/30 bg-orange-50/30 dark:bg-orange-500/5'}`}>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className={`text-sm font-medium ${isPast ? 'text-slate-500 dark:text-slate-400' : 'text-slate-800 dark:text-slate-200'}`}>
<span className={`text-sm font-medium ${isPast ? 'text-[var(--text-2)] dark:text-[var(--text-3)]' : 'text-[var(--text)] dark:text-[var(--text)]'}`}>
{sameDay ? formatPersianDate(tsToDate(h.start_date)) : `${formatPersianDate(tsToDate(h.start_date))} تا ${formatPersianDate(tsToDate(h.end_date))}`}
</span>
{isCurrent && h.active && (
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-red-100 dark:bg-red-500/20 text-red-600 dark:text-red-400 animate-pulse">در جریان</span>
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-[var(--danger-bg)] dark:bg-[var(--danger)]/20 text-[var(--danger)] dark:text-red-400 animate-pulse">در جریان</span>
)}
{!isCurrent && !isPast && h.active && (
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-orange-100 dark:bg-orange-500/20 text-orange-600 dark:text-orange-400">آینده</span>
)}
{isPast && (
<span className="text-xs px-2 py-0.5 rounded-full bg-slate-100 dark:bg-gray-700 text-slate-400 dark:text-slate-500">گذشته</span>
<span className="text-xs px-2 py-0.5 rounded-full bg-[var(--surface-2)] text-[var(--text-3)]">گذشته</span>
)}
{!h.active && (
<span className="text-xs px-2 py-0.5 rounded-full bg-slate-100 dark:bg-gray-700 text-slate-400 dark:text-slate-500">غیرفعال</span>
<span className="text-xs px-2 py-0.5 rounded-full bg-[var(--surface-2)] text-[var(--text-3)]">غیرفعال</span>
)}
</div>
{h.reason && <p className="text-xs text-slate-400 dark:text-slate-500 mt-0.5">{h.reason}</p>}
{h.reason && <p className="text-xs text-[var(--text-3)] mt-0.5">{h.reason}</p>}
</div>
{!readOnly && (
<div className="flex items-center gap-1 shrink-0">
<button onClick={() => toggleMut.mutate(h)} disabled={toggleMut.isPending}
className={`text-xs px-2.5 h-7 rounded-lg border transition-colors disabled:opacity-50 ${h.active ? 'border-slate-200 dark:border-gray-600 text-slate-500 dark:text-slate-400 hover:border-slate-300 hover:text-slate-700 dark:hover:text-slate-200' : 'border-emerald-200 dark:border-emerald-500/40 text-emerald-600 dark:text-emerald-400 hover:bg-emerald-50 dark:hover:bg-emerald-500/10'}`}>
className={`text-xs px-2.5 h-7 rounded-lg border transition-colors disabled:opacity-50 ${h.active ? 'border-[var(--border)] dark:border-gray-600 text-[var(--text-2)] dark:text-[var(--text-3)] hover:border-[var(--border-2)] hover:text-[var(--text)] dark:hover:text-[var(--text)]' : 'border-emerald-200 dark:border-emerald-500/40 text-[var(--success)] dark:text-emerald-400 hover:bg-[var(--success-bg)] dark:hover:bg-emerald-500/10'}`}>
{h.active ? 'غیرفعال' : 'فعال'}
</button>
<button onClick={() => { setEditing(h); setModalOpen(true); }}
className="w-8 h-8 flex items-center justify-center rounded-lg text-slate-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-500/10 transition-colors">
className="w-8 h-8 flex items-center justify-center rounded-lg text-[var(--text-3)] hover:text-[var(--info)] hover:bg-[var(--info-bg)] dark:hover:bg-blue-500/10 transition-colors">
<PencilIcon className="w-3.5 h-3.5" />
</button>
<button onClick={() => setDeletingUuid(h.uuid)}
className="w-8 h-8 flex items-center justify-center rounded-lg text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-500/10 transition-colors">
className="w-8 h-8 flex items-center justify-center rounded-lg text-[var(--text-3)] hover:text-[var(--danger)] hover:bg-[var(--danger-bg)] dark:hover:bg-[var(--danger)]/10 transition-colors">
<TrashIcon className="w-3.5 h-3.5" />
</button>
</div>
@@ -1323,14 +1323,14 @@ export function ScheduleSection({ doctorUuid, clinicUuid, readOnly = false }: {
return (
<div className="cp-card p-6">
<h2 className="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-4">برنامه کاری</h2>
<div className="flex gap-1 mb-5 bg-slate-100 dark:bg-gray-800 p-1 rounded-xl">
<h2 className="text-sm font-semibold text-[var(--text)] mb-4">برنامه کاری</h2>
<div className="flex gap-1 mb-5 bg-[var(--surface-2)] p-1 rounded-xl">
{SCHEDULE_TABS.map(t => (
<button key={t.id} type="button" onClick={() => setTab(t.id)}
className={`flex-1 text-xs sm:text-sm font-medium px-2 py-1.5 rounded-lg transition-colors ${
tab === t.id
? 'bg-white dark:bg-gray-700 text-slate-800 dark:text-slate-100 shadow-sm'
: 'text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200'
? 'bg-[var(--surface)] dark:bg-[var(--surface-2)] text-[var(--text)] dark:text-[var(--text)] shadow-sm'
: 'text-[var(--text-2)] dark:text-[var(--text-3)] hover:text-[var(--text)] dark:hover:text-[var(--text)]'
}`}>
{t.label}
</button>
+33 -33
View File
@@ -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>}
+35 -35
View File
@@ -4,8 +4,8 @@ import type { SessionCardData } from '../SessionServiceCard';
import { METHOD_LABELS } from './PaymentStep';
import { useIssueInvoice } from '../../hooks/useIssueInvoice';
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 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 {
session: SessionCardData;
@@ -69,82 +69,82 @@ export default function DetailsStep({ session, recordUuid, onBack, onFinish }: P
return (
<>
{/* گام جزییات — پورت tauri Step3Final با دیتای واقعی */}
<div dir="rtl" className="dark:border-[#404040] dark:bg-[#222433]" style={{ padding: 16, border: '1px dashed #A7A7E0' }}>
<div dir="rtl" className="dark:bg-[var(--surface)]" style={{ padding: 16, border: '1px dashed var(--primary-soft2)' }}>
{visitAt && (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, marginBottom: 16 }}>
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 13, color: '#616161' }}>تاریخ و ساعت مراجعه:</span>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 600, color: '#2f2f2f' }}>{formatDateTime(visitAt)}</span>
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>تاریخ و ساعت مراجعه:</span>
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>{formatDateTime(visitAt)}</span>
</div>
)}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 32, marginBottom: 16 }}>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 500, color: '#2f2f2f' }}>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text)' }}>
{serviceNames.length ? serviceNames.join(' - ') : 'ویزیت'}
</span>
<span className="dark:bg-[#404040]" style={{ width: 1, height: 38, background: '#E0E0E0' }} />
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 500, color: '#525252' }}>{session.doctor_name || '—'}</span>
<span style={{ width: 1, height: 38, background: 'var(--border-2)' }} />
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text)' }}>{session.doctor_name || '—'}</span>
</div>
{costItems.length > 0 && (
<div className="dark:border-[#404040]" style={{ marginBottom: 16, borderTop: '1px solid #EEE', borderBottom: '1px solid #EEE', padding: '4px 0' }}>
<div style={{ marginBottom: 16, borderTop: '1px solid var(--border)', borderBottom: '1px solid var(--border)', padding: '4px 0' }}>
{costItems.map((it, i) => (
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 0' }}>
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 13, color: '#616161' }}>{it.label}</span>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 500, color: '#525252' }}>{formatRial(it.rials)}</span>
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>{it.label}</span>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text)' }}>{formatRial(it.rials)}</span>
</div>
))}
{hasInsurance && (
<>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 0 4px', borderTop: '1px dashed #E0E0E0' }}>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 13, fontWeight: 700, color: '#2f2f2f' }}>جمع کل خدمات</span>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 700, color: '#2f2f2f' }}>{formatRial(grossTotal)}</span>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 0 4px', borderTop: '1px dashed var(--border-2)' }}>
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>جمع کل خدمات</span>
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>{formatRial(grossTotal)}</span>
</div>
{baseInsurance > 0 && (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 0' }}>
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 13, color: '#616161' }}>سهم بیمه پایه</span>
<span style={{ fontSize: 14, fontWeight: 500, color: '#2E7D32' }}>{formatRial(baseInsurance)}</span>
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>سهم بیمه پایه</span>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--success)' }}>{formatRial(baseInsurance)}</span>
</div>
)}
{suppInsurance > 0 && (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 0' }}>
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 13, color: '#616161' }}>سهم بیمه تکمیلی</span>
<span style={{ fontSize: 14, fontWeight: 500, color: '#2E7D32' }}>{formatRial(suppInsurance)}</span>
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>سهم بیمه تکمیلی</span>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--success)' }}>{formatRial(suppInsurance)}</span>
</div>
)}
</>
)}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 0 4px', borderTop: '1px dashed #E0E0E0' }}>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 13, fontWeight: 700, color: '#2f2f2f' }}>{hasInsurance ? 'سهم بیمار' : 'جمع کل'}</span>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 700, color: '#2f2f2f' }}>{formatRial(finalPrice)}</span>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 0 4px', borderTop: '1px dashed var(--border-2)' }}>
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{hasInsurance ? 'سهم بیمار' : 'جمع کل'}</span>
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>{formatRial(finalPrice)}</span>
</div>
</div>
)}
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 72, marginBottom: 16 }}>
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 500, color: '#525252' }}>هزینه سرویس:</span>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 500, color: '#525252' }}>{formatRial(finalPrice)}</span>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text)' }}>هزینه سرویس:</span>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text)' }}>{formatRial(finalPrice)}</span>
</span>
<span className="dark:bg-[#404040]" style={{ width: 1, height: 38, background: '#E0E0E0' }} />
<span style={{ width: 1, height: 38, background: 'var(--border-2)' }} />
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 500, color: '#525252' }}>تخفیف:</span>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 500, color: '#525252' }}>{formatRial(discountRials)}</span>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text)' }}>تخفیف:</span>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text)' }}>{formatRial(discountRials)}</span>
</span>
</div>
<span className="dark:text-[#6A6AD9]" style={{ fontSize: 13, fontWeight: 600, color: '#5559CE', display: 'block', marginBottom: 12 }}>پرداخت شده ها:</span>
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--primary)', display: 'block', marginBottom: 12 }}>پرداخت شده ها:</span>
{payments.length === 0 ? (
<span className="dark:text-[#A1A1A1]" style={{ fontSize: 13, color: '#6B7280' }}>پرداختی ثبت نشده است</span>
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>پرداختی ثبت نشده است</span>
) : payments.map((p) => (
<div key={p.uuid} className="dark:border-[#404040]" style={{ padding: '8px 0', borderBottom: '1px solid #EEE' }}>
<div key={p.uuid} style={{ padding: '8px 0', borderBottom: '1px solid var(--border)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 56 }}>
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span className="dark:bg-[#6A6AD9]" style={{ width: 6, height: 6, borderRadius: '50%', background: '#5559CE', flexShrink: 0 }} />
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 600, color: '#525252' }}>{METHOD_LABELS[p.method] ?? p.method}</span>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--primary)', flexShrink: 0 }} />
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>{METHOD_LABELS[p.method] ?? p.method}</span>
</span>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 600, color: '#111827' }}>مبلغ: {formatRial(p.amount_rials)}</span>
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>مبلغ: {formatRial(p.amount_rials)}</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>
@@ -153,8 +153,8 @@ export default function DetailsStep({ session, recordUuid, onBack, onFinish }: P
<div style={{ display: 'flex', width: '100%', justifyContent: 'flex-end', alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 16 }}>
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 600, color: '#525252' }}>مبلغ باقی مانده:</span>
<span className="dark:text-[#FF5252]" style={{ fontSize: 14, fontWeight: 600, color: '#d32f2f' }}>{formatRial(debt)}</span>
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text)' }}>مبلغ باقی مانده:</span>
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--danger)' }}>{formatRial(debt)}</span>
</div>
</div>
</div>
+51 -51
View File
@@ -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>
@@ -16,9 +16,9 @@ const FIELD_LABELS: Record<string, string> = {
};
const OP_LABELS: Record<string, { label: string; color: string }> = {
create: { label: 'ایجاد', color: '#3c9a4f' },
update: { label: 'ویرایش', color: '#5559ce' },
delete: { label: 'حذف', color: '#EF4444' },
create: { label: 'ایجاد', color: 'var(--success)' },
update: { label: 'ویرایش', color: 'var(--primary)' },
delete: { label: 'حذف', color: 'var(--danger)' },
};
// مقادیر پولی (ریال) را با فرمت تومان نشان بده؛ بقیه را خام.
@@ -53,7 +53,7 @@ export default function SessionAuditModal({ sessionUuid, onClose }: { sessionUui
<div key={i} className="card" style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 6 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span className="badge" style={{ fontSize: 11, color: '#fff', background: op.color, borderRadius: 4, padding: '2px 8px' }}>{op.label}</span>
<span className="badge" style={{ fontSize: 11, color: 'var(--on-primary)', background: op.color, borderRadius: 4, padding: '2px 8px' }}>{op.label}</span>
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>{FIELD_LABELS[r.field] ?? r.field}</span>
</span>
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>{formatDateTime(r.created_at)}</span>
@@ -8,15 +8,15 @@ import ConfirmAppointmentModal from '../appointments/ConfirmAppointmentModal';
// Labels follow the Figma نوبت‌ها design (ثبت شده / قطعی شده / ویزیت شده …).
export const STATUS_META: Record<string, { label: string; color: string }> = {
pending: { label: 'ثبت شده', color: '#3b82f6' },
confirmed: { label: 'قطعی شده', color: '#14b8a6' },
following_up: { label: 'در حال پیگیری', color: '#f59e0b' },
salon: { label: 'سالن', color: '#8b5cf6' },
completed: { label: 'ویزیت شده', color: '#22c55e' },
cancelled_by_doctor: { label: 'لغو شده', color: '#ef4444' },
cancelled_by_user: { label: 'لغو توسط بیمار', color: '#ef4444' },
no_show: { label: 'غیبت', color: '#9ca3af' },
expired: { label: 'منقضی شده', color: '#9ca3af' },
pending: { label: 'ثبت شده', color: 'var(--info)' },
confirmed: { label: 'قطعی شده', color: 'var(--primary)' },
following_up: { label: 'در حال پیگیری', color: 'var(--warning)' },
salon: { label: 'سالن', color: 'var(--violet)' },
completed: { label: 'ویزیت شده', color: 'var(--success)' },
cancelled_by_doctor: { label: 'لغو شده', color: 'var(--danger)' },
cancelled_by_user: { label: 'لغو توسط بیمار', color: 'var(--danger)' },
no_show: { label: 'غیبت', color: 'var(--text-3)' },
expired: { label: 'منقضی شده', color: 'var(--text-3)' },
};
// Mirrors Appointment::ALLOWED_TRANSITIONS on the backend.
@@ -83,7 +83,7 @@ export default function AppointmentStatusDropdown({ uuid, currentStatus, version
onError: (e: any) => toast.error(e?.message || 'خطا در تغییر وضعیت'),
});
const meta = STATUS_META[currentStatus] ?? { label: currentStatus, color: '#9ca3af' };
const meta = STATUS_META[currentStatus] ?? { label: currentStatus, color: 'var(--text-3)' };
const nextStatuses = TRANSITIONS[currentStatus] ?? [];
function handleToggle() {
@@ -142,7 +142,7 @@ export default function AppointmentStatusDropdown({ uuid, currentStatus, version
}}
>
{nextStatuses.map(s => {
const sm = STATUS_META[s] ?? { label: s, color: '#9ca3af' };
const sm = STATUS_META[s] ?? { label: s, color: 'var(--text-3)' };
return (
<button
key={s}
@@ -191,7 +191,7 @@ export default function PersianCalendar({ value, onChange, onClose, enableYearPi
border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
fontWeight: sel || tod ? 700 : 400,
background: sel ? 'var(--primary)' : tod ? 'var(--surface-2)' : 'transparent',
color: sel ? '#fff' : tod ? 'var(--text)' : 'var(--text)',
color: sel ? 'var(--on-primary)' : tod ? 'var(--text)' : 'var(--text)',
transition: 'background 0.1s',
}}
onMouseEnter={e => { if (!sel && !tod) (e.currentTarget as HTMLButtonElement).style.background = 'var(--surface-2)'; }}
@@ -216,7 +216,7 @@ export default function PersianCalendar({ value, onChange, onClose, enableYearPi
key={m}
type="button"
onClick={() => { setViewMonth(m); setMode('day'); }}
style={{ ...gridItemStyle, background: active ? 'var(--primary)' : 'transparent', color: active ? '#fff' : 'var(--text)' }}
style={{ ...gridItemStyle, background: active ? 'var(--primary)' : 'transparent', color: active ? 'var(--on-primary)' : 'var(--text)' }}
onMouseEnter={e => { if (!active) (e.currentTarget as HTMLButtonElement).style.background = 'var(--surface-2)'; }}
onMouseLeave={e => { if (!active) (e.currentTarget as HTMLButtonElement).style.background = 'transparent'; }}
>
@@ -237,7 +237,7 @@ export default function PersianCalendar({ value, onChange, onClose, enableYearPi
key={y}
type="button"
onClick={() => { setViewYear(y); setMode('month'); }}
style={{ ...gridItemStyle, background: active ? 'var(--primary)' : 'transparent', color: active ? '#fff' : 'var(--text)' }}
style={{ ...gridItemStyle, background: active ? 'var(--primary)' : 'transparent', color: active ? 'var(--on-primary)' : 'var(--text)' }}
onMouseEnter={e => { if (!active) (e.currentTarget as HTMLButtonElement).style.background = 'var(--surface-2)'; }}
onMouseLeave={e => { if (!active) (e.currentTarget as HTMLButtonElement).style.background = 'transparent'; }}
>
+1 -1
View File
@@ -33,7 +33,7 @@ export default function StatCard({ tone, label, value, icon }: Props) {
<div
style={{
width: 44, height: 44, borderRadius: 12, flexShrink: 0,
background: c.fg, color: '#fff',
background: c.fg, color: 'var(--on-primary)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}
>
@@ -23,7 +23,7 @@ describe('STATUS_META', () => {
it.each(BACKEND_STATUSES)('has a Persian label and colour for %s', (status) => {
expect(STATUS_META[status]).toBeDefined();
expect(STATUS_META[status].label.trim()).not.toBe('');
expect(STATUS_META[status].color).toMatch(/^#[0-9a-f]{6}$/i);
expect(STATUS_META[status].color).toMatch(/^var\(--[a-z0-9-]+\)$/);
});
it('carries no label that is still English', () => {