dyn options in api
This commit is contained in:
parent
f99b6b7604
commit
b207c0cc5b
1 changed files with 29 additions and 19 deletions
|
|
@ -82,6 +82,8 @@ export interface FormGeneratorFormProps<T = any> {
|
||||||
transformField?: (attribute: AttributeDefinition) => AttributeDefinition;
|
transformField?: (attribute: AttributeDefinition) => AttributeDefinition;
|
||||||
// Optional: Custom validation function
|
// Optional: Custom validation function
|
||||||
customValidator?: (formData: T, attributes: AttributeDefinition[]) => Record<string, string>;
|
customValidator?: (formData: T, attributes: AttributeDefinition[]) => Record<string, string>;
|
||||||
|
// Optional: Feature instance ID for feature-scoped options (replaces {instanceId} in API paths)
|
||||||
|
instanceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormGeneratorForm component - Backend-driven form generation
|
// FormGeneratorForm component - Backend-driven form generation
|
||||||
|
|
@ -98,9 +100,23 @@ export function FormGeneratorForm<T extends Record<string, any>>({
|
||||||
attributes: providedAttributes,
|
attributes: providedAttributes,
|
||||||
filterFields,
|
filterFields,
|
||||||
transformField,
|
transformField,
|
||||||
customValidator
|
customValidator,
|
||||||
|
instanceId
|
||||||
}: FormGeneratorFormProps<T>) {
|
}: FormGeneratorFormProps<T>) {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
|
||||||
|
// Helper to resolve API paths with {instanceId} placeholder
|
||||||
|
const resolveApiPath = (path: string): string => {
|
||||||
|
if (path.includes('{instanceId}')) {
|
||||||
|
const resolvedInstanceId = instanceId || (data as any)?.featureInstanceId;
|
||||||
|
if (!resolvedInstanceId) {
|
||||||
|
console.warn(`API path "${path}" requires instanceId but none provided`);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return path.replace('{instanceId}', resolvedInstanceId);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
};
|
||||||
const [formData, setFormData] = useState<T>(data || {} as T);
|
const [formData, setFormData] = useState<T>(data || {} as T);
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
const [fieldFocused, setFieldFocused] = useState<Record<string, boolean>>({});
|
const [fieldFocused, setFieldFocused] = useState<Record<string, boolean>>({});
|
||||||
|
|
@ -234,7 +250,8 @@ export function FormGeneratorForm<T extends Record<string, any>>({
|
||||||
setFieldFocused({});
|
setFieldFocused({});
|
||||||
}, [data, getFilteredAttributes]);
|
}, [data, getFilteredAttributes]);
|
||||||
|
|
||||||
// Fetch options for fields with optionsReference
|
// Fetch options for fields with optionsReference (API path)
|
||||||
|
// Backend provides options in standardized format: { value, label }
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchOptions = async () => {
|
const fetchOptions = async () => {
|
||||||
const filteredAttrs = getFilteredAttributes();
|
const filteredAttrs = getFilteredAttributes();
|
||||||
|
|
@ -250,39 +267,32 @@ export function FormGeneratorForm<T extends Record<string, any>>({
|
||||||
for (const field of fieldsToFetch) {
|
for (const field of fieldsToFetch) {
|
||||||
if (typeof field.options !== 'string') continue;
|
if (typeof field.options !== 'string') continue;
|
||||||
|
|
||||||
|
const optionKey = field.options;
|
||||||
setLoadingOptions(prev => ({ ...prev, [field.name]: true }));
|
setLoadingOptions(prev => ({ ...prev, [field.name]: true }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`/api/options/${field.options}`);
|
// Backend provides full API path (e.g., "/api/connections/statuses/options")
|
||||||
|
// Resolve {instanceId} placeholder if present
|
||||||
|
const apiPath = resolveApiPath(optionKey);
|
||||||
|
const response = await api.get(apiPath);
|
||||||
|
|
||||||
let fetchedOptions: Array<{ value: string | number; label: string }> = [];
|
let fetchedOptions: Array<{ value: string | number; label: string }> = [];
|
||||||
|
|
||||||
if (Array.isArray(response.data)) {
|
if (Array.isArray(response.data)) {
|
||||||
|
// Backend returns standardized format: [{ value, label }]
|
||||||
fetchedOptions = response.data.map((opt: any) => {
|
fetchedOptions = response.data.map((opt: any) => {
|
||||||
if (typeof opt === 'string' || typeof opt === 'number') {
|
if (typeof opt === 'string' || typeof opt === 'number') {
|
||||||
return { value: opt, label: String(opt) };
|
return { value: opt, label: String(opt) };
|
||||||
}
|
}
|
||||||
|
// Handle multilingual labels
|
||||||
const labelValue = typeof opt.label === 'string'
|
const labelValue = typeof opt.label === 'string'
|
||||||
? opt.label
|
? opt.label
|
||||||
: opt.label?.en || opt.label?.[Object.keys(opt.label || {})[0]] || String(opt.value);
|
: opt.label?.en || opt.label?.[Object.keys(opt.label || {})[0]] || String(opt.value);
|
||||||
return {
|
return { value: opt.value, label: labelValue };
|
||||||
value: opt.value,
|
|
||||||
label: labelValue
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else if (response.data?.options && Array.isArray(response.data.options)) {
|
|
||||||
fetchedOptions = response.data.options.map((opt: any) => {
|
|
||||||
const labelValue = typeof opt.label === 'string'
|
|
||||||
? opt.label
|
|
||||||
: opt.label?.en || opt.label?.[Object.keys(opt.label || {})[0]] || String(opt.value);
|
|
||||||
return {
|
|
||||||
value: opt.value,
|
|
||||||
label: labelValue
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setOptionsCache(prev => ({ ...prev, [field.options as string]: fetchedOptions }));
|
setOptionsCache(prev => ({ ...prev, [optionKey]: fetchedOptions }));
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Failed to fetch options for ${field.options}:`, error);
|
console.error(`Failed to fetch options for ${field.options}:`, error);
|
||||||
setOptionsCache(prev => ({ ...prev, [field.options as string]: [] }));
|
setOptionsCache(prev => ({ ...prev, [field.options as string]: [] }));
|
||||||
|
|
@ -293,7 +303,7 @@ export function FormGeneratorForm<T extends Record<string, any>>({
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchOptions();
|
fetchOptions();
|
||||||
}, [getFilteredAttributes, optionsCache]);
|
}, [getFilteredAttributes, optionsCache, resolveApiPath]);
|
||||||
|
|
||||||
// Handle field focus
|
// Handle field focus
|
||||||
const handleFieldFocus = (fieldName: string, focused: boolean) => {
|
const handleFieldFocus = (fieldName: string, focused: boolean) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue