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" String(50), nullable=False, default="csv"
) )
created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) 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): class ReportInstance(Base, TenantMixin, OwnedMixin):
+1
View File
@@ -24,6 +24,7 @@ export interface ReportTemplate {
deleted_at?: string | null; deleted_at?: string | null;
created_at?: string | null; created_at?: string | null;
updated_at?: string | null; updated_at?: string | null;
folder_id?: string | null;
} }
export interface TemplateCreateInput { export interface TemplateCreateInput {
+14 -3
View File
@@ -1,5 +1,5 @@
import { asError } from '@/utils/errorTypes'; import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
BarChart3, BarChart3,
@@ -20,6 +20,8 @@ import clsx from 'clsx';
import { PrintButton } from '@/components/common/PrintButton'; import { PrintButton } from '@/components/common/PrintButton';
import { printPdfBlob } from '@/utils/print'; import { printPdfBlob } from '@/utils/print';
import { useToast } from '@/components/ui/Toast'; import { useToast } from '@/components/ui/Toast';
import { ResizablePanel } from '@/components/ui/ResizablePanel';
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
import { import {
useReportTemplates, useReportTemplates,
useCreateReportTemplate, useCreateReportTemplate,
@@ -58,6 +60,7 @@ interface DownloadEntry {
export function ReportsPage() { export function ReportsPage() {
const { t } = useTranslation(); const { t } = useTranslation();
const toast = useToast(); const toast = useToast();
const { registerItems, unregisterPlugin } = usePluginToolbarStore();
// Data hooks // Data hooks
const { data: templates = [], isLoading: templatesLoading } = useReportTemplates(); const { data: templates = [], isLoading: templatesLoading } = useReportTemplates();
@@ -80,6 +83,14 @@ export function ReportsPage() {
const [downloadHistory, setDownloadHistory] = useState<DownloadEntry[]>([]); const [downloadHistory, setDownloadHistory] = useState<DownloadEntry[]>([]);
const [activePresetFormat, setActivePresetFormat] = useState<OutputFormat>('pdf'); 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 // Select a template for editing
const selectTemplate = (tpl: ReportTemplate) => { const selectTemplate = (tpl: ReportTemplate) => {
setSelectedTemplate(tpl); setSelectedTemplate(tpl);
@@ -274,7 +285,7 @@ export function ReportsPage() {
{/* Main 3-column layout */} {/* Main 3-column layout */}
<div className="flex flex-1 gap-4 min-h-0" id="reports-content"> <div className="flex flex-1 gap-4 min-h-0" id="reports-content">
{/* Left: Template List */} {/* 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"> <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> <h2 className="text-sm font-semibold text-secondary-700">{t('reports.templates', 'Templates')}</h2>
<button <button
@@ -321,7 +332,7 @@ export function ReportsPage() {
</ul> </ul>
)} )}
</div> </div>
</div> </ResizablePanel>
{/* Center: Template Editor */} {/* 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"> <div className="flex-1 bg-white rounded-lg shadow-sm border border-secondary-200 flex flex-col" data-testid="template-editor-panel">