+ {fields.map((field) => {
+ const label = field.label_key ? t(field.label_key) : field.label || field.name;
+ const value = values[field.name] ?? field.value ?? field.default_value ?? '';
+
+ if (field.field_type === 'boolean') {
+ return (
+
+ onChange?.(field.name, e.target.checked)}
+ className="h-4 w-4 rounded border-secondary-300"
+ />
+
+
+ );
+ }
+
+ if (field.field_type === 'select') {
+ return (
+
+
+
+ );
+ }
+
+ if (field.field_type === 'multiselect') {
+ const selected: string[] = Array.isArray(value) ? value : [];
+ return (
+
+
+
+ {field.options.map((opt) => (
+
+ ))}
+
+
+ );
+ }
+
+ // text, number, date
+ return (
+
+
+ {
+ let val: any = e.target.value;
+ if (field.field_type === 'number' && val !== '') val = Number(val);
+ if (val === '') val = null;
+ onChange?.(field.name, val);
+ }}
+ />
+
+ );
+ })}
+
+ );
+}
diff --git a/frontend/src/components/plugins/__tests__/PluginRegistry.test.tsx b/frontend/src/components/plugins/__tests__/PluginRegistry.test.tsx
index d13e39e..d2e0be7 100644
--- a/frontend/src/components/plugins/__tests__/PluginRegistry.test.tsx
+++ b/frontend/src/components/plugins/__tests__/PluginRegistry.test.tsx
@@ -21,6 +21,7 @@ const mockManifests: PluginUiManifest[] = [
detail_tabs: [],
settings_pages: [],
dashboard_widgets: [],
+ custom_fields: [],
},
];
diff --git a/frontend/src/components/plugins/__tests__/PluginRouteRenderer.test.tsx b/frontend/src/components/plugins/__tests__/PluginRouteRenderer.test.tsx
index 29b1989..07bf9d9 100644
--- a/frontend/src/components/plugins/__tests__/PluginRouteRenderer.test.tsx
+++ b/frontend/src/components/plugins/__tests__/PluginRouteRenderer.test.tsx
@@ -29,6 +29,7 @@ const mockManifests: PluginUiManifest[] = [
detail_tabs: [],
settings_pages: [],
dashboard_widgets: [],
+ custom_fields: [],
},
{
name: 'calendar_plugin',
@@ -42,6 +43,7 @@ const mockManifests: PluginUiManifest[] = [
detail_tabs: [],
settings_pages: [],
dashboard_widgets: [],
+ custom_fields: [],
},
];
diff --git a/frontend/src/store/__tests__/pluginStore.test.ts b/frontend/src/store/__tests__/pluginStore.test.ts
index da7a412..6b03cd6 100644
--- a/frontend/src/store/__tests__/pluginStore.test.ts
+++ b/frontend/src/store/__tests__/pluginStore.test.ts
@@ -25,6 +25,7 @@ const mockManifests: PluginUiManifest[] = [
dashboard_widgets: [
{ id: 'widget_a', label_key: 'widgets.a', label: 'Widget A', component: '@/components/WidgetA', icon: 'A', order: 200, col_span: 2, row_span: 1, permission: '' },
],
+ custom_fields: [],
},
{
name: 'plugin_b',
@@ -46,6 +47,7 @@ const mockManifests: PluginUiManifest[] = [
dashboard_widgets: [
{ id: 'widget_b', label_key: 'widgets.b', label: 'Widget B', component: '@/components/WidgetB', icon: 'B', order: 100, col_span: 1, row_span: 1, permission: '' },
],
+ custom_fields: [],
},
];
diff --git a/frontend/src/store/pluginStore.ts b/frontend/src/store/pluginStore.ts
index 3791330..e568360 100644
--- a/frontend/src/store/pluginStore.ts
+++ b/frontend/src/store/pluginStore.ts
@@ -50,6 +50,17 @@ export interface PluginDashboardWidget {
permission: string;
}
+export interface PluginCustomFieldDefinition {
+ name: string;
+ label: string;
+ label_key: string;
+ field_type: 'text' | 'number' | 'date' | 'select' | 'multiselect' | 'boolean';
+ options: string[];
+ default_value: any;
+ required: boolean;
+ entity: string;
+}
+
export interface PluginUiManifest {
name: string;
display_name: string;
@@ -60,6 +71,7 @@ export interface PluginUiManifest {
detail_tabs: PluginDetailTab[];
settings_pages: PluginSettingsPage[];
dashboard_widgets: PluginDashboardWidget[];
+ custom_fields: PluginCustomFieldDefinition[];
}
interface PluginState {
@@ -77,6 +89,7 @@ interface PluginState {
getDetailTabsForEntity: (entityType: string) => PluginDetailTab[];
getAllSettingsPages: () => PluginSettingsPage[];
getAllDashboardWidgets: () => PluginDashboardWidget[];
+ getCustomFieldsForEntity: (entityType: string) => PluginCustomFieldDefinition[];
}
export const usePluginStore = create