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"
|
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):
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user