fix:search api call to search

This commit is contained in:
suryac 2026-08-18 13:31:19 +05:30
parent 1d4ec78360
commit 3a021c1748
5 changed files with 109 additions and 71 deletions

View File

@ -264,6 +264,6 @@ export const HIDDEN_FORM_FIELDS = [
'store_code_3', 'store_code_3',
]; ];
export const SELECT_OPTION_LIMIT = 500; export const SELECT_OPTION_LIMIT = 200;
export const PDF_MAX_SKUS_PER_CHUNK = 20; export const PDF_MAX_SKUS_PER_CHUNK = 20;

View File

@ -60,9 +60,9 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
}, [values]); }, [values]);
const [storeOptions, setStoreOptions] = useState<{ value: string; label: string; _raw?: any }[]>([]); const [storeOptions, setStoreOptions] = useState<{ value: string; label: string; _raw?: any }[]>([]);
const [storeSearch, setStoreSearch] = useState('');
const [fetchingDailyLog, setFetchingDailyLog] = useState(false); const [fetchingDailyLog, setFetchingDailyLog] = useState(false);
const [fetchingStores, setFetchingStores] = useState(false); const [fetchingStores, setFetchingStores] = useState(false);
const fetchingStoresRef = useRef(false);
const [chainedActivity, setChainedActivity] = useState<{ const [chainedActivity, setChainedActivity] = useState<{
activityId: string; activityId: string;
@ -135,9 +135,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
}, [client, activityId, todayStr, nowTimeStr]); }, [client, activityId, todayStr, nowTimeStr]);
// 2. Helper to fetch select_store options ONLY using updated prefilled formData // 2. Helper to fetch select_store options ONLY using updated prefilled formData
const fetchStoreOptions = useCallback(async (formDataOverride?: Record<string, any>) => { const fetchStoreOptions = useCallback(async (formDataOverride?: Record<string, any>, searchStr: string = '') => {
if (fetchingStoresRef.current) return;
fetchingStoresRef.current = true;
setFetchingStores(true); setFetchingStores(true);
setStoreError(null); setStoreError(null);
@ -149,6 +147,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
fieldId: 'select_store', fieldId: 'select_store',
formData: formDataToSend, formData: formDataToSend,
limit: SELECT_OPTION_LIMIT, limit: SELECT_OPTION_LIMIT,
search: searchStr,
}); });
const arr = Array.isArray(lookupRes) const arr = Array.isArray(lookupRes)
@ -198,10 +197,16 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
} }
} finally { } finally {
setFetchingStores(false); setFetchingStores(false);
fetchingStoresRef.current = false;
} }
}, [client, activityId]); }, [client, activityId]);
useEffect(() => {
const timer = setTimeout(() => {
fetchStoreOptions(valuesRef.current, storeSearch);
}, 300);
return () => clearTimeout(timer);
}, [storeSearch, fetchStoreOptions]);
// 3. Fetch Daily Log lookup data initially & prefill form state // 3. Fetch Daily Log lookup data initially & prefill form state
const loadDailyLogData = useCallback(async (dateVal: string, timeVal: string) => { const loadDailyLogData = useCallback(async (dateVal: string, timeVal: string) => {
setFetchingDailyLog(true); setFetchingDailyLog(true);
@ -274,9 +279,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
loadDailyLogData(values.date_of_visit, values.time_of_visit); loadDailyLogData(values.date_of_visit, values.time_of_visit);
}, [loadDailyLogData, values.date_of_visit, values.time_of_visit]); }, [loadDailyLogData, values.date_of_visit, values.time_of_visit]);
const handleStoreDropdownOpen = () => {
fetchStoreOptions();
};
const handleStoreSelect = (fieldId: string, val: string) => { const handleStoreSelect = (fieldId: string, val: string) => {
const selectedOpt = storeOptions.find(o => String(o.value) === String(val)); const selectedOpt = storeOptions.find(o => String(o.value) === String(val));
@ -420,8 +423,9 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
required={f.mandatory} required={f.mandatory}
value={(val as string) || ''} value={(val as string) || ''}
options={[{ value: '', label: `Select ${f.name}...` }, ...storeOptions]} options={[{ value: '', label: `Select ${f.name}...` }, ...storeOptions]}
onDropdownOpen={handleStoreDropdownOpen} onDropdownOpen={() => fetchStoreOptions(valuesRef.current, storeSearch)}
onChange={e => handleStoreSelect(fieldId, e.target.value)} onChange={e => handleStoreSelect(fieldId, e.target.value)}
onSearch={setStoreSearch}
disabled={isDisabled} disabled={isDisabled}
/> />
{fetchingStores && ( {fetchingStores && (

View File

@ -6,19 +6,26 @@ export function SelectField({
value, value,
options, options,
onChange, onChange,
onSearch,
}: { }: {
label: string; label: string;
required?: boolean; required?: boolean;
value: string; value: string;
options: any[]; options: any[];
onChange: (val: string) => void; onChange: (val: string, fullRow?: any) => void;
onSearch?: (query: string) => void;
}) { }) {
return ( return (
<Select <Select
label={label} label={label}
required={required} required={required}
value={value ?? ''} value={value ?? ''}
onChange={(e) => onChange(e.target.value)} onChange={(e) => {
const val = e.target.value;
const opt = options.find((o: any) => String(o.value) === val);
onChange(val, opt?._raw);
}}
onSearch={onSearch}
options={[{ value: '', label: 'Select...' }, ...options.map((o: any) => ({ value: String(o.value), label: o.label }))]} options={[{ value: '', label: 'Select...' }, ...options.map((o: any) => ({ value: String(o.value), label: o.label }))]}
/> />
); );

View File

@ -18,6 +18,7 @@ export interface WfLookupFieldProps {
export function WfLookupField({ label, required, value, onChange, client, config, activityId, fieldId, formData }: WfLookupFieldProps) { export function WfLookupField({ label, required, value, onChange, client, config, activityId, fieldId, formData }: WfLookupFieldProps) {
const [options, setOptions] = useState<{ label: string; value: string; _raw?: any }[]>([]); const [options, setOptions] = useState<{ label: string; value: string; _raw?: any }[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const formDataStr = JSON.stringify(formData); const formDataStr = JSON.stringify(formData);
@ -35,11 +36,13 @@ export function WfLookupField({ label, required, value, onChange, client, config
} }
setLoading(true); setLoading(true);
const timer = setTimeout(() => {
client.wfLookupRecords({ client.wfLookupRecords({
activityId, activityId,
fieldId, fieldId,
formData, formData: JSON.parse(formDataStr),
limit: SELECT_OPTION_LIMIT limit: SELECT_OPTION_LIMIT,
search: searchQuery
}) })
.then(res => { .then(res => {
if (!mounted) return; if (!mounted) return;
@ -81,9 +84,10 @@ export function WfLookupField({ label, required, value, onChange, client, config
.finally(() => { .finally(() => {
if (mounted) setLoading(false); if (mounted) setLoading(false);
}); });
}, 300);
return () => { mounted = false; }; return () => { mounted = false; clearTimeout(timer); };
}, [client, config, activityId, fieldId, formDataStr]); }, [client, config, activityId, fieldId, formDataStr, searchQuery]);
const handleSelectChange = (newVal: string) => { const handleSelectChange = (newVal: string) => {
const selectedOpt = options.find(o => String(o.value) === String(newVal)); const selectedOpt = options.find(o => String(o.value) === String(newVal));
@ -96,6 +100,7 @@ export function WfLookupField({ label, required, value, onChange, client, config
required={required} required={required}
value={String(value || '')} value={String(value || '')}
onChange={handleSelectChange} onChange={handleSelectChange}
onSearch={setSearchQuery}
options={options} options={options}
/> />
); );

View File

@ -20,6 +20,8 @@ export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
searchable?: boolean; searchable?: boolean;
/** Callback fired when dropdown opens */ /** Callback fired when dropdown opens */
onDropdownOpen?: () => void; onDropdownOpen?: () => void;
/** Callback fired when search query changes */
onSearch?: (query: string) => void;
} }
/** Custom searchable select component using React Portal to prevent container clipping. */ /** Custom searchable select component using React Portal to prevent container clipping. */
@ -34,6 +36,7 @@ export function Select({
placeholder, placeholder,
searchable = true, searchable = true,
onDropdownOpen, onDropdownOpen,
onSearch,
...rest ...rest
}: SelectProps) { }: SelectProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -50,7 +53,9 @@ export function Select({
const selectedOption = normalizedOptions.find((o) => o.value === currentValue); const selectedOption = normalizedOptions.find((o) => o.value === currentValue);
const selectedOptionLabel = selectedOption ? selectedOption.label : ''; const selectedOptionLabel = selectedOption ? selectedOption.label : '';
const filteredOptions = normalizedOptions.filter((o) => const filteredOptions = onSearch
? normalizedOptions
: normalizedOptions.filter((o) =>
o.label.toLowerCase().includes(searchQuery.toLowerCase()) o.label.toLowerCase().includes(searchQuery.toLowerCase())
); );
@ -127,14 +132,20 @@ export function Select({
<input <input
type="text" type="text"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => {
setSearchQuery(e.target.value);
if (onSearch) onSearch(e.target.value);
}}
placeholder="Search options..." placeholder="Search options..."
className="w-full text-xs bg-transparent border-none outline-none text-foreground placeholder:text-muted" className="w-full text-xs bg-transparent border-none outline-none text-foreground placeholder:text-muted"
/> />
{searchQuery && ( {searchQuery && (
<button <button
type="button" type="button"
onClick={() => setSearchQuery('')} onClick={() => {
setSearchQuery('');
if (onSearch) onSearch('');
}}
className="text-muted hover:text-foreground p-0.5 rounded cursor-pointer" className="text-muted hover:text-foreground p-0.5 rounded cursor-pointer"
> >
<X size={12} /> <X size={12} />
@ -208,6 +219,8 @@ export interface MultiSelectProps {
className?: string; className?: string;
/** Disable search header filter if set to false */ /** Disable search header filter if set to false */
searchable?: boolean; searchable?: boolean;
/** Callback fired when search query changes */
onSearch?: (query: string) => void;
} }
/** Custom searchable multiselect component using React Portal. */ /** Custom searchable multiselect component using React Portal. */
@ -223,6 +236,7 @@ export function MultiSelect({
disabled, disabled,
placeholder, placeholder,
searchable = true, searchable = true,
onSearch,
}: MultiSelectProps) { }: MultiSelectProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
@ -247,7 +261,9 @@ export function MultiSelect({
} }
}; };
const filteredOptions = normalizedOptions.filter((o) => const filteredOptions = onSearch
? normalizedOptions
: normalizedOptions.filter((o) =>
o.label.toLowerCase().includes(searchQuery.toLowerCase()) o.label.toLowerCase().includes(searchQuery.toLowerCase())
); );
@ -330,15 +346,21 @@ export function MultiSelect({
<input <input
type="text" type="text"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => {
setSearchQuery(e.target.value);
if (onSearch) onSearch(e.target.value);
}}
placeholder="Search options..." placeholder="Search options..."
className="w-full text-xs bg-transparent border-none outline-none text-strong placeholder:text-muted" className="w-full text-xs bg-transparent border-none outline-none text-foreground placeholder:text-muted"
/> />
{searchQuery && ( {searchQuery && (
<button <button
type="button" type="button"
onClick={() => setSearchQuery('')} onClick={() => {
className="text-muted hover:text-strong p-0.5 rounded cursor-pointer" setSearchQuery('');
if (onSearch) onSearch('');
}}
className="text-muted hover:text-foreground p-0.5 rounded cursor-pointer"
> >
<X size={12} /> <X size={12} />
</button> </button>