feat(M3): Dashboard-Builder — Edit-Modus, Drag&Drop, Palette, Tabs (#361)

- DashboardBuilder: @dnd-kit 12-Spalten-Flow-Grid (seed-konsistent), View/Edit-Schalter, Resize, Tab-Verwaltung, Dashboard-CRUD + Set-Default, Dirty-Save
- MiniAppHost ersetzt DashboardWidgetLoader (lazy Registry + settings-Props); Palette nur renderbare Apps (component-Filter)
- WidgetSettingsForm generisch aus settings_schema; Bestands-Widgets settings-fähig (RecentContacts: limit)
- api/miniapps.ts + api/dashboards.ts (TanStack-Query-Hooks, documents.ts-Muster)
- Dashboard.tsx = Builder-Host (StatCards/SystemMetrics bleiben bis M4); Legacy-Grid/Loader gelöscht, Geister-Test ersetzt
- Tests: Builder 13/13, Page 11/11, i18n de/en, tsc clean, build OK
This commit is contained in:
Agent Zero
2026-08-30 21:28:40 +02:00
parent 74827156d0
commit 26948fdb51
16 changed files with 1257 additions and 221 deletions
+7 -18
View File
@@ -1,9 +1,8 @@
import { useTranslation } from 'react-i18next';
import { StatCard } from '@/components/shared/StatCard';
import { ActivityFeed, ActivityItem } from '@/components/shared/ActivityFeed';
import { DashboardGrid } from '@/components/dashboard/DashboardGrid';
import { DashboardBuilder } from '@/components/dashboard/DashboardBuilder';
import { useUnifiedContacts, useAuditLog } from '@/api/hooks';
import { useDashboardWidgets } from '@/api/dashboard';
import { useSystemDashboard } from '@/api/systemDashboard';
import { useAuthStore } from '@/store/authStore';
import { formatDateTime } from '@/utils/date';
@@ -14,7 +13,6 @@ export function DashboardPage() {
const { data: companiesData } = useUnifiedContacts(1, 1, undefined, 'company');
const { data: contactsData } = useUnifiedContacts(1, 1, undefined, 'person');
const { data: auditData, isError: auditError } = useAuditLog(1, 5);
const { data: widgetsData, isError: widgetsError } = useDashboardWidgets();
const { data: systemData } = useSystemDashboard();
const user = useAuthStore((s) => s.user);
const isAdmin = user?.is_system_admin ?? false;
@@ -49,12 +47,15 @@ export function DashboardPage() {
avatarUrl: null,
}));
const widgets = widgetsData?.items ?? [];
return (
<div className="p-6 max-w-7xl mx-auto" data-testid="dashboard-page">
<h1 className="text-2xl font-bold text-secondary-900 mb-6">{t('dashboard.title')}</h1>
{/* Personal dashboard builder (Phase M3) — replaces the old plugin widget grid */}
<div className="mb-10">
<DashboardBuilder />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<StatCard
label={t('dashboard.totalCompanies')}
@@ -78,7 +79,7 @@ export function DashboardPage() {
/>
</div>
{/* System Metrics (Admin only) — I-DASH, I-COST, I-USE */}
{/* System Metrics (Admin only) — I-DASH, I-COST, I-USE (M4: becomes system MiniApp) */}
{isAdmin && systemData && (
<div className="mb-8">
<h2 className="text-lg font-semibold text-secondary-800 mb-4">{t('dashboard.systemMetrics', 'System Status')}</h2>
@@ -144,18 +145,6 @@ export function DashboardPage() {
</div>
)}
{/* Dynamic Plugin Widgets */}
{widgetsError ? (
<p className="text-sm text-secondary-500 mb-6" data-testid="dashboard-widgets-unavailable">
{t('dashboard.widgetsUnavailable')}
</p>
) : widgets.length > 0 ? (
<div className="mb-8">
<h2 className="text-lg font-semibold text-secondary-800 mb-4">{t('dashboard.widgets')}</h2>
<DashboardGrid widgets={widgets} />
</div>
) : null}
{auditError ? (
<p className="text-sm text-secondary-500" data-testid="activity-unavailable">
{t('dashboard.activityUnavailable')}