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:
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { avatarGradient } from '../lib/avatarColors';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
@@ -84,18 +85,13 @@ const DEGREE_OPTIONS = [
|
||||
{ value: 'subspecialistplus', label: 'فلوشیپ' },
|
||||
];
|
||||
const GENDER_OPTIONS = [{ value: 'man', label: 'مرد' }, { value: 'woman', label: 'زن' }];
|
||||
const AVATAR_COLORS = [
|
||||
'from-blue-500 to-cyan-500', 'from-violet-500 to-purple-600',
|
||||
'from-emerald-500 to-teal-600', 'from-rose-500 to-pink-600',
|
||||
'from-amber-500 to-orange-600',
|
||||
];
|
||||
const SPECIALTY_COLORS = [
|
||||
'bg-[var(--danger-bg)] dark:bg-red-400/10 text-[var(--danger)] dark:text-red-300',
|
||||
'bg-[var(--info-bg)] dark:bg-blue-400/10 text-[var(--info)] dark:text-blue-300',
|
||||
'bg-emerald-100 dark:bg-emerald-400/10 text-emerald-700 dark:text-emerald-300',
|
||||
'bg-violet-100 dark:bg-violet-400/10 text-violet-700 dark:text-violet-300',
|
||||
'bg-orange-100 dark:bg-orange-400/10 text-orange-700 dark:text-orange-300',
|
||||
'bg-pink-100 dark:bg-pink-400/10 text-pink-700 dark:text-pink-300',
|
||||
'bg-[var(--danger-bg)] text-[var(--danger)]',
|
||||
'bg-[var(--info-bg)] text-[var(--info)]',
|
||||
'bg-[var(--success-bg)] text-[var(--success)]',
|
||||
'bg-[var(--violet-bg)] text-[var(--violet)]',
|
||||
'bg-[var(--accent-bg)] text-[var(--accent)]',
|
||||
'bg-[var(--violet-bg)] text-[var(--violet)]',
|
||||
];
|
||||
|
||||
// ── Image upload ───────────────────────────────────────────────────────────
|
||||
@@ -135,8 +131,8 @@ function DoctorAvatar({ name, img, idx, onUpload, uploading }: {
|
||||
onClick={() => onUpload && ref.current?.click()}
|
||||
>
|
||||
{img
|
||||
? <img src={img} alt={name} className="w-24 h-24 rounded-2xl object-cover shadow-xl ring-4 ring-white dark:ring-gray-900" />
|
||||
: <div className={`w-24 h-24 rounded-2xl bg-gradient-to-br ${AVATAR_COLORS[idx % AVATAR_COLORS.length]} flex items-center justify-center text-[var(--on-primary)] text-3xl font-bold shadow-xl ring-4 ring-white dark:ring-gray-900`}>{initials}</div>
|
||||
? <img src={img} alt={name} className="w-24 h-24 rounded-2xl object-cover shadow-xl ring-4 ring-[var(--surface)]" />
|
||||
: <div className={`w-24 h-24 rounded-2xl bg-gradient-to-br ${avatarGradient(idx)} flex items-center justify-center text-[var(--on-primary)] text-3xl font-bold shadow-xl ring-4 ring-[var(--surface)]`}>{initials}</div>
|
||||
}
|
||||
{onUpload && (
|
||||
<div className="absolute inset-0 rounded-2xl bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
||||
@@ -161,13 +157,13 @@ function InfoCard({ icon: Icon, label, value, mono = false, copyable = false }:
|
||||
mono?: boolean; copyable?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-[var(--surface-2)] dark:bg-[var(--surface)]/60 hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)] transition-colors group">
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-[var(--surface-2)] hover:bg-[var(--surface-2)] transition-colors group">
|
||||
<div className="w-9 h-9 rounded-lg bg-[var(--surface)] shadow-sm flex items-center justify-center shrink-0">
|
||||
<Icon className="w-4 h-4 text-[var(--text-2)]" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[11px] font-medium text-[var(--text-3)] uppercase tracking-wide mb-0.5">{label}</p>
|
||||
<p className={`text-sm font-medium text-[var(--text)] dark:text-[var(--text)] ${mono ? 'font-mono' : ''}`} dir={mono ? 'ltr' : undefined}>
|
||||
<p className={`text-sm font-medium text-[var(--text)] ${mono ? 'font-mono' : ''}`} dir={mono ? 'ltr' : undefined}>
|
||||
{value ?? <span className="text-[var(--text-3)] font-normal">ثبت نشده</span>}
|
||||
</p>
|
||||
</div>
|
||||
@@ -186,7 +182,7 @@ function StarRating({ rate }: { rate: number }) {
|
||||
<div className="flex items-center gap-1" dir="ltr">
|
||||
{Array.from({ length: 5 }).map((_, i) =>
|
||||
i < Math.round(rate)
|
||||
? <StarSolid key={i} className="w-4 h-4 text-amber-400" />
|
||||
? <StarSolid key={i} className="w-4 h-4 text-[var(--warning)]" />
|
||||
: <StarIcon key={i} className="w-4 h-4 text-[var(--text-3)]" />
|
||||
)}
|
||||
<span className="text-sm font-medium text-[var(--text-2)] mr-1">{rate.toFixed(1)}</span>
|
||||
@@ -306,7 +302,7 @@ function SearchableSelect({ options, value, onChange, placeholder, disabled = fa
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={openDropdown}
|
||||
className={`cp-input h-11 flex items-center justify-between text-right w-full ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'} ${!selected ? 'text-[var(--text-3)] dark:text-[var(--text-2)]' : ''}`}
|
||||
className={`cp-input h-11 flex items-center justify-between text-right w-full ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'} ${!selected ? 'text-[var(--text-3)]' : ''}`}
|
||||
>
|
||||
<span className="truncate flex-1">{selected?.label ?? placeholder ?? 'انتخاب کنید'}</span>
|
||||
<ChevronDownIcon className={`w-4 h-4 shrink-0 mr-2 transition-transform ${open ? 'rotate-180' : ''}`} />
|
||||
@@ -328,7 +324,7 @@ function SearchableSelect({ options, value, onChange, placeholder, disabled = fa
|
||||
{filtered.map(o => (
|
||||
<button key={o.value} type="button"
|
||||
onClick={() => { onChange(o.value, o.label); setOpen(false); setQ(''); }}
|
||||
className={`w-full text-right px-3 py-2 text-sm hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)] ${value === o.value ? 'text-primary-600 dark:text-primary-400 font-medium bg-primary-50 dark:bg-primary-500/10' : 'text-[var(--text)] dark:text-[var(--text-3)]'}`}>
|
||||
className={`w-full text-right px-3 py-2 text-sm hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)] ${value === o.value ? 'text-[var(--primary)] dark:text-[var(--primary)] font-medium bg-[var(--primary-soft)] dark:bg-[color-mix(in_srgb,var(--primary)_10%,transparent)]' : 'text-[var(--text)]'}`}>
|
||||
{o.label}
|
||||
</button>
|
||||
))}
|
||||
@@ -380,16 +376,16 @@ function HierarchicalSpecialtyPicker({ selected, onChange, specialties }: {
|
||||
|
||||
return (
|
||||
<div className="border border-[var(--border)] rounded-xl overflow-hidden">
|
||||
<div className="p-2 border-b border-[var(--border)] bg-[var(--surface-2)] dark:bg-[var(--surface)]/60">
|
||||
<div className="p-2 border-b border-[var(--border)] bg-[var(--surface-2)]">
|
||||
<input type="text" value={q} onChange={e => setQ(e.target.value)} placeholder="جستجوی تخصص..."
|
||||
className="cp-input text-sm h-8" />
|
||||
</div>
|
||||
{selectedLabels.length > 0 && (
|
||||
<div className="px-3 py-2 border-b border-[var(--border)] flex flex-wrap gap-1">
|
||||
{selectedLabels.map(({ id, name }) => (
|
||||
<span key={id} className="inline-flex items-center gap-1 text-xs px-2.5 py-0.5 rounded-full bg-primary-100 dark:bg-primary-500/20 text-primary-700 dark:text-primary-300">
|
||||
<span key={id} className="inline-flex items-center gap-1 text-xs px-2.5 py-0.5 rounded-full bg-[var(--primary-soft2)] dark:bg-[color-mix(in_srgb,var(--primary)_20%,transparent)] text-[var(--primary-700)] dark:text-[var(--primary)]">
|
||||
{name}
|
||||
<button type="button" onClick={() => toggleSelect(id)} className="hover:text-primary-900 dark:hover:text-primary-100 leading-none">×</button>
|
||||
<button type="button" onClick={() => toggleSelect(id)} className="hover:text-[var(--primary-700)] dark:hover:text-[var(--primary-soft2)] leading-none">×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -400,7 +396,7 @@ function HierarchicalSpecialtyPicker({ selected, onChange, specialties }: {
|
||||
? filteredFlat.map(s => (
|
||||
<label key={s.id} className="flex items-center gap-2.5 px-3 py-2 hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)] cursor-pointer">
|
||||
<input type="checkbox" checked={selected.includes(s.id)} onChange={() => toggleSelect(s.id)}
|
||||
className="rounded border-[var(--border-2)] dark:border-gray-600 text-primary-600 shrink-0" />
|
||||
className="rounded border-[var(--border-2)] text-[var(--primary)] shrink-0" />
|
||||
<span className="text-sm text-[var(--text)]">{s.name}</span>
|
||||
{s.parent_id !== null && (
|
||||
<span className="text-[10px] text-[var(--text-3)] mr-auto truncate max-w-[100px]">
|
||||
@@ -424,13 +420,13 @@ function HierarchicalSpecialtyPicker({ selected, onChange, specialties }: {
|
||||
? <ChevronDownIcon className="w-4 h-4 text-[var(--text-3)] shrink-0" />
|
||||
: <ChevronRightIcon className="w-4 h-4 text-[var(--text-3)] shrink-0" />)
|
||||
: <input type="checkbox" checked={selected.includes(parent.id)} readOnly
|
||||
className="rounded border-[var(--border-2)] dark:border-gray-600 text-primary-600 pointer-events-none shrink-0" />
|
||||
className="rounded border-[var(--border-2)] text-[var(--primary)] pointer-events-none shrink-0" />
|
||||
}
|
||||
<span className={`text-sm font-medium ${numSel > 0 ? 'text-primary-600 dark:text-primary-400' : 'text-[var(--text)] dark:text-[var(--text-3)]'}`}>
|
||||
<span className={`text-sm font-medium ${numSel > 0 ? 'text-[var(--primary)] dark:text-[var(--primary)]' : 'text-[var(--text)]'}`}>
|
||||
{parent.name}
|
||||
</span>
|
||||
{numSel > 0 && (
|
||||
<span className="mr-auto text-[10px] bg-primary-100 dark:bg-primary-500/20 text-primary-700 dark:text-primary-300 px-1.5 py-0.5 rounded-full">
|
||||
<span className="mr-auto text-[10px] bg-[var(--primary-soft2)] dark:bg-[color-mix(in_srgb,var(--primary)_20%,transparent)] text-[var(--primary-700)] dark:text-[var(--primary)] px-1.5 py-0.5 rounded-full">
|
||||
{numSel} انتخاب
|
||||
</span>
|
||||
)}
|
||||
@@ -438,7 +434,7 @@ function HierarchicalSpecialtyPicker({ selected, onChange, specialties }: {
|
||||
{isOpen && children.map(child => (
|
||||
<label key={child.id} className="flex items-center gap-2.5 px-3 py-2 pr-9 hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)] cursor-pointer">
|
||||
<input type="checkbox" checked={selected.includes(child.id)} onChange={() => toggleSelect(child.id)}
|
||||
className="rounded border-[var(--border-2)] dark:border-gray-600 text-primary-600 shrink-0" />
|
||||
className="rounded border-[var(--border-2)] text-[var(--primary)] shrink-0" />
|
||||
<span className="text-sm text-[var(--text-2)]">{child.name}</span>
|
||||
</label>
|
||||
))}
|
||||
@@ -487,7 +483,7 @@ function ServicesPicker({ selected, onChange, services, specialties, selectedSpe
|
||||
|
||||
if (selectedSpecialtyIds.length === 0) {
|
||||
return (
|
||||
<div className="rounded-xl border border-dashed border-[var(--border-2)] dark:border-gray-600 py-8 text-center text-[var(--text-3)] text-sm">
|
||||
<div className="rounded-xl border border-dashed border-[var(--border-2)] py-8 text-center text-[var(--text-3)] text-sm">
|
||||
ابتدا تخصصهایی را انتخاب کنید تا خدمات نمایش داده شوند
|
||||
</div>
|
||||
);
|
||||
@@ -495,7 +491,7 @@ function ServicesPicker({ selected, onChange, services, specialties, selectedSpe
|
||||
|
||||
if (relevantServices.length === 0) {
|
||||
return (
|
||||
<div className="rounded-xl border border-dashed border-[var(--border-2)] dark:border-gray-600 py-8 text-center text-[var(--text-3)] text-sm">
|
||||
<div className="rounded-xl border border-dashed border-[var(--border-2)] py-8 text-center text-[var(--text-3)] text-sm">
|
||||
خدمتی برای تخصصهای انتخابشده یافت نشد
|
||||
</div>
|
||||
);
|
||||
@@ -504,11 +500,11 @@ function ServicesPicker({ selected, onChange, services, specialties, selectedSpe
|
||||
return (
|
||||
<div className="border border-[var(--border)] rounded-xl overflow-hidden">
|
||||
{tabs.length > 1 && (
|
||||
<div className="flex gap-1 px-2 py-2 border-b border-[var(--border)] bg-[var(--surface-2)] dark:bg-[var(--surface)]/60 overflow-x-auto">
|
||||
<div className="flex gap-1 px-2 py-2 border-b border-[var(--border)] bg-[var(--surface-2)] overflow-x-auto">
|
||||
{[{ id: 'all' as const, name: 'همه' }, ...tabs].map(t => (
|
||||
<button key={String(t.id)} type="button"
|
||||
onClick={() => setActiveTab(t.id as number | 'all')}
|
||||
className={`px-2.5 py-1 rounded-lg text-xs font-medium whitespace-nowrap transition-colors ${activeTab === t.id ? 'bg-primary-600 text-[var(--on-primary)]' : 'text-[var(--text-2)] dark:text-[var(--text-3)] hover:bg-[var(--surface)] dark:hover:bg-[var(--surface-2)]'}`}>
|
||||
className={`px-2.5 py-1 rounded-lg text-xs font-medium whitespace-nowrap transition-colors ${activeTab === t.id ? 'bg-[var(--primary)] text-[var(--on-primary)]' : 'text-[var(--text-2)] hover:bg-[var(--surface)] dark:hover:bg-[var(--surface-2)]'}`}>
|
||||
{t.name}
|
||||
</button>
|
||||
))}
|
||||
@@ -521,9 +517,9 @@ function ServicesPicker({ selected, onChange, services, specialties, selectedSpe
|
||||
{selectedLabels.length > 0 && (
|
||||
<div className="px-3 py-2 border-b border-[var(--border)] flex flex-wrap gap-1">
|
||||
{selectedLabels.map(({ id, name }) => (
|
||||
<span key={id} className="inline-flex items-center gap-1 text-xs px-2.5 py-0.5 rounded-full bg-emerald-100 dark:bg-emerald-500/20 text-emerald-700 dark:text-emerald-300">
|
||||
<span key={id} className="inline-flex items-center gap-1 text-xs px-2.5 py-0.5 rounded-full bg-[var(--success-bg)] text-[var(--success)]">
|
||||
{name}
|
||||
<button type="button" onClick={() => toggle(id)} className="hover:text-emerald-900 dark:hover:text-emerald-100 leading-none">×</button>
|
||||
<button type="button" onClick={() => toggle(id)} className="hover:text-[var(--success)] dark:hover:text-[var(--success)] leading-none">×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -532,7 +528,7 @@ function ServicesPicker({ selected, onChange, services, specialties, selectedSpe
|
||||
{visibleServices.map(svc => (
|
||||
<label key={svc.id} className="flex items-center gap-2.5 px-3 py-2 hover:bg-[var(--surface-2)] dark:hover:bg-[var(--surface)] cursor-pointer">
|
||||
<input type="checkbox" checked={selected.includes(svc.id)} onChange={() => toggle(svc.id)}
|
||||
className="rounded border-[var(--border-2)] dark:border-gray-600 text-[var(--success)] shrink-0" />
|
||||
className="rounded border-[var(--border-2)] text-[var(--success)] shrink-0" />
|
||||
<span className="text-sm text-[var(--text)]">{svc.name}</span>
|
||||
</label>
|
||||
))}
|
||||
@@ -741,7 +737,7 @@ function AddressCard({ addr, onEdit, onDelete, readOnly = false }: {
|
||||
addr: AddressData; onEdit: () => void; onDelete: () => void; readOnly?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-[var(--surface-2)] dark:bg-[var(--surface)]/60 border border-[var(--border)] group">
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-[var(--surface-2)] border border-[var(--border)] group">
|
||||
<div className="w-9 h-9 rounded-lg bg-[var(--surface)] shadow-sm flex items-center justify-center shrink-0 mt-0.5">
|
||||
<MapPinIcon className="w-4 h-4 text-[var(--text-3)]" />
|
||||
</div>
|
||||
@@ -769,7 +765,7 @@ function AddressCard({ addr, onEdit, onDelete, readOnly = false }: {
|
||||
{!readOnly && (
|
||||
<div className="opacity-0 group-hover:opacity-100 flex items-center gap-1 transition-opacity shrink-0">
|
||||
<button onClick={onEdit}
|
||||
className="w-7 h-7 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-7 h-7 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={onDelete}
|
||||
@@ -1362,7 +1358,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
if (isError || !doctor) {
|
||||
return (
|
||||
<div className="animate-slide-up cp-card p-16 flex flex-col items-center gap-4 text-center">
|
||||
<div className="w-16 h-16 rounded-2xl bg-[var(--danger-bg)] dark:bg-red-400/10 flex items-center justify-center">
|
||||
<div className="w-16 h-16 rounded-2xl bg-[var(--danger-bg)] flex items-center justify-center">
|
||||
<XCircleIcon className="w-8 h-8 text-[var(--danger)]" />
|
||||
</div>
|
||||
<p className="font-semibold text-[var(--text)]">پزشک یافت نشد</p>
|
||||
@@ -1407,7 +1403,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
|
||||
{/* Profile Card */}
|
||||
<div className="cp-card overflow-hidden">
|
||||
<div className="h-28 bg-gradient-to-l from-blue-600/20 via-blue-500/10 to-transparent dark:from-blue-500/20" />
|
||||
<div className="h-28 bg-gradient-to-l from-[color-mix(in_srgb,var(--info)_20%,transparent)] via-[color-mix(in_srgb,var(--info)_10%,transparent)] to-transparent dark:from-[color-mix(in_srgb,var(--info)_20%,transparent)]" />
|
||||
<div className="px-6 pb-6 -mt-12 flex flex-col sm:flex-row sm:items-end gap-4">
|
||||
|
||||
<div className="flex flex-col items-center gap-1.5">
|
||||
@@ -1427,11 +1423,11 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0 sm:mb-1">
|
||||
<h1 className="text-xl font-bold text-[var(--text)] dark:text-slate-50">{displayDoctorName(doctor.name)}</h1>
|
||||
<h1 className="text-xl font-bold text-[var(--text)]">{displayDoctorName(doctor.name)}</h1>
|
||||
<div className="flex items-center gap-2 mt-1.5 flex-wrap">
|
||||
{/* Active status badge */}
|
||||
{doctor.active
|
||||
? <span className="inline-flex items-center gap-1 text-xs font-medium px-2.5 py-0.5 rounded-full bg-emerald-100 dark:bg-emerald-400/10 text-emerald-700 dark:text-emerald-300">
|
||||
? <span className="inline-flex items-center gap-1 text-xs font-medium px-2.5 py-0.5 rounded-full bg-[var(--success-bg)] text-[var(--success)]">
|
||||
<CheckCircleIcon className="w-3 h-3" />فعال
|
||||
</span>
|
||||
: <span className="inline-flex items-center gap-1 text-xs font-medium px-2.5 py-0.5 rounded-full bg-[var(--surface-2)] text-[var(--text-2)]">
|
||||
@@ -1439,7 +1435,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
</span>
|
||||
}
|
||||
{doctor.degree && (
|
||||
<span className="inline-flex text-xs font-medium px-2.5 py-0.5 rounded-full bg-[var(--info-bg)] dark:bg-blue-400/10 text-[var(--info)] dark:text-blue-300">{degreeLabel}</span>
|
||||
<span className="inline-flex text-xs font-medium px-2.5 py-0.5 rounded-full bg-[var(--info-bg)] text-[var(--info)]">{degreeLabel}</span>
|
||||
)}
|
||||
{doctor.gender && (
|
||||
<span className="text-xs text-[var(--text-2)]">{doctor.gender === 'man' ? '♂ مرد' : '♀ زن'}</span>
|
||||
@@ -1455,7 +1451,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
{doctor.expertise.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{doctor.expertise.map(e => (
|
||||
<span key={e.id} className="text-[11px] px-2 py-0.5 rounded-full bg-[var(--surface-2)] text-[var(--text-2)] border border-[var(--border)] dark:border-gray-600">{e.name}</span>
|
||||
<span key={e.id} className="text-[11px] px-2 py-0.5 rounded-full bg-[var(--surface-2)] text-[var(--text-2)] border border-[var(--border)]">{e.name}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -1476,8 +1472,8 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
disabled={toggleMut.isPending}
|
||||
className={`inline-flex items-center gap-1.5 text-sm font-medium px-3 h-10 rounded-xl border transition-colors disabled:opacity-50 ${
|
||||
doctor.active
|
||||
? 'border-orange-300 dark:border-orange-500/40 text-orange-600 dark:text-orange-400 bg-orange-50 dark:bg-orange-500/10 hover:bg-orange-100 dark:hover:bg-orange-500/20'
|
||||
: 'border-emerald-300 dark:border-emerald-500/40 text-[var(--success)] dark:text-emerald-400 bg-[var(--success-bg)] dark:bg-emerald-500/10 hover:bg-emerald-100 dark:hover:bg-emerald-500/20'
|
||||
? 'border-[var(--accent)] text-[var(--accent)] bg-[var(--accent-bg)] hover:bg-[var(--accent-bg)]'
|
||||
: 'border-[var(--success)] text-[var(--success)] bg-[var(--success-bg)] hover:bg-[var(--success-bg)]'
|
||||
}`}
|
||||
>
|
||||
{toggleMut.isPending
|
||||
@@ -1497,7 +1493,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
{menuOpen && (
|
||||
<div className="absolute left-0 top-12 z-30 w-52 cp-card shadow-2xl border border-[var(--border)] py-1.5 animate-scale-in" data-dmenu>
|
||||
<button onClick={() => { setDeleteOpen(true); setMenuOpen(false); }}
|
||||
className="w-full flex items-center gap-2.5 px-3 py-2 text-sm text-[var(--danger)] dark:text-red-400 hover:bg-[var(--danger-bg)] dark:hover:bg-[var(--danger)]/10">
|
||||
className="w-full flex items-center gap-2.5 px-3 py-2 text-sm text-[var(--danger)] hover:bg-[var(--danger-bg)] dark:hover:bg-[var(--danger)]/10">
|
||||
<TrashIcon className="w-4 h-4 shrink-0" />حذف این پزشک
|
||||
</button>
|
||||
</div>
|
||||
@@ -1556,7 +1552,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
</h2>
|
||||
{!isReadOnly && (
|
||||
<button onClick={() => { setEditingAddr(null); setAddrModalOpen(true); }}
|
||||
className="flex items-center gap-1.5 text-xs font-medium text-primary-600 dark:text-primary-400 hover:underline">
|
||||
className="flex items-center gap-1.5 text-xs font-medium text-[var(--primary)] dark:text-[var(--primary)] hover:underline">
|
||||
<PlusIcon className="w-4 h-4" />افزودن آدرس
|
||||
</button>
|
||||
)}
|
||||
@@ -1576,7 +1572,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
<p className="text-sm">هنوز آدرسی ثبت نشده است</p>
|
||||
{!isReadOnly && (
|
||||
<button onClick={() => { setEditingAddr(null); setAddrModalOpen(true); }}
|
||||
className="mt-3 text-xs text-primary-600 dark:text-primary-400 hover:underline">
|
||||
className="mt-3 text-xs text-[var(--primary)] dark:text-[var(--primary)] hover:underline">
|
||||
اولین آدرس را اضافه کنید
|
||||
</button>
|
||||
)}
|
||||
@@ -1595,7 +1591,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
</h2>
|
||||
<div className="space-y-2">
|
||||
{doctor.clinics.map(c => (
|
||||
<div key={c.uuid} className="flex items-start gap-3 p-3 rounded-xl bg-[var(--surface-2)] dark:bg-[var(--surface)]/60">
|
||||
<div key={c.uuid} className="flex items-start gap-3 p-3 rounded-xl bg-[var(--surface-2)]">
|
||||
<div className="w-8 h-8 rounded-lg bg-[var(--surface)] shadow-sm flex items-center justify-center shrink-0">
|
||||
<BuildingOfficeIcon className="w-4 h-4 text-[var(--text-3)]" />
|
||||
</div>
|
||||
@@ -1625,7 +1621,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
: <p className="text-sm text-[var(--text-3)]">تخصصی ثبت نشده</p>
|
||||
}
|
||||
{!isReadOnly && (
|
||||
<button onClick={() => setEditOpen(true)} className="w-full mt-3 text-xs text-primary-600 dark:text-primary-400 hover:underline text-right">
|
||||
<button onClick={() => setEditOpen(true)} className="w-full mt-3 text-xs text-[var(--primary)] dark:text-[var(--primary)] hover:underline text-right">
|
||||
ویرایش تخصصها ←
|
||||
</button>
|
||||
)}
|
||||
@@ -1644,7 +1640,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
: <p className="text-sm text-[var(--text-3)]">خدمتی ثبت نشده</p>
|
||||
}
|
||||
{!isReadOnly && (
|
||||
<button onClick={() => setEditOpen(true)} className="w-full mt-3 text-xs text-primary-600 dark:text-primary-400 hover:underline text-right">
|
||||
<button onClick={() => setEditOpen(true)} className="w-full mt-3 text-xs text-[var(--primary)] dark:text-[var(--primary)] hover:underline text-right">
|
||||
ویرایش خدمات ←
|
||||
</button>
|
||||
)}
|
||||
@@ -1661,7 +1657,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
<p className="text-xs text-[var(--text-2)] mb-1">درصد رضایت</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 h-2 rounded-full bg-[var(--surface-3)]">
|
||||
<div className="h-full rounded-full bg-amber-400 transition-all" style={{ width: `${doctor.satisfaction}%` }} />
|
||||
<div className="h-full rounded-full bg-[var(--warning)] transition-all" style={{ width: `${doctor.satisfaction}%` }} />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-[var(--text)]">{doctor.satisfaction}%</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user