diff --git a/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx b/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx index 2e9e7a7..640f143 100644 --- a/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx +++ b/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx @@ -82,6 +82,8 @@ export interface FormGeneratorFormProps { transformField?: (attribute: AttributeDefinition) => AttributeDefinition; // Optional: Custom validation function customValidator?: (formData: T, attributes: AttributeDefinition[]) => Record; + // Optional: Feature instance ID for feature-scoped options (replaces {instanceId} in API paths) + instanceId?: string; } // FormGeneratorForm component - Backend-driven form generation @@ -98,9 +100,23 @@ export function FormGeneratorForm>({ attributes: providedAttributes, filterFields, transformField, - customValidator + customValidator, + instanceId }: FormGeneratorFormProps) { 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(data || {} as T); const [errors, setErrors] = useState>({}); const [fieldFocused, setFieldFocused] = useState>({}); @@ -234,7 +250,8 @@ export function FormGeneratorForm>({ setFieldFocused({}); }, [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(() => { const fetchOptions = async () => { const filteredAttrs = getFilteredAttributes(); @@ -250,39 +267,32 @@ export function FormGeneratorForm>({ for (const field of fieldsToFetch) { if (typeof field.options !== 'string') continue; + const optionKey = field.options; setLoadingOptions(prev => ({ ...prev, [field.name]: true })); 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 }> = []; if (Array.isArray(response.data)) { + // Backend returns standardized format: [{ value, label }] fetchedOptions = response.data.map((opt: any) => { if (typeof opt === 'string' || typeof opt === 'number') { return { value: opt, label: String(opt) }; } + // Handle multilingual labels 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 - }; - }); - } 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 - }; + return { value: opt.value, label: labelValue }; }); } - setOptionsCache(prev => ({ ...prev, [field.options as string]: fetchedOptions })); + setOptionsCache(prev => ({ ...prev, [optionKey]: fetchedOptions })); } catch (error: any) { console.error(`Failed to fetch options for ${field.options}:`, error); setOptionsCache(prev => ({ ...prev, [field.options as string]: [] })); @@ -293,7 +303,7 @@ export function FormGeneratorForm>({ }; fetchOptions(); - }, [getFilteredAttributes, optionsCache]); + }, [getFilteredAttributes, optionsCache, resolveApiPath]); // Handle field focus const handleFieldFocus = (fieldName: string, focused: boolean) => {