refactor: update UserDetailPage styles and introduce avatar gradient utility
- Refactored role metadata styles in UserDetailPage for consistency with design tokens. - Replaced hardcoded avatar colors with a utility function to generate gradients based on user ID. - Improved InfoCard component styles for better hover effects and accessibility. - Removed deprecated color classes and adjusted background gradients for various components. - Updated theme token tests to reflect the removal of deferred files and ensure compliance with design standards. - Added new avatarColors utility file to manage avatar gradient definitions.
This commit is contained in:
@@ -169,7 +169,7 @@ 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-[var(--text)] dark:text-[var(--text)]' : 'text-[var(--text-3)] dark:text-[var(--text-2)]'}>
|
||||
<span className={value ? 'text-[var(--text)]' : 'text-[var(--text-3)]'}>
|
||||
{value ? formatPersianDate(value) : (placeholder ?? 'انتخاب تاریخ')}
|
||||
</span>
|
||||
<CalendarIcon className="w-4 h-4 text-[var(--text-3)] shrink-0 mr-2" />
|
||||
@@ -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-[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-[var(--text)] dark:text-[var(--text-3)] hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)]'}`}>
|
||||
${dis ? 'text-[var(--text-3)] cursor-not-allowed'
|
||||
: sel ? 'bg-[var(--primary)] text-[var(--on-primary)] font-semibold shadow-sm'
|
||||
: tod ? 'ring-1 ring-[var(--primary)] text-[var(--primary-700)] dark:text-[var(--primary)] font-medium'
|
||||
: 'text-[var(--text)] hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)]'}`}>
|
||||
{toPersianNums(jd)}
|
||||
</button>
|
||||
);
|
||||
@@ -352,7 +352,7 @@ function SlotEditor({ slots, onChange }: {
|
||||
</div>
|
||||
))}
|
||||
<button type="button" onClick={add}
|
||||
className="flex items-center gap-1.5 text-xs text-primary-600 dark:text-primary-400 hover:underline mt-1">
|
||||
className="flex items-center gap-1.5 text-xs text-[var(--primary)] dark:text-[var(--primary)] hover:underline mt-1">
|
||||
<PlusIcon className="w-3.5 h-3.5" />افزودن بازه زمانی
|
||||
</button>
|
||||
</div>
|
||||
@@ -511,7 +511,7 @@ 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-[var(--warning-bg)] dark:bg-amber-500/10 border border-[var(--warning)] dark:border-amber-500/30 flex items-center justify-center">
|
||||
<div className="w-12 h-12 rounded-full bg-[var(--warning-bg)] border border-[var(--warning)] flex items-center justify-center">
|
||||
<MapPinIcon className="w-6 h-6 text-[var(--warning)]" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -627,9 +627,9 @@ 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-[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">
|
||||
<div className="flex items-center gap-2 mb-3 p-3 rounded-xl bg-[var(--success-bg)] border border-[var(--success)]">
|
||||
<CalendarIcon className="w-4 h-4 text-[var(--success)] shrink-0" />
|
||||
<span className="text-sm text-[var(--success)]">
|
||||
مجموع <strong>{totalSlots}</strong> نوبت در هفته
|
||||
</span>
|
||||
</div>
|
||||
@@ -638,7 +638,7 @@ 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-[var(--border)] bg-[var(--surface)] dark:bg-[var(--surface)]/40">
|
||||
<div key={day.key} className="flex items-center gap-3 px-4 py-3 rounded-xl border border-[var(--border)] bg-[var(--surface)]">
|
||||
<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) => (
|
||||
@@ -665,9 +665,9 @@ 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-[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">
|
||||
<div className="flex items-center gap-2 mb-3 p-3 rounded-xl bg-[var(--success-bg)] border border-[var(--success)]">
|
||||
<CalendarIcon className="w-4 h-4 text-[var(--success)] shrink-0" />
|
||||
<span className="text-sm text-[var(--success)]">
|
||||
مجموع <strong>{totalSlots}</strong> نوبت در هفته
|
||||
</span>
|
||||
</div>
|
||||
@@ -675,7 +675,7 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
|
||||
|
||||
{/* ─ روش نوبتدهی */}
|
||||
<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">
|
||||
<div className="px-4 py-3 bg-[var(--surface-2)]">
|
||||
<span className="text-sm font-medium text-[var(--text)]">روش نوبتدهی</span>
|
||||
</div>
|
||||
<div className="px-4 py-3 space-y-3">
|
||||
@@ -694,11 +694,11 @@ 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-[var(--border)] dark:border-[var(--border)] bg-[var(--surface)] dark:bg-[var(--bg)]'
|
||||
: 'border-[var(--border)] bg-[var(--surface)]'
|
||||
} ${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-[var(--border-2)] dark:border-gray-600'}`} />
|
||||
<span className={`w-3.5 h-3.5 rounded-full border shrink-0 ${selected ? 'border-[var(--primary)] bg-[var(--primary)]' : 'border-[var(--border-2)]'}`} />
|
||||
<span className="text-sm font-medium text-[var(--text)]">{lbl}</span>
|
||||
</div>
|
||||
<p className="text-xs text-[var(--text-2)] leading-relaxed">{desc}</p>
|
||||
@@ -712,7 +712,7 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
|
||||
نوع نوبتدهی ثبت شده و دیگر قابل تغییر نیست.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xs text-[var(--warning)] dark:text-amber-400 leading-relaxed">
|
||||
<p className="text-xs text-[var(--warning)] leading-relaxed">
|
||||
⚠️ توجه: نوع نوبتدهی پس از اولین ثبت <span className="font-medium">بههیچعنوان قابل تغییر نیست</span>. پیش از ذخیره با دقت انتخاب کنید.
|
||||
</p>
|
||||
)}
|
||||
@@ -745,7 +745,7 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
|
||||
{/* ─ نوبتدهی آنلاین */}
|
||||
<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-[var(--surface-2)] dark:bg-[var(--surface)]/50">
|
||||
<label className="flex items-center justify-between gap-3 px-4 py-3 cursor-pointer bg-[var(--surface-2)]">
|
||||
<div className="flex items-center gap-2">
|
||||
<GlobeAltIcon className="w-4 h-4 text-[var(--text-2)] shrink-0" />
|
||||
<span className="text-sm font-medium text-[var(--text)]">نوبتدهی آنلاین</span>
|
||||
@@ -757,7 +757,7 @@ export function WeeklyScheduleTab({ doctorUuid, clinicUuid, addresses, readOnly
|
||||
checked={meta.online_booking_enabled}
|
||||
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="w-10 h-6 rounded-full bg-[var(--surface-3)] transition-colors peer-checked:bg-[var(--success)]" />
|
||||
<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>
|
||||
@@ -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-[var(--danger)] dark:text-red-400 flex items-center gap-1">
|
||||
<p className="text-xs text-[var(--danger)] flex items-center gap-1">
|
||||
<ExclamationTriangleIcon className="w-3.5 h-3.5" />
|
||||
{hasAnyOverlap ? 'تداخل زمانی در برنامه وجود دارد' : 'مکان مطب برای همه بازهها الزامی است'}
|
||||
</p>
|
||||
@@ -966,7 +966,7 @@ function DateOverrideModal({ open, onClose, existing, doctorUuid, clinicUuid, on
|
||||
<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-[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)]'}`}>
|
||||
className={`flex items-center gap-2 p-3 rounded-xl border-2 text-sm transition-colors text-right ${overrideType === 'closed' ? 'border-[var(--danger)] bg-[var(--danger-bg)] text-[var(--danger)]' : 'border-[var(--border)] text-[var(--text-2)] 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-[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)]'}`}>
|
||||
className={`flex items-center gap-2 p-3 rounded-xl border-2 text-sm transition-colors text-right ${overrideType === 'custom' ? 'border-[var(--warning)] bg-[var(--warning-bg)] text-[var(--warning)]' : 'border-[var(--border)] text-[var(--text-2)] hover:border-[var(--border-2)]'}`}>
|
||||
<CalendarIcon className="w-5 h-5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium leading-tight">ساعات خاص</p>
|
||||
@@ -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-[var(--danger-bg)] dark:bg-[var(--danger)]/10 border border-[var(--danger)] dark:border-red-500/30">
|
||||
<div className="flex items-start gap-2 p-3 rounded-xl bg-[var(--danger-bg)] border border-[var(--danger)]">
|
||||
<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 className="text-xs text-[var(--danger)]">
|
||||
در این روز هیچ نوبتی داده نخواهد شد، حتی اگر در برنامه هفتگی ساعات کاری داشته باشید.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1079,16 +1079,16 @@ function DateOverridesTab({ doctorUuid, clinicUuid, addresses, readOnly = false
|
||||
) : (
|
||||
<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-[var(--border)] bg-[var(--surface)] dark:bg-[var(--surface)]/60">
|
||||
<div key={ov.uuid} className="flex items-center gap-3 p-3.5 rounded-xl border border-[var(--border)] bg-[var(--surface)]">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<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-[var(--warning-bg)] 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)] text-[var(--warning)]">
|
||||
{ov.custom_slots.length > 0 ? `${ov.custom_slots.length} بازه سفارشی` : 'ساعات خاص'}
|
||||
</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">
|
||||
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-[var(--danger-bg)] text-[var(--danger)]">
|
||||
تعطیل
|
||||
</span>
|
||||
)}
|
||||
@@ -1098,7 +1098,7 @@ function DateOverridesTab({ doctorUuid, clinicUuid, addresses, readOnly = false
|
||||
{!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-[var(--text-3)] hover:text-[var(--info)] hover:bg-[var(--info-bg)] 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-[var(--info)]/10 transition-colors">
|
||||
<PencilIcon className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button onClick={() => setDeletingUuid(ov.uuid)}
|
||||
@@ -1250,17 +1250,17 @@ 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-[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 key={h.uuid} className={`flex items-center gap-3 p-3.5 rounded-xl border transition-colors ${isCurrent ? 'border-[var(--danger)] bg-[var(--danger-bg)]/40' : isPast ? 'border-[var(--border)] bg-[var(--surface-2)]/40 opacity-70' : 'border-[var(--accent)] bg-[var(--accent-bg)]/30'}`}>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className={`text-sm font-medium ${isPast ? 'text-[var(--text-2)] dark:text-[var(--text-3)]' : 'text-[var(--text)] dark:text-[var(--text)]'}`}>
|
||||
<span className={`text-sm font-medium ${isPast ? 'text-[var(--text-2)]' : '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-[var(--danger-bg)] dark:bg-[var(--danger)]/20 text-[var(--danger)] dark:text-red-400 animate-pulse">در جریان</span>
|
||||
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-[var(--danger-bg)] text-[var(--danger)] 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>
|
||||
<span className="text-xs px-2 py-0.5 rounded-full font-medium bg-[var(--accent-bg)] text-[var(--accent)]">آینده</span>
|
||||
)}
|
||||
{isPast && (
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-[var(--surface-2)] text-[var(--text-3)]">گذشته</span>
|
||||
@@ -1274,11 +1274,11 @@ function HolidaysTab({ doctorUuid, clinicUuid, readOnly = false }: { doctorUuid:
|
||||
{!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-[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'}`}>
|
||||
className={`text-xs px-2.5 h-7 rounded-lg border transition-colors disabled:opacity-50 ${h.active ? 'border-[var(--border)] text-[var(--text-2)] hover:border-[var(--border-2)] hover:text-[var(--text)]' : 'border-[var(--success)] text-[var(--success)] hover:bg-[var(--success-bg)] dark:hover:bg-[var(--success)]/10'}`}>
|
||||
{h.active ? 'غیرفعال' : 'فعال'}
|
||||
</button>
|
||||
<button onClick={() => { setEditing(h); setModalOpen(true); }}
|
||||
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">
|
||||
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-[var(--info)]/10 transition-colors">
|
||||
<PencilIcon className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button onClick={() => setDeletingUuid(h.uuid)}
|
||||
@@ -1329,8 +1329,8 @@ export function ScheduleSection({ doctorUuid, clinicUuid, readOnly = false }: {
|
||||
<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-[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)]'
|
||||
? 'bg-[var(--surface)] text-[var(--text)] shadow-sm'
|
||||
: 'text-[var(--text-2)] hover:text-[var(--text)]'
|
||||
}`}>
|
||||
{t.label}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user