feat(UI-Overhaul-Phase7): Reports UI mit ResizablePanel und PluginToolbar
Check Cross-Plugin Imports / check (push) Has been cancelled
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:
@@ -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):
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user