diff --git a/alembic/versions/0139_reports_folder_id.py b/alembic/versions/0139_reports_folder_id.py new file mode 100644 index 0000000..64c22f7 --- /dev/null +++ b/alembic/versions/0139_reports_folder_id.py @@ -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") diff --git a/app/plugins/builtins/report_generator/models.py b/app/plugins/builtins/report_generator/models.py index 8533b50..9bd9540 100644 --- a/app/plugins/builtins/report_generator/models.py +++ b/app/plugins/builtins/report_generator/models.py @@ -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): diff --git a/frontend/src/api/reports.ts b/frontend/src/api/reports.ts index 3c7ef7e..5502344 100644 --- a/frontend/src/api/reports.ts +++ b/frontend/src/api/reports.ts @@ -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 { diff --git a/frontend/src/pages/Reports.tsx b/frontend/src/pages/Reports.tsx index ed6a0e1..c815a9e 100644 --- a/frontend/src/pages/Reports.tsx +++ b/frontend/src/pages/Reports.tsx @@ -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([]); const [activePresetFormat, setActivePresetFormat] = useState('pdf'); + // Register toolbar + useEffect(() => { + registerItems('reports', [ + { id: 'new-report', plugin: 'reports', label: t('reports.newTemplate', 'Neuer Report'), group: 'actions', onClick: startNewTemplate, icon: }, + ]); + 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 */}
{/* Left: Template List */} -
+

{t('reports.templates', 'Templates')}

-
+ {/* Center: Template Editor */}