fix:search api call to search
This commit is contained in:
parent
1d4ec78360
commit
3a021c1748
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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 && (
|
||||||
|
|||||||
@ -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 }))]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,55 +36,58 @@ export function WfLookupField({ label, required, value, onChange, client, config
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
client.wfLookupRecords({
|
const timer = setTimeout(() => {
|
||||||
activityId,
|
client.wfLookupRecords({
|
||||||
fieldId,
|
activityId,
|
||||||
formData,
|
fieldId,
|
||||||
limit: SELECT_OPTION_LIMIT
|
formData: JSON.parse(formDataStr),
|
||||||
})
|
limit: SELECT_OPTION_LIMIT,
|
||||||
.then(res => {
|
search: searchQuery
|
||||||
if (!mounted) return;
|
})
|
||||||
// The API might return { data: [...] } or { records: [...] } or just an array
|
.then(res => {
|
||||||
const arr = Array.isArray(res) ? res : (res.data || res.records || []);
|
if (!mounted) return;
|
||||||
|
// The API might return { data: [...] } or { records: [...] } or just an array
|
||||||
const displayFields = config.display_fields || [];
|
const arr = Array.isArray(res) ? res : (res.data || res.records || []);
|
||||||
|
|
||||||
const opts = arr.map((row: any) => {
|
const displayFields = config.display_fields || [];
|
||||||
// Join the display fields to form the label
|
|
||||||
const labelParts = displayFields
|
const opts = arr.map((row: any) => {
|
||||||
.map((df: any) => row[df.field_id])
|
// Join the display fields to form the label
|
||||||
.filter((v: any) => v != null && v !== '');
|
const labelParts = displayFields
|
||||||
|
.map((df: any) => row[df.field_id])
|
||||||
const labelText = labelParts.length > 0
|
.filter((v: any) => v != null && v !== '');
|
||||||
? labelParts.join(' - ')
|
|
||||||
: `ID: ${row.instance_id || row.id}`;
|
const labelText = labelParts.length > 0
|
||||||
|
? labelParts.join(' - ')
|
||||||
return {
|
: `ID: ${row.instance_id || row.id}`;
|
||||||
value: String(row.instance_id || row.id),
|
|
||||||
label: labelText,
|
return {
|
||||||
_raw: row
|
value: String(row.instance_id || row.id),
|
||||||
};
|
label: labelText,
|
||||||
|
_raw: row
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setOptions(opts);
|
||||||
|
|
||||||
|
// Auto-select first option if no value is selected yet (e.g. for daily_log)
|
||||||
|
const isDailyLogField = fieldId === 'daily_log' || fieldId === 'field_1785225403902' || label.toLowerCase().includes('daily log');
|
||||||
|
if ((!value || value === '') && opts.length > 0 && isDailyLogField) {
|
||||||
|
const firstOpt = opts[0];
|
||||||
|
console.log("[WfLookupField] Auto-selecting first daily_log option:", firstOpt);
|
||||||
|
onChange(firstOpt.value, firstOpt._raw);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Failed to load wf_lookup records:", err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (mounted) setLoading(false);
|
||||||
});
|
});
|
||||||
|
}, 300);
|
||||||
setOptions(opts);
|
|
||||||
|
|
||||||
// Auto-select first option if no value is selected yet (e.g. for daily_log)
|
return () => { mounted = false; clearTimeout(timer); };
|
||||||
const isDailyLogField = fieldId === 'daily_log' || fieldId === 'field_1785225403902' || label.toLowerCase().includes('daily log');
|
}, [client, config, activityId, fieldId, formDataStr, searchQuery]);
|
||||||
if ((!value || value === '') && opts.length > 0 && isDailyLogField) {
|
|
||||||
const firstOpt = opts[0];
|
|
||||||
console.log("[WfLookupField] Auto-selecting first daily_log option:", firstOpt);
|
|
||||||
onChange(firstOpt.value, firstOpt._raw);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error("Failed to load wf_lookup records:", err);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
if (mounted) setLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => { mounted = false; };
|
|
||||||
}, [client, config, activityId, fieldId, formDataStr]);
|
|
||||||
|
|
||||||
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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,9 +53,11 @@ 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
|
||||||
o.label.toLowerCase().includes(searchQuery.toLowerCase())
|
? normalizedOptions
|
||||||
);
|
: normalizedOptions.filter((o) =>
|
||||||
|
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,9 +261,11 @@ export function MultiSelect({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredOptions = normalizedOptions.filter((o) =>
|
const filteredOptions = onSearch
|
||||||
o.label.toLowerCase().includes(searchQuery.toLowerCase())
|
? normalizedOptions
|
||||||
);
|
: normalizedOptions.filter((o) =>
|
||||||
|
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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user