- Updated RepresentationsPage to use SearchableSelect for city filtering. - Modified form handling to use Controller from react-hook-form for city selection. - Improved city filter handling to support null values. - Added city_id to Representation interface in types. - Implemented uploadLogo endpoint in CategoryController for logo uploads. - Added logo_url field to Category entity and updated related services. - Created SearchableSelect component for better user experience in selecting options. - Added migration to include logo_url in categories table.
122 lines
3.6 KiB
TypeScript
122 lines
3.6 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;
|
|
}
|
|
|
|
export default function SearchableSelect({
|
|
options,
|
|
value,
|
|
onChange,
|
|
placeholder = 'انتخاب کنید...',
|
|
isLoading,
|
|
isDisabled,
|
|
isClearable,
|
|
noOptionsMessage = 'موردی یافت نشد',
|
|
inputId,
|
|
}: 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: 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',
|
|
'&:hover': {
|
|
borderColor: state.isFocused ? '#7c3aed' : darkMode ? '#6b7280' : '#94a3b8',
|
|
},
|
|
}),
|
|
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',
|
|
}),
|
|
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
|
option: (base, state) => ({
|
|
...base,
|
|
background: state.isSelected
|
|
? '#7c3aed'
|
|
: state.isFocused
|
|
? darkMode ? '#374151' : '#f1f5f9'
|
|
: 'transparent',
|
|
color: state.isSelected ? '#ffffff' : darkMode ? '#e5e7eb' : '#1e293b',
|
|
fontSize: '0.875rem',
|
|
cursor: 'pointer',
|
|
}),
|
|
singleValue: (base) => ({ ...base, color: darkMode ? '#f3f4f6' : '#1e293b' }),
|
|
input: (base) => ({ ...base, color: darkMode ? '#f3f4f6' : '#1e293b' }),
|
|
placeholder: (base) => ({ ...base, color: darkMode ? '#6b7280' : '#94a3b8' }),
|
|
indicatorSeparator: (base) => ({
|
|
...base,
|
|
background: darkMode ? '#374151' : '#e2e8f0',
|
|
}),
|
|
dropdownIndicator: (base) => ({
|
|
...base,
|
|
color: darkMode ? '#6b7280' : '#94a3b8',
|
|
'&:hover': { color: darkMode ? '#9ca3af' : '#64748b' },
|
|
}),
|
|
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',
|
|
}),
|
|
};
|
|
|
|
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}
|
|
/>
|
|
);
|
|
}
|