Files
myeasycms-v2/packages/features/verbandsverwaltung/src/components/hierarchy-report.tsx

269 lines
9.2 KiB
TypeScript

'use client';
import {
Building2,
Users,
CalendarDays,
BookOpen,
Euro,
TrendingUp,
} from 'lucide-react';
import { formatNumber, formatCurrencyAmount } from '@kit/shared/formatters';
import { Badge } from '@kit/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
interface HierarchyReportProps {
summary: {
total_orgs: number;
total_active_members: number;
total_members: number;
new_members_this_year: number;
total_upcoming_events: number;
total_active_courses: number;
total_open_invoices: number;
total_open_invoice_amount: number;
total_sepa_batches_this_year: number;
};
report: Array<{
org_id: string;
org_name: string;
org_slug: string | null;
depth: number;
active_members: number;
inactive_members: number;
total_members: number;
new_members_this_year: number;
active_courses: number;
upcoming_events: number;
open_invoices: number;
open_invoice_amount: number;
sepa_batches_this_year: number;
}>;
}
function getDepthLabel(depth: number) {
switch (depth) {
case 0:
return 'Verband';
case 1:
return 'Unterverband';
default:
return 'Verein';
}
}
function getDepthVariant(depth: number) {
switch (depth) {
case 0:
return 'default' as const;
case 1:
return 'secondary' as const;
default:
return 'outline' as const;
}
}
export function HierarchyReport({ summary, report }: HierarchyReportProps) {
return (
<div className="flex w-full flex-col gap-6">
{/* Summary Cards */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-muted-foreground text-sm font-medium">
Organisationen
</p>
<p className="text-2xl font-bold">
{formatNumber(summary.total_orgs)}
</p>
</div>
<div className="bg-primary/10 text-primary rounded-full p-3">
<Building2 className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-muted-foreground text-sm font-medium">
Aktive Mitglieder
</p>
<p className="text-2xl font-bold">
{formatNumber(summary.total_active_members)}
</p>
<p className="text-muted-foreground text-xs">
von {formatNumber(summary.total_members)} gesamt
</p>
</div>
<div className="bg-primary/10 text-primary rounded-full p-3">
<Users className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-muted-foreground text-sm font-medium">
Neue Mitglieder (Jahr)
</p>
<p className="text-2xl font-bold">
{formatNumber(summary.new_members_this_year)}
</p>
</div>
<div className="bg-primary/10 text-primary rounded-full p-3">
<TrendingUp className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-muted-foreground text-sm font-medium">
Anstehende Termine
</p>
<p className="text-2xl font-bold">
{formatNumber(summary.total_upcoming_events)}
</p>
</div>
<div className="bg-primary/10 text-primary rounded-full p-3">
<CalendarDays className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-muted-foreground text-sm font-medium">
Aktive Kurse
</p>
<p className="text-2xl font-bold">
{formatNumber(summary.total_active_courses)}
</p>
</div>
<div className="bg-primary/10 text-primary rounded-full p-3">
<BookOpen className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-muted-foreground text-sm font-medium">
Offene Rechnungen
</p>
<p className="text-2xl font-bold">
{formatCurrencyAmount(summary.total_open_invoice_amount)}
</p>
<p className="text-muted-foreground text-xs">
{formatNumber(summary.total_open_invoices)} Rechnungen
</p>
</div>
<div className="bg-primary/10 text-primary rounded-full p-3">
<Euro className="h-5 w-5" />
</div>
</div>
</CardContent>
</Card>
</div>
{/* Per-Org Report Table */}
<Card>
<CardHeader>
<CardTitle>Bericht pro Organisation</CardTitle>
</CardHeader>
<CardContent>
{report.length === 0 ? (
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-12 text-center">
<h3 className="text-lg font-semibold">
Keine Organisationen vorhanden
</h3>
<p className="text-muted-foreground mt-1 max-w-sm text-sm">
Die Hierarchie enthält noch keine Organisationen.
</p>
</div>
) : (
<div className="overflow-x-auto rounded-md border">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left font-medium">Name</th>
<th className="p-3 text-left font-medium">Ebene</th>
<th className="p-3 text-right font-medium">
Aktive Mitgl.
</th>
<th className="p-3 text-right font-medium">Gesamt</th>
<th className="p-3 text-right font-medium">Neu (Jahr)</th>
<th className="p-3 text-right font-medium">Kurse</th>
<th className="p-3 text-right font-medium">Termine</th>
<th className="p-3 text-right font-medium">
Offene Rechn.
</th>
<th className="p-3 text-right font-medium">
Offener Betrag
</th>
</tr>
</thead>
<tbody>
{report.map((row) => (
<tr key={row.org_id} className="hover:bg-muted/30 border-b">
<td className="p-3 font-medium">
<span style={{ paddingLeft: `${row.depth * 1.25}rem` }}>
{row.org_name}
</span>
</td>
<td className="p-3">
<Badge variant={getDepthVariant(row.depth)}>
{getDepthLabel(row.depth)}
</Badge>
</td>
<td className="p-3 text-right">
{formatNumber(row.active_members)}
</td>
<td className="p-3 text-right">
{formatNumber(row.total_members)}
</td>
<td className="p-3 text-right">
{formatNumber(row.new_members_this_year)}
</td>
<td className="p-3 text-right">
{formatNumber(row.active_courses)}
</td>
<td className="p-3 text-right">
{formatNumber(row.upcoming_events)}
</td>
<td className="p-3 text-right">
{formatNumber(row.open_invoices)}
</td>
<td className="p-3 text-right">
{formatCurrencyAmount(row.open_invoice_amount)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
</div>
);
}