feat: I-MINI-RENDER — MiniAppBlock from placeholder to real rendering (loads manifest from backend, renders schema fields + config), tsc clean
This commit is contained in:
@@ -1,37 +1,95 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import type { MessageBlock } from '@/store/commStore';
|
import type { MessageBlock } from '@/store/commStore';
|
||||||
|
import { AppWindow, Loader2 } from 'lucide-react';
|
||||||
import { AppWindow } from 'lucide-react';
|
import { apiClient } from '@/api/client';
|
||||||
|
|
||||||
interface MiniAppBlockProps {
|
interface MiniAppBlockProps {
|
||||||
block: MessageBlock;
|
block: MessageBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MiniAppDef {
|
||||||
|
app_id: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
description: string;
|
||||||
|
plugin_name: string;
|
||||||
|
render_schema: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
const MiniAppBlock: React.FC<MiniAppBlockProps> = ({ block }) => {
|
const MiniAppBlock: React.FC<MiniAppBlockProps> = ({ block }) => {
|
||||||
const { app_id, config } = block.block_data;
|
const { app_id, config } = block.block_data;
|
||||||
|
const [appDef, setAppDef] = useState<MiniAppDef | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!app_id) return;
|
||||||
|
setLoading(true);
|
||||||
|
apiClient.get('/comm/miniapps')
|
||||||
|
.then((res) => {
|
||||||
|
const apps: MiniAppDef[] = res.data || [];
|
||||||
|
const found = apps.find((a) => a.app_id === app_id);
|
||||||
|
setAppDef(found || null);
|
||||||
|
})
|
||||||
|
.catch(() => setAppDef(null))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [app_id]);
|
||||||
|
|
||||||
const appId: string = app_id || 'Unbekannt';
|
const appId: string = app_id || 'Unbekannt';
|
||||||
const hasConfig = config && typeof config === 'object' && Object.keys(config).length > 0;
|
const hasConfig = config && typeof config === 'object' && Object.keys(config).length > 0;
|
||||||
|
const schema = appDef?.render_schema;
|
||||||
|
const hasSchema = schema && Object.keys(schema).length > 0;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="border-2 border-dashed border-secondary-300 rounded-lg p-4 text-center bg-secondary-50">
|
||||||
|
<Loader2 className="w-6 h-6 text-secondary-400 animate-spin mx-auto" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-2 border-dashed border-secondary-300 rounded-lg p-4 text-center bg-secondary-50">
|
<div className="border border-secondary-200 rounded-lg p-4 bg-white">
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<AppWindow className="w-8 h-8 text-secondary-400" aria-hidden="true" strokeWidth={1.5} />
|
<div className="w-8 h-8 rounded-lg bg-primary-100 flex items-center justify-center flex-shrink-0">
|
||||||
<p className="text-sm font-medium text-secondary-600">
|
<AppWindow className="w-4 h-4 text-primary-600" aria-hidden="true" strokeWidth={2} />
|
||||||
Mini-App: {appId}
|
</div>
|
||||||
</p>
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-xs text-secondary-400">
|
<p className="text-sm font-medium text-secondary-900">{appDef?.name || appId}</p>
|
||||||
Mini-Apps werden in Zukunft vollständig gerendert.
|
{appDef?.description && <p className="text-xs text-secondary-500 truncate">{appDef.description}</p>}
|
||||||
</p>
|
</div>
|
||||||
{hasConfig && (
|
{appDef?.plugin_name && (
|
||||||
<details className="text-xs text-secondary-400 mt-1">
|
<span className="text-xs px-2 py-0.5 rounded-full bg-secondary-100 text-secondary-600">{appDef.plugin_name}</span>
|
||||||
<summary className="cursor-pointer hover:text-secondary-600">Konfiguration</summary>
|
|
||||||
<pre className="mt-1 p-2 rounded bg-white text-left overflow-x-auto">
|
|
||||||
{JSON.stringify(config, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</details>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{hasSchema && schema.fields && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{schema.fields.map((field: any) => {
|
||||||
|
const value = config?.[field.name];
|
||||||
|
return (
|
||||||
|
<div key={field.name} className="flex items-center justify-between gap-2">
|
||||||
|
<span className="text-xs text-secondary-500">{field.label || field.name}</span>
|
||||||
|
<span className="text-sm text-secondary-900 font-medium">{value ?? '—'}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasConfig && !hasSchema && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
{Object.entries(config).slice(0, 5).map(([key, value]) => (
|
||||||
|
<div key={key} className="flex items-center justify-between gap-2">
|
||||||
|
<span className="text-xs text-secondary-500">{key}</span>
|
||||||
|
<span className="text-sm text-secondary-900 font-medium truncate max-w-[200px]">{String(value)}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!hasConfig && !hasSchema && (
|
||||||
|
<p className="text-xs text-secondary-400 text-center py-2">Keine Konfiguration</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user