feat: enhance DoctorFormPage with searchable specialties and improved UI components

- Refactored DoctorFormPage to use Controller from react-hook-form for better form handling.
- Added a new Field component for consistent input styling and error handling.
- Implemented a SpecialtyPicker component with improved selection logic for specialties.
- Updated the layout and styling of the form sections for better user experience.
- Integrated SearchableSelect for selecting specialties and roles in DoctorsPage and UsersPage.
- Added createClinic API endpoint to handle clinic creation with validation for mobile and name fields.
This commit is contained in:
hamed
2026-06-12 20:43:47 +03:30
parent 9e8064e101
commit 960ff1ab29
10 changed files with 480 additions and 344 deletions
+38 -40
View File
@@ -17,6 +17,7 @@ interface Props {
isClearable?: boolean;
noOptionsMessage?: string;
inputId?: string;
height?: number;
}
export default function SearchableSelect({
@@ -29,6 +30,7 @@ export default function SearchableSelect({
isClearable,
noOptionsMessage = 'موردی یافت نشد',
inputId,
height = 42,
}: Props) {
const darkMode = useUiStore((s) => s.darkMode);
@@ -40,64 +42,60 @@ export default function SearchableSelect({
const styles: StylesConfig<SelectOption, false, GroupBase<SelectOption>> = {
control: (base, state) => ({
...base,
background: darkMode ? '#111827' : '#ffffff',
borderColor: state.isFocused ? '#7c3aed' : darkMode ? '#4b5563' : '#cbd5e1',
boxShadow: state.isFocused ? '0 0 0 2px rgba(124,58,237,0.25)' : 'none',
borderRadius: '0.75rem',
minHeight: '2.75rem',
fontSize: '0.875rem',
background: darkMode ? 'var(--surface)' : 'var(--surface)',
borderColor: state.isFocused ? 'var(--primary)' : 'var(--border)',
boxShadow: state.isFocused ? '0 0 0 4px var(--ring)' : 'none',
borderRadius: 'var(--r-sm)',
minHeight: height,
fontSize: 14,
cursor: 'pointer',
'&:hover': {
borderColor: state.isFocused ? '#7c3aed' : darkMode ? '#6b7280' : '#94a3b8',
borderColor: state.isFocused ? 'var(--primary)' : 'var(--border-2)',
},
}),
valueContainer: (base) => ({ ...base, padding: '0 14px' }),
menu: (base) => ({
...base,
background: darkMode ? '#1f2937' : '#ffffff',
border: `1px solid ${darkMode ? '#374151' : '#e2e8f0'}`,
boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
borderRadius: '0.75rem',
overflow: 'hidden',
background: 'var(--surface)',
border: '1px solid var(--border)',
boxShadow: 'var(--shadow-lg)',
borderRadius: 'var(--r)',
overflow: 'hidden',
marginTop: 4,
}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (base, state) => ({
...base,
background: state.isSelected
? '#7c3aed'
? 'var(--primary-soft2)'
: state.isFocused
? darkMode ? '#374151' : '#f1f5f9'
? 'var(--surface-2)'
: 'transparent',
color: state.isSelected ? '#ffffff' : darkMode ? '#e5e7eb' : '#1e293b',
fontSize: '0.875rem',
cursor: 'pointer',
color: state.isSelected ? 'var(--primary-700)' : 'var(--text)',
fontWeight: state.isSelected ? 700 : 400,
fontSize: 14,
cursor: 'pointer',
padding: '9px 14px',
}),
singleValue: (base) => ({ ...base, color: darkMode ? '#f3f4f6' : '#1e293b' }),
input: (base) => ({ ...base, color: darkMode ? '#f3f4f6' : '#1e293b' }),
placeholder: (base) => ({ ...base, color: darkMode ? '#6b7280' : '#94a3b8' }),
indicatorSeparator: (base) => ({
singleValue: (base) => ({ ...base, color: 'var(--text)' }),
input: (base) => ({ ...base, color: 'var(--text)', margin: 0, padding: 0 }),
placeholder: (base) => ({ ...base, color: 'var(--text-3)' }),
indicatorSeparator: (base) => ({ ...base, background: 'var(--border)' }),
dropdownIndicator: (base) => ({
...base,
background: darkMode ? '#374151' : '#e2e8f0',
}),
dropdownIndicator: (base) => ({
...base,
color: darkMode ? '#6b7280' : '#94a3b8',
'&:hover': { color: darkMode ? '#9ca3af' : '#64748b' },
color: 'var(--text-3)',
padding: '0 10px',
'&:hover': { color: 'var(--text-2)' },
}),
clearIndicator: (base) => ({
...base,
color: darkMode ? '#6b7280' : '#94a3b8',
'&:hover': { color: '#ef4444' },
}),
loadingIndicator: (base) => ({ ...base, color: '#7c3aed' }),
noOptionsMessage: (base) => ({
...base,
color: darkMode ? '#6b7280' : '#94a3b8',
fontSize: '0.875rem',
}),
loadingMessage: (base) => ({
...base,
color: darkMode ? '#6b7280' : '#94a3b8',
fontSize: '0.875rem',
color: 'var(--text-3)',
padding: '0 6px',
'&:hover': { color: 'var(--danger)' },
}),
loadingIndicator: (base) => ({ ...base, color: 'var(--primary)' }),
noOptionsMessage: (base) => ({ ...base, color: 'var(--text-3)', fontSize: 13 }),
loadingMessage: (base) => ({ ...base, color: 'var(--text-3)', fontSize: 13 }),
};
return (