Dashboard & Reports
Dashboard
Das Dashboard (/admin) zeigt eine Übersicht der wichtigsten Kennzahlen mit konfigurierbarem Datumsbereich.
Konfiguration
File: Dashboard.php
class Dashboard extends BaseDashboard
{
protected static ?string $navigationIcon = 'heroicon-o-home';
protected static ?int $navigationSort = -2;
public function getHeaderWidgets(): array
{
return [
DateRangeFilter::class,
];
}
public function getWidgets(): array
{
return [
OrdersOverviewWidget::class,
NewRegistrationRequestsWidget::class,
RecentReviewsWidget::class,
];
}
}
Widget-Layout
| Widget | Beschreibung | Span |
|---|---|---|
| DateRangeFilter | Globaler Datumsbereich-Selektor | Header |
| OrdersOverviewWidget | Stat-Cards mit Aufträgen/Umsatz | Full |
| NewRegistrationRequestsWidget | Tabelle Registrierungsanfragen | Full |
| RecentReviewsWidget | Aktuelle Bewertungen | Full |
DateRangeFilter
File: DateRangeFilter.php
Der DateRangeFilter ist ein shared Widget, das Session-basiert den Datumsbereich für alle Dashboard-Widgets steuert.
Vordefinierte Bereiche
| Label | Bereich |
|---|---|
| Heute | Aktueller Tag |
| Gestern | Vorheriger Tag |
| Letzte 7 Tage | Letzte Woche |
| Letzte 30 Tage | Letzter Monat |
| Dieser Monat | Aktueller Monat |
| Letzter Monat | Vorheriger Monat |
| Dieses Jahr | Aktuelles Jahr |
| Letztes Jahr | Vorheriges Jahr |
Event-Kommunikation
// Widget dispatcht Event bei Filteränderung
public function applyFilter(): void
{
$dates = explode(' - ', $this->dateRange);
$start = Carbon::createFromFormat('Y-m-d', $dates[0])->startOfDay();
$end = Carbon::createFromFormat('Y-m-d', $dates[1])->endOfDay();
// In Session speichern
session(['dashboard_date_range' => [
'start' => $start->toDateString(),
'end' => $end->toDateString(),
]]);
// Event für andere Widgets
$this->dispatch('dateRangeUpdated', start: $start, end: $end);
}
In anderen Widgets lauschen
use Livewire\Attributes\On;
class OrdersOverviewWidget extends Widget
{
public ?string $startDate = null;
public ?string $endDate = null;
public function mount(): void
{
$this->loadDateRange();
}
#[On('dateRangeUpdated')]
public function updateDateRange($start, $end): void
{
$this->startDate = $start;
$this->endDate = $end;
}
protected function loadDateRange(): void
{
$range = session('dashboard_date_range');
if ($range) {
$this->startDate = $range['start'];
$this->endDate = $range['end'];
} else {
// Default: Letzte 30 Tage
$this->startDate = now()->subDays(30)->toDateString();
$this->endDate = now()->toDateString();
}
}
}
OrdersOverviewWidget
File: OrdersOverviewWidget.php
Zeigt 3 Stat-Cards mit Trends und Mini-Charts.
Stat-Cards
| Card | Beschreibung | Chart |
|---|---|---|
| Aufträge im Zeitraum | Gesamtzahl mit Trend vs. Vorperiode | Linien-Chart |
| Aktive Aufträge | Open + Assigned + Query + NotMatched | Mini-Chart |
| Umsatz im Zeitraum | Summe abgeschlossener Aufträge | Revenue-Chart |
Polling
protected static ?string $pollingInterval = '60s';
Trend-Berechnung
protected function calculateTrend(int $current, int $previous): array
{
if ($previous === 0) {
return ['value' => 0, 'direction' => 'flat'];
}
$change = (($current - $previous) / $previous) * 100;
return [
'value' => abs(round($change, 1)),
'direction' => $change > 0 ? 'up' : ($change < 0 ? 'down' : 'flat'),
];
}
NewRegistrationRequestsWidget
File: NewRegistrationRequestsWidget.php
Tabelle mit neuen Registrierungsanfragen (State = Requested).
Query
protected function getTableQuery(): Builder
{
return User::query()
->where('state', Requested::class)
->orderBy('created_at', 'desc')
->limit(10);
}
Spalten
| Spalte | Beschreibung |
|---|---|
| created_at | Registrierungsdatum |
| name | Name mit E-Mail |
| roles | Rollen-Badge |
| phone | Telefonnummer |
| email_verified_at | Verifiziert-Icon |
Actions
| Action | Beschreibung |
|---|---|
| approve | Benutzer aktivieren (State → Active) |
| view | Zur Benutzer-Detailseite |
Reports-Seite
File: Reports.php
Die Reports-Seite (/admin/reports) bietet erweiterte Auswertungen und Datenexport.
Konfiguration
class Reports extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-chart-bar';
protected static ?string $navigationLabel = 'Berichte';
protected static ?string $title = 'Auswertungen & Kennzahlen';
protected static ?int $navigationSort = 2;
}
Widget-Layout
public function getWidgets(): array
{
return [
KeyMetricsWidget::class, // Fullscreen
CategoryDistributionWidget::class, // Fullscreen
InterpreterLanguagesWidget::class, // Half
LanguageStatsWidget::class, // Half
ClientCountriesWidget::class, // Full
LocationsMapWidget::class, // Fullscreen
];
}
public function getWidgetsColumns(): int|string|array
{
return [
'md' => 2,
'xl' => 4,
];
}
Excel-Export
protected function getHeaderActions(): array
{
return [
Action::make('export_excel')
->label('Als Excel exportieren')
->icon('heroicon-o-arrow-down-tray')
->action(function () {
$dateRange = session('dashboard_date_range', [
'start' => now()->subDays(30)->toDateString(),
'end' => now()->toDateString(),
]);
return Excel::download(
new ReportsExport($dateRange['start'], $dateRange['end']),
'auswertungen-' . now()->format('Y-m-d') . '.xlsx'
);
}),
];
}
Report Widgets
KeyMetricsWidget
Übersicht der wichtigsten Kennzahlen als Stat-Cards.
CategoryDistributionWidget
Gestapeltes Balkendiagramm der Auftragsverteilung nach Kategorien.
InterpreterLanguagesWidget
Verteilung der Sprachkenntnisse unter Sprachmittlern.
LanguageStatsWidget
Sprachstatistiken der Aufträge (meistgefragte Sprachen).
ClientCountriesWidget
Herkunftsländer der Klienten als Balkendiagramm.
LocationsMapWidget
File: LocationsMapWidget.php
Google Maps mit Clustering der Auftragsstandorte.
use Cheesegrits\FilamentGoogleMaps\Widgets\MapWidget;
class LocationsMapWidget extends MapWidget
{
protected static ?string $heading = 'Auftragsstandorte';
protected static ?int $sort = 6;
protected int | string | array $columnSpan = 'full';
protected function getMarkers(): array
{
return Order::query()
->whereNotNull('location_latitude')
->whereNotNull('location_longitude')
->get()
->map(fn ($order) => [
'lat' => $order->location_latitude,
'lng' => $order->location_longitude,
'title' => $order->order_number,
])
->toArray();
}
}
Custom Widget erstellen
Stat Widget
namespace App\Filament\Widgets;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class MyStatsWidget extends BaseWidget
{
protected function getStats(): array
{
return [
Stat::make('Unique Label', 'Value')
->description('Description text')
->descriptionIcon('heroicon-m-arrow-trending-up')
->chart([7, 2, 10, 3, 15, 4, 17])
->color('success'),
];
}
}
Chart Widget
namespace App\Filament\Widgets;
use Filament\Widgets\ChartWidget;
class MyChartWidget extends ChartWidget
{
protected static ?string $heading = 'Chart';
protected static ?int $sort = 2;
protected function getData(): array
{
return [
'datasets' => [
[
'label' => 'Blog posts',
'data' => [0, 10, 5, 2, 21, 32, 45, 74, 65, 45, 77, 89],
],
],
'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
];
}
protected function getType(): string
{
return 'line'; // 'bar', 'pie', 'doughnut', etc.
}
}