diff --git a/cannamanage-frontend/messages/de.json b/cannamanage-frontend/messages/de.json
index 48af0de..67372b2 100644
--- a/cannamanage-frontend/messages/de.json
+++ b/cannamanage-frontend/messages/de.json
@@ -66,7 +66,15 @@
"today": "Heute",
"trend": "+{value}% ggü. Vormonat",
"quotaUsed": "{value}% verbraucht",
- "distributionCount": "{count} Ausgaben, {grams}g"
+ "distributionCount": "{count} Ausgaben, {grams}g",
+ "outstandingPayments": "Offene Zahlungen",
+ "monthlyIncome": "Monatliches Einkommen",
+ "thisMonth": "Diesen Monat",
+ "strainsAvailable": "Sorten verfügbar",
+ "upcomingEvents": "Nächste Termine",
+ "latestAnnouncements": "Neueste Beiträge",
+ "rsvps": "Zusagen",
+ "viewAll": "Alle anzeigen"
},
"members": {
"title": "Mitgliederverwaltung",
@@ -1004,5 +1012,100 @@
"positions": "Positionen",
"active": "Aktiv",
"inactive": "Inaktiv"
+ },
+ "reportsCenter": {
+ "title": "Berichtszentrale",
+ "subtitle": "Alle gesetzlichen und internen Berichte an einem Ort generieren und verwalten.",
+ "generate": "Generieren",
+ "cancel": "Abbrechen",
+ "authorityExport": {
+ "title": "Behörden-Export",
+ "description": "Gebündelter Export aller behördlich relevanten Dokumente für ein Kalenderjahr.",
+ "button": "Behörden-Export starten",
+ "dialogTitle": "Behörden-Export erstellen",
+ "dialogDescription": "Erstellt ein passwortgeschütztes Archiv mit allen compliance-relevanten Berichten für das gewählte Jahr.",
+ "warning": "Dieser Export enthält sensible Daten und wird im Audit-Protokoll erfasst. Bitte nur bei berechtigter Anforderung durchführen.",
+ "year": "Berichtsjahr",
+ "password": "Passwort für Archiv",
+ "passwordPlaceholder": "Sicheres Passwort eingeben",
+ "confirm": "Export erstellen"
+ },
+ "categories": {
+ "finance": "Finanzen",
+ "kcang": "KCanG-Compliance",
+ "dsgvo": "DSGVO",
+ "admin": "Verwaltung"
+ },
+ "reports": {
+ "EUER": {
+ "name": "EÜR",
+ "description": "Einnahmenüberschussrechnung für den gewählten Zeitraum"
+ },
+ "KASSENBUCH_EXPORT": {
+ "name": "Kassenbuch-Export",
+ "description": "Vollständige Kassenbuchführung als PDF oder CSV"
+ },
+ "BEITRAGSBESCHEINIGUNG": {
+ "name": "Beitragsbescheinigung",
+ "description": "Bescheinigung über gezahlte Mitgliedsbeiträge"
+ },
+ "JAHRESBERICHT_BEHOERDE": {
+ "name": "Jahresbericht Behörde",
+ "description": "Gesetzlich vorgeschriebener Bericht an die zuständige Behörde"
+ },
+ "AUSGABEPROTOKOLL": {
+ "name": "Ausgabeprotokoll",
+ "description": "Protokoll aller Ausgaben im Zeitraum mit Mengen und Empfängern"
+ },
+ "VERNICHTUNGSPROTOKOLL": {
+ "name": "Vernichtungsprotokoll",
+ "description": "Dokumentation der ordnungsgemäßen Vernichtung von Cannabis"
+ },
+ "TRANSPORTZERTIFIKAT": {
+ "name": "Transportzertifikat",
+ "description": "Zertifikat für den genehmigten Transport von Cannabis"
+ },
+ "BESTANDSFUEHRUNG": {
+ "name": "Bestandsführung",
+ "description": "Aktueller Lagerbestand mit allen Ein- und Ausgängen"
+ },
+ "VERARBEITUNGSVERZEICHNIS": {
+ "name": "Verarbeitungsverzeichnis",
+ "description": "Verzeichnis aller Verarbeitungstätigkeiten gem. Art. 30 DSGVO"
+ },
+ "TOM": {
+ "name": "TOM",
+ "description": "Technische und organisatorische Maßnahmen gem. Art. 32 DSGVO"
+ },
+ "DSFA": {
+ "name": "DSFA",
+ "description": "Datenschutz-Folgenabschätzung gem. Art. 35 DSGVO"
+ },
+ "LOESCHKONZEPT": {
+ "name": "Löschkonzept",
+ "description": "Konzept zur fristgerechten Datenlöschung"
+ },
+ "DATENPANNEN_MELDUNG": {
+ "name": "Datenpannen-Meldung",
+ "description": "Vorlage zur Meldung einer Datenschutzverletzung"
+ },
+ "MITGLIEDERLISTE_REGISTER": {
+ "name": "Mitgliederliste Register",
+ "description": "Offizielle Mitgliederliste für das Vereinsregister"
+ },
+ "VORSTANDSAENDERUNG": {
+ "name": "Vorstandsänderung",
+ "description": "Meldung einer Vorstandsänderung ans Vereinsregister"
+ }
+ },
+ "history": {
+ "title": "Generierte Berichte",
+ "empty": "Noch keine Berichte generiert. Wähle oben einen Bericht aus, um zu beginnen.",
+ "report": "Bericht",
+ "format": "Format",
+ "date": "Datum",
+ "user": "Erstellt von",
+ "size": "Größe"
+ }
}
}
\ No newline at end of file
diff --git a/cannamanage-frontend/messages/en.json b/cannamanage-frontend/messages/en.json
index dc893fd..8d4f077 100644
--- a/cannamanage-frontend/messages/en.json
+++ b/cannamanage-frontend/messages/en.json
@@ -66,7 +66,15 @@
"today": "Today",
"trend": "+{value}% vs last month",
"quotaUsed": "{value}% used",
- "distributionCount": "{count} distributions, {grams}g"
+ "distributionCount": "{count} distributions, {grams}g",
+ "outstandingPayments": "Outstanding Payments",
+ "monthlyIncome": "Monthly Income",
+ "thisMonth": "This month",
+ "strainsAvailable": "strains available",
+ "upcomingEvents": "Upcoming Events",
+ "latestAnnouncements": "Latest Announcements",
+ "rsvps": "RSVPs",
+ "viewAll": "View all"
},
"members": {
"title": "Member Management",
@@ -1004,5 +1012,100 @@
"positions": "Positions",
"active": "Active",
"inactive": "Inactive"
+ },
+ "reportsCenter": {
+ "title": "Reports Center",
+ "subtitle": "Generate and manage all regulatory and internal reports in one place.",
+ "generate": "Generate",
+ "cancel": "Cancel",
+ "authorityExport": {
+ "title": "Authority Export",
+ "description": "Bundled export of all authority-relevant documents for a calendar year.",
+ "button": "Start Authority Export",
+ "dialogTitle": "Create Authority Export",
+ "dialogDescription": "Creates a password-protected archive with all compliance-relevant reports for the selected year.",
+ "warning": "This export contains sensitive data and will be logged in the audit trail. Only proceed with a legitimate request.",
+ "year": "Report Year",
+ "password": "Archive Password",
+ "passwordPlaceholder": "Enter secure password",
+ "confirm": "Create Export"
+ },
+ "categories": {
+ "finance": "Finance",
+ "kcang": "KCanG Compliance",
+ "dsgvo": "GDPR",
+ "admin": "Administration"
+ },
+ "reports": {
+ "EUER": {
+ "name": "Income Statement",
+ "description": "Simplified income statement for the selected period"
+ },
+ "KASSENBUCH_EXPORT": {
+ "name": "Cash Book Export",
+ "description": "Complete cash book as PDF or CSV"
+ },
+ "BEITRAGSBESCHEINIGUNG": {
+ "name": "Membership Fee Certificate",
+ "description": "Certificate of paid membership fees"
+ },
+ "JAHRESBERICHT_BEHOERDE": {
+ "name": "Annual Authority Report",
+ "description": "Legally required annual report to the responsible authority"
+ },
+ "AUSGABEPROTOKOLL": {
+ "name": "Distribution Log",
+ "description": "Log of all distributions in the period with amounts and recipients"
+ },
+ "VERNICHTUNGSPROTOKOLL": {
+ "name": "Destruction Protocol",
+ "description": "Documentation of proper cannabis destruction"
+ },
+ "TRANSPORTZERTIFIKAT": {
+ "name": "Transport Certificate",
+ "description": "Certificate for approved cannabis transport"
+ },
+ "BESTANDSFUEHRUNG": {
+ "name": "Inventory Report",
+ "description": "Current stock with all inflows and outflows"
+ },
+ "VERARBEITUNGSVERZEICHNIS": {
+ "name": "Processing Register",
+ "description": "Register of all processing activities per Art. 30 GDPR"
+ },
+ "TOM": {
+ "name": "TOM",
+ "description": "Technical and organizational measures per Art. 32 GDPR"
+ },
+ "DSFA": {
+ "name": "DPIA",
+ "description": "Data Protection Impact Assessment per Art. 35 GDPR"
+ },
+ "LOESCHKONZEPT": {
+ "name": "Deletion Policy",
+ "description": "Policy for timely data deletion"
+ },
+ "DATENPANNEN_MELDUNG": {
+ "name": "Data Breach Report",
+ "description": "Template for reporting a data protection violation"
+ },
+ "MITGLIEDERLISTE_REGISTER": {
+ "name": "Member Registry List",
+ "description": "Official member list for the association register"
+ },
+ "VORSTANDSAENDERUNG": {
+ "name": "Board Change Notification",
+ "description": "Notification of board change to the association register"
+ }
+ },
+ "history": {
+ "title": "Generated Reports",
+ "empty": "No reports generated yet. Select a report above to get started.",
+ "report": "Report",
+ "format": "Format",
+ "date": "Date",
+ "user": "Created by",
+ "size": "Size"
+ }
}
}
\ No newline at end of file
diff --git a/cannamanage-frontend/src/app/(dashboard-layout)/dashboard/page.tsx b/cannamanage-frontend/src/app/(dashboard-layout)/dashboard/page.tsx
index 3f71147..055fad6 100644
--- a/cannamanage-frontend/src/app/(dashboard-layout)/dashboard/page.tsx
+++ b/cannamanage-frontend/src/app/(dashboard-layout)/dashboard/page.tsx
@@ -15,7 +15,19 @@ import {
XAxis,
YAxis,
} from "recharts"
-import { Leaf, Package, Plus, TrendingUp, UserPlus, Users } from "lucide-react"
+import {
+ AlertCircle,
+ Calendar,
+ CreditCard,
+ Leaf,
+ Megaphone,
+ Package,
+ Plus,
+ TrendingUp,
+ UserPlus,
+ Users,
+ Wallet,
+} from "lucide-react"
import {
mockClubStats,
@@ -27,6 +39,58 @@ import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { CardSkeleton, TableSkeleton } from "@/components/ui/data-skeleton"
+// Mock data for new widgets (backend fallback)
+const mockOutstandingPayments = { count: 3, totalCents: 14700 }
+const mockMonthlyIncome = { totalCents: 234500 }
+const mockUpcomingEvents = [
+ {
+ id: "e1",
+ title: "Mitgliederversammlung",
+ date: "2026-06-20T18:00:00Z",
+ rsvpCount: 28,
+ },
+ {
+ id: "e2",
+ title: "Grow-Workshop",
+ date: "2026-06-22T14:00:00Z",
+ rsvpCount: 12,
+ },
+ {
+ id: "e3",
+ title: "Sommerfest",
+ date: "2026-06-28T16:00:00Z",
+ rsvpCount: 35,
+ },
+]
+const mockLatestAnnouncements = [
+ {
+ id: "a1",
+ title: "Neue Öffnungszeiten ab Juli",
+ createdAt: "2026-06-14T10:00:00Z",
+ },
+ {
+ id: "a2",
+ title: "Sorte 'Blue Dream' wieder verfügbar",
+ createdAt: "2026-06-13T15:30:00Z",
+ },
+]
+const mockAlerts: {
+ type: string
+ message: string
+ severity: "red" | "yellow" | "blue"
+}[] = [
+ {
+ type: "overdue_payments",
+ message: "3 überfällige Zahlungen",
+ severity: "red",
+ },
+ {
+ type: "compliance_deadline",
+ message: "Jahresbericht fällig in 5 Tagen",
+ severity: "yellow",
+ },
+]
+
export default function DashboardPage() {
const t = useTranslations("dashboard")
@@ -45,16 +109,18 @@ export default function DashboardPage() {
return (
- {/* KPI Cards */}
+ {/* KPI Cards — 6 total */}
{statsLoading ? (
-
+
+
+
) : (
-
+
{/* Active Members */}
@@ -106,7 +172,7 @@ export default function DashboardPage() {
{t("grams")}
- {mockStockByStrain.length} Sorten verfügbar
+ {mockStockByStrain.length} {t("strainsAvailable")}
@@ -130,6 +196,49 @@ export default function DashboardPage() {
+
+ {/* Outstanding Payments — NEW */}
+
+
+
+ {t("outstandingPayments")}
+
+
+
+
+
+ {mockOutstandingPayments.count}
+
+
+ {(mockOutstandingPayments.totalCents / 100).toLocaleString(
+ "de-DE",
+ {
+ style: "currency",
+ currency: "EUR",
+ }
+ )}
+
+
+
+
+ {/* Monthly Income — NEW */}
+
+
+
+ {t("monthlyIncome")}
+
+
+
+
+
+ {(mockMonthlyIncome.totalCents / 100).toLocaleString("de-DE", {
+ style: "currency",
+ currency: "EUR",
+ })}
+
+ {t("thisMonth")}
+
+
)}
@@ -161,100 +270,182 @@ export default function DashboardPage() {
- {/* Bottom section: Table + Chart */}
-
+ {/* Alerts — conditional */}
+ {mockAlerts.length > 0 && (
+
+ {mockAlerts.map((alert, idx) => (
+
+
+
+ {alert.message}
+
+
+ ))}
+
+ )}
+
+ {/* Middle section: Recent Distributions + Upcoming Events + Announcements */}
+
{/* Recent Distributions Table */}
-
+
{t("recentDistributions")}
{distributionsLoading ? (
-
+
) : (
-
-
-
-
- | {t("date")} |
- {t("member")} |
- {t("strain")} |
- {t("amount")} |
- {t("staff")} |
-
-
-
- {recentDistributions.map((dist) => (
-
- |
- {new Date(dist.recordedAt).toLocaleTimeString(
- "de-DE",
- {
- hour: "2-digit",
- minute: "2-digit",
- }
- )}
- |
- {dist.memberName} |
- {dist.strainName} |
- {dist.amountGrams}g |
- {dist.recordedBy} |
-
- ))}
-
-
+
+ {recentDistributions.slice(0, 5).map((dist) => (
+
+
+
+ {dist.memberName}
+
+
+ {dist.strainName}
+
+
+
+ {dist.amountGrams}g
+
+
+ ))}
)}
- {/* Stock Level Chart */}
-
+ {/* Upcoming Events — NEW */}
+
- {t("stockByStrain")}
+
+
+ {t("upcomingEvents")}
+
-
-
-
+ {mockUpcomingEvents.map((event) => (
+
-
-
-
[`${value}g`, "Bestand"]}
- />
-
- {chartData.map((_, index) => (
- |
- ))}
-
-
-
+
+ {event.title}
+
+ {new Date(event.date).toLocaleDateString("de-DE", {
+ day: "2-digit",
+ month: "2-digit",
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+
+
+ {event.rsvpCount} {t("rsvps")}
+
+
+ ))}
+
+
+
+
+ {/* Latest Announcements — NEW */}
+
+
+
+
+ {t("latestAnnouncements")}
+
+
+
+
+ {mockLatestAnnouncements.map((post) => (
+
+ {post.title}
+
+ {new Date(post.createdAt).toLocaleDateString("de-DE")}
+
+
+ ))}
+
+
+ {/* Bottom section: Chart */}
+
+
+ {t("stockByStrain")}
+
+
+
+
+
+
+
+ [`${value}g`, "Bestand"]}
+ />
+
+ {chartData.map((_, index) => (
+ |
+ ))}
+
+
+
+
+
+
)
}
diff --git a/cannamanage-frontend/src/app/(dashboard-layout)/reports-center/page.tsx b/cannamanage-frontend/src/app/(dashboard-layout)/reports-center/page.tsx
new file mode 100644
index 0000000..a52e24e
--- /dev/null
+++ b/cannamanage-frontend/src/app/(dashboard-layout)/reports-center/page.tsx
@@ -0,0 +1,472 @@
+"use client"
+
+import { useState } from "react"
+import {
+ downloadReport,
+ useAuthorityExport,
+ useGenerateReport,
+ useGeneratedReports,
+} from "@/services/compliance-reports"
+import { useTranslations } from "next-intl"
+import {
+ AlertTriangle,
+ Calendar,
+ ChartBar,
+ Download,
+ FileText,
+ Info,
+ Shield,
+ ShieldAlert,
+} from "lucide-react"
+
+import type { GeneratedReport } from "@/services/compliance-reports"
+
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+
+// Report definitions grouped by category
+const reportCategories = [
+ {
+ id: "finance",
+ icon: ChartBar,
+ reports: [
+ { type: "EUER", formats: ["PDF", "CSV"], requiresDateRange: true },
+ {
+ type: "KASSENBUCH_EXPORT",
+ formats: ["PDF", "CSV"],
+ requiresDateRange: true,
+ },
+ {
+ type: "BEITRAGSBESCHEINIGUNG",
+ formats: ["PDF"],
+ requiresDateRange: true,
+ },
+ ],
+ },
+ {
+ id: "kcang",
+ icon: Shield,
+ reports: [
+ {
+ type: "JAHRESBERICHT_BEHOERDE",
+ formats: ["PDF"],
+ requiresDateRange: true,
+ },
+ {
+ type: "AUSGABEPROTOKOLL",
+ formats: ["PDF", "CSV"],
+ requiresDateRange: true,
+ },
+ {
+ type: "VERNICHTUNGSPROTOKOLL",
+ formats: ["PDF"],
+ requiresDateRange: true,
+ },
+ {
+ type: "TRANSPORTZERTIFIKAT",
+ formats: ["PDF"],
+ requiresDateRange: false,
+ },
+ {
+ type: "BESTANDSFUEHRUNG",
+ formats: ["PDF", "CSV", "JSON"],
+ requiresDateRange: false,
+ },
+ ],
+ },
+ {
+ id: "dsgvo",
+ icon: ShieldAlert,
+ reports: [
+ {
+ type: "VERARBEITUNGSVERZEICHNIS",
+ formats: ["PDF"],
+ requiresDateRange: false,
+ },
+ { type: "TOM", formats: ["PDF"], requiresDateRange: false },
+ { type: "DSFA", formats: ["PDF"], requiresDateRange: false },
+ { type: "LOESCHKONZEPT", formats: ["PDF"], requiresDateRange: false },
+ {
+ type: "DATENPANNEN_MELDUNG",
+ formats: ["PDF"],
+ requiresDateRange: false,
+ },
+ ],
+ },
+ {
+ id: "admin",
+ icon: FileText,
+ reports: [
+ {
+ type: "MITGLIEDERLISTE_REGISTER",
+ formats: ["PDF", "CSV"],
+ requiresDateRange: false,
+ },
+ {
+ type: "VORSTANDSAENDERUNG",
+ formats: ["PDF"],
+ requiresDateRange: false,
+ },
+ ],
+ },
+]
+
+// Mock data for generated reports history
+const mockGeneratedReports: GeneratedReport[] = [
+ {
+ id: "gr-001",
+ type: "AUSGABEPROTOKOLL",
+ typeName: "Ausgabeprotokoll",
+ format: "PDF",
+ status: "COMPLETED",
+ generatedAt: "2026-06-14T10:30:00Z",
+ generatedBy: "Patrick Plate",
+ fileSize: 245000,
+ },
+ {
+ id: "gr-002",
+ type: "BESTANDSFUEHRUNG",
+ typeName: "Bestandsführung",
+ format: "CSV",
+ status: "COMPLETED",
+ generatedAt: "2026-06-13T15:20:00Z",
+ generatedBy: "Anna Schmidt",
+ fileSize: 12400,
+ },
+ {
+ id: "gr-003",
+ type: "MITGLIEDERLISTE_REGISTER",
+ typeName: "Mitgliederliste Register",
+ format: "PDF",
+ status: "COMPLETED",
+ generatedAt: "2026-06-10T09:00:00Z",
+ generatedBy: "Patrick Plate",
+ fileSize: 89000,
+ },
+]
+
+export default function ReportsCenterPage() {
+ const t = useTranslations("reportsCenter")
+ const [selectedFormats, setSelectedFormats] = useState<
+ Record
+ >({})
+ const [dateRanges, setDateRanges] = useState<
+ Record
+ >({})
+ const [authorityYear, setAuthorityYear] = useState(
+ new Date().getFullYear().toString()
+ )
+ const [authorityPassword, setAuthorityPassword] = useState("")
+ const [authorityDialogOpen, setAuthorityDialogOpen] = useState(false)
+
+ const { data: generatedReports } = useGeneratedReports(10)
+ const generateReport = useGenerateReport()
+ const authorityExport = useAuthorityExport()
+
+ const reports = generatedReports ?? mockGeneratedReports
+
+ const handleGenerate = (reportType: string, formats: string[]) => {
+ const format = (selectedFormats[reportType] || formats[0]) as
+ | "PDF"
+ | "CSV"
+ | "JSON"
+ const dateRange = dateRanges[reportType]
+
+ generateReport.mutate({
+ type: reportType,
+ format,
+ dateFrom: dateRange?.from,
+ dateTo: dateRange?.to,
+ })
+ }
+
+ const handleAuthorityExport = () => {
+ authorityExport.mutate({
+ year: parseInt(authorityYear),
+ password: authorityPassword,
+ reason: "Behördenanfrage",
+ })
+ setAuthorityDialogOpen(false)
+ setAuthorityPassword("")
+ }
+
+ return (
+
+ {/* Header */}
+
+
{t("title")}
+
{t("subtitle")}
+
+
+ {/* Behörden-Export Hero */}
+
+
+
+
+
+
+ {t("authorityExport.title")}
+
+
+ {t("authorityExport.description")}
+
+
+
+
+
+
+
+ {/* Report Categories */}
+ {reportCategories.map((category) => {
+ const Icon = category.icon
+ return (
+
+
+
+
+ {t(`categories.${category.id}`)}
+
+
+
+ {category.reports.map((report) => (
+
+
+
+ {t(`reports.${report.type}.name`)}
+
+
+ {t(`reports.${report.type}.description`)}
+
+
+
+ {/* Format badges */}
+
+ {report.formats.map((fmt) => (
+
+ setSelectedFormats((prev) => ({
+ ...prev,
+ [report.type]: fmt,
+ }))
+ }
+ >
+ {fmt}
+
+ ))}
+
+
+ {/* Date range picker */}
+ {report.requiresDateRange && (
+
+
+ setDateRanges((prev) => ({
+ ...prev,
+ [report.type]: {
+ ...prev[report.type],
+ from: e.target.value,
+ to: prev[report.type]?.to || "",
+ },
+ }))
+ }
+ />
+
+ setDateRanges((prev) => ({
+ ...prev,
+ [report.type]: {
+ ...prev[report.type],
+ from: prev[report.type]?.from || "",
+ to: e.target.value,
+ },
+ }))
+ }
+ />
+
+ )}
+
+ {/* Generate button */}
+
+
+
+ ))}
+
+
+ )
+ })}
+
+ {/* Generated Reports History */}
+
+
+
+
+ {t("history.title")}
+
+
+
+ {reports.length === 0 ? (
+
+
+
+ {t("history.empty")}
+
+
+ ) : (
+
+
+
+
+ | {t("history.report")} |
+ {t("history.format")} |
+ {t("history.date")} |
+ {t("history.user")} |
+ {t("history.size")} |
+ |
+
+
+
+ {reports.map((report) => (
+
+ | {report.typeName} |
+
+
+ {report.format}
+
+ |
+
+ {new Date(report.generatedAt).toLocaleDateString(
+ "de-DE"
+ )}
+ |
+ {report.generatedBy} |
+
+ {report.fileSize
+ ? `${(report.fileSize / 1024).toFixed(0)} KB`
+ : "—"}
+ |
+
+
+ |
+
+ ))}
+
+
+
+ )}
+
+
+
+ )
+}
diff --git a/cannamanage-frontend/src/app/(marketing)/pricing/page.tsx b/cannamanage-frontend/src/app/(marketing)/pricing/page.tsx
index b3f9f02..aae3701 100644
--- a/cannamanage-frontend/src/app/(marketing)/pricing/page.tsx
+++ b/cannamanage-frontend/src/app/(marketing)/pricing/page.tsx
@@ -159,11 +159,11 @@ export default function PricingPage() {
{/* Feature Comparison Table */}
-
+
{t("comparisonTitle")}
-
+
diff --git a/cannamanage-frontend/src/data/navigations.ts b/cannamanage-frontend/src/data/navigations.ts
index f8f124a..40fe045 100644
--- a/cannamanage-frontend/src/data/navigations.ts
+++ b/cannamanage-frontend/src/data/navigations.ts
@@ -2,7 +2,7 @@ import type { NavigationType } from "@/types"
export const navigationsData: NavigationType[] = [
{
- title: "Main",
+ title: "Betrieb",
items: [
{
title: "Dashboard",
@@ -29,16 +29,31 @@ export const navigationsData: NavigationType[] = [
href: "/grow",
iconName: "Sprout",
},
- {
- title: "Berichte",
- href: "/reports",
- iconName: "FileText",
- },
+ ],
+ },
+ {
+ title: "Kommunikation",
+ items: [
{
title: "Schwarzes Brett",
href: "/info-board",
iconName: "Megaphone",
},
+ {
+ title: "Kalender",
+ href: "/calendar",
+ iconName: "Calendar",
+ },
+ {
+ title: "Forum",
+ href: "/forum",
+ iconName: "MessageSquare",
+ },
+ ],
+ },
+ {
+ title: "Verwaltung",
+ items: [
{
title: "Finanzen",
href: "/finance",
@@ -59,16 +74,6 @@ export const navigationsData: NavigationType[] = [
href: "/board",
iconName: "Shield",
},
- {
- title: "Kalender",
- href: "/calendar",
- iconName: "Calendar",
- },
- {
- title: "Forum",
- href: "/forum",
- iconName: "MessageSquare",
- },
{
title: "Personal",
href: "/settings/staff",
@@ -79,11 +84,21 @@ export const navigationsData: NavigationType[] = [
{
title: "Compliance",
items: [
+ {
+ title: "Berichtszentrale",
+ href: "/reports-center",
+ iconName: "ChartBar",
+ },
{
title: "Protokoll",
href: "/audit-log",
iconName: "ScrollText",
},
+ {
+ title: "Berichte",
+ href: "/reports",
+ iconName: "FileText",
+ },
],
},
]
diff --git a/cannamanage-frontend/src/services/compliance-reports.ts b/cannamanage-frontend/src/services/compliance-reports.ts
new file mode 100644
index 0000000..01a8aad
--- /dev/null
+++ b/cannamanage-frontend/src/services/compliance-reports.ts
@@ -0,0 +1,147 @@
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
+
+import { apiClient, apiDownload } from "@/lib/api-client"
+
+// --- Types ---
+
+export interface ReportTypeInfo {
+ type: string
+ name: string
+ description: string
+ category: "FINANCE" | "KCANG_COMPLIANCE" | "DSGVO" | "ADMINISTRATION"
+ formats: ("PDF" | "CSV" | "JSON")[]
+ requiresDateRange: boolean
+ requiresPassword: boolean
+}
+
+export interface GenerateReportParams {
+ type: string
+ format: "PDF" | "CSV" | "JSON"
+ dateFrom?: string
+ dateTo?: string
+ year?: number
+ additionalParams?: Record
+}
+
+export interface GeneratedReport {
+ id: string
+ type: string
+ typeName: string
+ format: string
+ status: "PENDING" | "COMPLETED" | "FAILED"
+ generatedAt: string
+ generatedBy: string
+ fileSize?: number
+ downloadUrl?: string
+}
+
+export interface AuthorityExportParams {
+ year: number
+ password: string
+ reason: string
+}
+
+export interface AuthorityExportResult {
+ id: string
+ year: number
+ generatedAt: string
+ reports: { type: string; name: string; format: string }[]
+ downloadUrl: string
+}
+
+export interface ComplianceDeadline {
+ id: string
+ title: string
+ description: string
+ dueDate: string
+ area: string
+ status: "PENDING" | "OVERDUE" | "COMPLETED"
+ daysRemaining: number
+}
+
+// --- Query Hooks ---
+
+export function useReportTypes() {
+ return useQuery({
+ queryKey: ["reports", "types"],
+ queryFn: () => apiClient("/reports/types"),
+ })
+}
+
+export function useGeneratedReports(limit?: number) {
+ return useQuery({
+ queryKey: ["reports", "generated", limit],
+ queryFn: () =>
+ apiClient("/reports/generated", {
+ params: { limit: limit?.toString() },
+ }),
+ })
+}
+
+export function useComplianceDeadlines() {
+ return useQuery({
+ queryKey: ["compliance", "deadlines"],
+ queryFn: () => apiClient("/compliance/deadlines"),
+ })
+}
+
+// --- Mutation Hooks ---
+
+export function useGenerateReport() {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (params: GenerateReportParams) =>
+ apiClient("/reports/generate", {
+ method: "POST",
+ body: JSON.stringify(params),
+ }),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["reports", "generated"] })
+ },
+ })
+}
+
+export function useAuthorityExport() {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (params: AuthorityExportParams) =>
+ apiClient("/reports/authority-export", {
+ method: "POST",
+ body: JSON.stringify(params),
+ }),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["reports", "generated"] })
+ },
+ })
+}
+
+// --- Download Functions ---
+
+export async function downloadReport(reportId: string) {
+ const { blob, filename } = await apiDownload(
+ `/reports/generated/${reportId}/download`
+ )
+ triggerDownload(blob, filename || `report-${reportId}`)
+}
+
+export async function downloadAuthorityExport(exportId: string) {
+ const { blob, filename } = await apiDownload(
+ `/reports/authority-export/${exportId}/download`
+ )
+ triggerDownload(blob, filename || `behoerden-export-${exportId}.zip`)
+}
+
+// --- Helpers ---
+
+function triggerDownload(blob: Blob, filename: string) {
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement("a")
+ a.href = url
+ a.download = filename
+ document.body.appendChild(a)
+ a.click()
+ document.body.removeChild(a)
+ URL.revokeObjectURL(url)
+}