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 { AppWindow } from 'lucide-react';
|
||||
import { AppWindow, Loader2 } from 'lucide-react';
|
||||
import { apiClient } from '@/api/client';
|
||||
|
||||
interface MiniAppBlockProps {
|
||||
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 { 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 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 (
|
||||
<div className="border-2 border-dashed border-secondary-300 rounded-lg p-4 text-center bg-secondary-50">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<AppWindow className="w-8 h-8 text-secondary-400" aria-hidden="true" strokeWidth={1.5} />
|
||||
<p className="text-sm font-medium text-secondary-600">
|
||||
Mini-App: {appId}
|
||||
</p>
|
||||
<p className="text-xs text-secondary-400">
|
||||
Mini-Apps werden in Zukunft vollständig gerendert.
|
||||
</p>
|
||||
{hasConfig && (
|
||||
<details className="text-xs text-secondary-400 mt-1">
|
||||
<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 className="border border-secondary-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-primary-100 flex items-center justify-center flex-shrink-0">
|
||||
<AppWindow className="w-4 h-4 text-primary-600" aria-hidden="true" strokeWidth={2} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-secondary-900">{appDef?.name || appId}</p>
|
||||
{appDef?.description && <p className="text-xs text-secondary-500 truncate">{appDef.description}</p>}
|
||||
</div>
|
||||
{appDef?.plugin_name && (
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-secondary-100 text-secondary-600">{appDef.plugin_name}</span>
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user