Files
clinicpro/assets/admin/components/ui/SearchableSelect.tsx
T
hamed 27840da78d feat: integrate insurance coverage management for clinic services
- Updated NewSessionPage to calculate patient share based on insurance coverage rules.
- Refactored billing calculations to utilize new patientShareOf function for service items.
- Enhanced API documentation to reflect changes in service coverage structure.
- Implemented ServiceInsuranceModal for managing insurance coverage per service.
- Added UI components for displaying and editing insurance coverage details.
- Removed obsolete toggle switch styles and adjusted CSS for new components.
- Ensured backend endpoints support both service_item_id and service_item_uuid for flexibility.
2026-06-24 04:23:50 +03:30

120 lines
3.7 KiB
TypeScript

import React, { useMemo } from 'react';
import Select, { StylesConfig, GroupBase } from 'react-select';
import { useUiStore } from '../../stores/uiStore';
export interface SelectOption {
value: string | number;
label: string;
}
interface Props {
options: SelectOption[];
value?: string | number | null;
onChange?: (value: string | number | null) => void;
placeholder?: string;
isLoading?: boolean;
isDisabled?: boolean;
isClearable?: boolean;
noOptionsMessage?: string;
inputId?: string;
height?: number;
}
export default function SearchableSelect({
options,
value,
onChange,
placeholder = 'انتخاب کنید...',
isLoading,
isDisabled,
isClearable,
noOptionsMessage = 'موردی یافت نشد',
inputId,
height = 42,
}: Props) {
const darkMode = useUiStore((s) => s.darkMode);
const selected = useMemo(
() => options.find((o) => o.value === value) ?? null,
[options, value],
);
const styles: StylesConfig<SelectOption, false, GroupBase<SelectOption>> = {
control: (base, state) => ({
...base,
background: state.isFocused ? 'var(--surface)' : 'var(--surface-2)',
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 ? 'var(--primary)' : 'var(--border-2)',
},
}),
valueContainer: (base) => ({ ...base, padding: '0 14px' }),
menu: (base) => ({
...base,
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
? 'var(--primary-soft2)'
: state.isFocused
? 'var(--surface-2)'
: 'transparent',
color: state.isSelected ? 'var(--primary-700)' : 'var(--text)',
fontWeight: state.isSelected ? 700 : 400,
fontSize: 14,
cursor: 'pointer',
padding: '9px 14px',
}),
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,
color: 'var(--text-3)',
padding: '0 10px',
'&:hover': { color: 'var(--text-2)' },
}),
clearIndicator: (base) => ({
...base,
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 (
<Select<SelectOption>
options={options}
value={selected}
onChange={(opt) => onChange?.(opt ? opt.value : null)}
placeholder={placeholder}
isLoading={isLoading}
isDisabled={isDisabled}
isClearable={isClearable}
styles={styles}
isRtl
menuPortalTarget={typeof document !== 'undefined' ? document.body : undefined}
menuPosition="fixed"
noOptionsMessage={() => noOptionsMessage}
loadingMessage={() => 'در حال بارگذاری...'}
inputId={inputId}
/>
);
}