feat(UI-Overhaul-Phase7): Reports UI mit ResizablePanel und PluginToolbar
Check Cross-Plugin Imports / check (push) Has been cancelled

Migration 0139: Add folder_id column to report_templates for folder-based organization

Backend:
- ReportTemplate model: add folder_id field (nullable UUID)

Frontend:
- api/reports.ts: ReportTemplate interface updated with folder_id
- Reports.tsx: Added ResizablePanel for template list (resizable left sidebar)
- Reports.tsx: Added PluginToolbar registration with new-report button
- Reports.tsx: Added useEffect import for toolbar registration

tsc clean
This commit is contained in:
Agent Zero
2026-08-21 13:55:11 +02:00
parent 6c197e1fc5
commit 16d15bcfbf
4 changed files with 44 additions and 3 deletions
@@ -0,0 +1,28 @@
"""Reports: folder_id column for folder-based sorting
Revision ID: 0139
Revises: 0138
Create Date: 2026-08-21
Adds folder_id to report_templates for folder-based organization.
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import UUID as PGUUID
# revision identifiers, used by Alembic.
revision = "0139"
down_revision = "0138"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column("report_templates", sa.Column("folder_id", PGUUID(as_uuid=True), nullable=True))
op.create_index("ix_report_templates_folder", "report_templates", ["folder_id"])
def downgrade() -> None:
op.drop_index("ix_report_templates_folder", table_name="report_templates")
op.drop_column("report_templates", "folder_id")
@@ -34,6 +34,7 @@ class ReportTemplate(Base, TenantMixin, OwnedMixin):
String(50), nullable=False, default="csv"
)
created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
folder_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True)
class ReportInstance(Base, TenantMixin, OwnedMixin):
+1
View File
@@ -24,6 +24,7 @@ export interface ReportTemplate {
deleted_at?: string | null;
created_at?: string | null;
updated_at?: string | null;
folder_id?: string | null;
}
export interface TemplateCreateInput {
+14 -3
View File
@@ -1,5 +1,5 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
BarChart3,
@@ -20,6 +20,8 @@ import clsx from 'clsx';
import { PrintButton } from '@/components/common/PrintButton';
import { printPdfBlob } from '@/utils/print';
import { useToast } from '@/components/ui/Toast';
import { ResizablePanel } from '@/components/ui/ResizablePanel';
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
import {
useReportTemplates,
useCreateReportTemplate,
@@ -58,6 +60,7 @@ interface DownloadEntry {
export function ReportsPage() {
const { t } = useTranslation();
const toast = useToast();
const { registerItems, unregisterPlugin } = usePluginToolbarStore();
// Data hooks
const { data: templates = [], isLoading: templatesLoading } = useReportTemplates();
@@ -80,6 +83,14 @@ export function ReportsPage() {
const [downloadHistory, setDownloadHistory] = useState<DownloadEntry[]>([]);
const [activePresetFormat, setActivePresetFormat] = useState<OutputFormat>('pdf');
// Register toolbar
useEffect(() => {
registerItems('reports', [
{ id: 'new-report', plugin: 'reports', label: t('reports.newTemplate', 'Neuer Report'), group: 'actions', onClick: startNewTemplate, icon: <Plus className="w-3.5 h-3.5" strokeWidth={2} /> },
]);
return () => unregisterPlugin('reports');
}, [registerItems, unregisterPlugin, t]);
// Select a template for editing
const selectTemplate = (tpl: ReportTemplate) => {
setSelectedTemplate(tpl);
@@ -274,7 +285,7 @@ export function ReportsPage() {
{/* Main 3-column layout */}
<div className="flex flex-1 gap-4 min-h-0" id="reports-content">
{/* Left: Template List */}
<div className="w-64 flex-shrink-0 bg-white rounded-lg shadow-sm border border-secondary-200 flex flex-col" data-testid="template-list-panel">
<ResizablePanel initialWidth={256} minWidth={200} maxWidth={400} className="bg-white rounded-lg shadow-sm border border-secondary-200 flex flex-col flex-shrink-0" data-testid="template-list-panel">
<div className="flex items-center justify-between p-3 border-b border-secondary-200">
<h2 className="text-sm font-semibold text-secondary-700">{t('reports.templates', 'Templates')}</h2>
<button
@@ -321,7 +332,7 @@ export function ReportsPage() {
</ul>
)}
</div>
</div>
</ResizablePanel>
{/* Center: Template Editor */}
<div className="flex-1 bg-white rounded-lg shadow-sm border border-secondary-200 flex flex-col" data-testid="template-editor-panel">