Files
WebAPP/resources/views/admin/finances/index.blade.php
Rhino 4eaf2368af Finanzverwaltung und Saison-System
Neues Einnahmen-/Ausgaben-Modul mit Kategorie-Filter, Monats-Charts und
Saison-basierter Filterung. Saison-Verwaltung im Admin-Bereich mit
Möglichkeit zum Wechsel der aktuellen Saison.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:48:20 +01:00

244 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<x-layouts.admin :title="__('admin.finances_title')">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold">{{ __('admin.finances_title') }}</h1>
<a href="{{ route('admin.finances.create') }}" class="inline-flex items-center gap-1.5 bg-blue-600 text-white px-4 py-2 rounded-md text-sm font-medium hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
{{ __('admin.new_finance') }}
</a>
</div>
{{-- Filter --}}
<form method="GET" action="{{ route('admin.finances.index') }}" class="bg-white rounded-lg shadow p-4 mb-6">
<div class="flex flex-wrap items-end gap-3">
{{-- Saison --}}
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.season') }}</label>
<select name="season_id" class="px-2 py-1.5 border border-gray-300 rounded-md text-sm" onchange="this.form.submit()">
<option value="">{{ __('admin.all_seasons') }}</option>
@foreach ($seasons as $season)
<option value="{{ $season->id }}" {{ request('season_id', $activeSeason?->id) == $season->id ? 'selected' : '' }}>
{{ $season->name }}{{ $season->is_current ? ' ●' : '' }}
</option>
@endforeach
</select>
</div>
{{-- Jahr --}}
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.filter_year') }}</label>
<select name="year" class="px-2 py-1.5 border border-gray-300 rounded-md text-sm" onchange="this.form.submit()">
<option value=""></option>
@foreach ($years as $y)
<option value="{{ $y }}" {{ request('year') == $y ? 'selected' : '' }}>{{ $y }}</option>
@endforeach
</select>
</div>
{{-- Team --}}
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('ui.team') }}</label>
<select name="team_id" class="px-2 py-1.5 border border-gray-300 rounded-md text-sm" onchange="this.form.submit()">
<option value="">{{ __('ui.all_teams') }}</option>
@foreach ($teams as $team)
<option value="{{ $team->id }}" {{ request('team_id') == $team->id ? 'selected' : '' }}>{{ $team->name }}</option>
@endforeach
</select>
</div>
{{-- Typ --}}
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.finance_type') }}</label>
<select name="type" class="px-2 py-1.5 border border-gray-300 rounded-md text-sm" onchange="this.form.submit()">
<option value="">{{ __('admin.finance_all_types') }}</option>
<option value="income" {{ request('type') === 'income' ? 'selected' : '' }}>{{ __('admin.finance_income') }}</option>
<option value="expense" {{ request('type') === 'expense' ? 'selected' : '' }}>{{ __('admin.finance_expense') }}</option>
</select>
</div>
{{-- Kategorie --}}
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.finance_category') }}</label>
<select name="category" class="px-2 py-1.5 border border-gray-300 rounded-md text-sm" onchange="this.form.submit()">
<option value="">{{ __('admin.finance_all_categories') }}</option>
@foreach (\App\Enums\FinanceCategory::cases() as $cat)
<option value="{{ $cat->value }}" {{ request('category') === $cat->value ? 'selected' : '' }}>{{ $cat->label() }}</option>
@endforeach
</select>
</div>
</div>
</form>
{{-- Bilanz-Cards --}}
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
<div class="bg-green-50 rounded-lg shadow p-4 text-center">
<div class="text-xs font-medium text-green-600 uppercase tracking-wider mb-1">{{ __('admin.finance_total_income') }}</div>
<div class="text-2xl font-bold text-green-700">{{ number_format($totalIncome / 100, 2, ',', '.') }} </div>
</div>
<div class="bg-red-50 rounded-lg shadow p-4 text-center">
<div class="text-xs font-medium text-red-600 uppercase tracking-wider mb-1">{{ __('admin.finance_total_expense') }}</div>
<div class="text-2xl font-bold text-red-700">{{ number_format($totalExpense / 100, 2, ',', '.') }} </div>
</div>
<div class="{{ $balance >= 0 ? 'bg-green-50' : 'bg-red-50' }} rounded-lg shadow p-4 text-center">
<div class="text-xs font-medium {{ $balance >= 0 ? 'text-green-600' : 'text-red-600' }} uppercase tracking-wider mb-1">{{ __('admin.finance_balance') }}</div>
<div class="text-2xl font-bold {{ $balance >= 0 ? 'text-green-700' : 'text-red-700' }}">
{{ $balance >= 0 ? '+' : '' }}{{ number_format($balance / 100, 2, ',', '.') }}
</div>
</div>
</div>
{{-- Charts --}}
@if ($totalIncome > 0 || $totalExpense > 0)
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
{{-- Monatliche Uebersicht --}}
@if (!empty($chartMonthly['labels']))
<div class="bg-white rounded-lg shadow p-4">
<h3 class="text-sm font-semibold text-gray-700 mb-3">{{ __('admin.finance_chart_monthly') }}</h3>
<div class="relative" style="height: 250px;">
<canvas id="chartMonthly"></canvas>
</div>
</div>
@endif
{{-- Kategorie-Aufschluesselung --}}
<div class="bg-white rounded-lg shadow p-4">
<h3 class="text-sm font-semibold text-gray-700 mb-3">{{ __('admin.finance_chart_categories') }}</h3>
<div class="grid grid-cols-2 gap-4">
@if (!empty($chartCategories['income']['data']))
<div>
<p class="text-xs text-center text-green-600 font-medium mb-2">{{ __('admin.finance_income') }}</p>
<div class="relative" style="height: 200px;">
<canvas id="chartCatIncome"></canvas>
</div>
</div>
@endif
@if (!empty($chartCategories['expense']['data']))
<div>
<p class="text-xs text-center text-red-600 font-medium mb-2">{{ __('admin.finance_expense') }}</p>
<div class="relative" style="height: 200px;">
<canvas id="chartCatExpense"></canvas>
</div>
</div>
@endif
</div>
</div>
</div>
@endif
{{-- Tabelle --}}
<div class="bg-white rounded-lg shadow overflow-hidden overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b">
<tr>
<th class="text-left px-4 py-3 font-medium text-gray-600">{{ __('admin.finance_date') }}</th>
<th class="text-left px-4 py-3 font-medium text-gray-600">{{ __('admin.finance_type') }}</th>
<th class="text-left px-4 py-3 font-medium text-gray-600">{{ __('admin.finance_category') }}</th>
<th class="text-left px-4 py-3 font-medium text-gray-600">{{ __('ui.team') }}</th>
<th class="text-left px-4 py-3 font-medium text-gray-600">{{ __('admin.finance_title') }}</th>
<th class="text-right px-4 py-3 font-medium text-gray-600">{{ __('admin.finance_amount') }}</th>
<th class="text-right px-4 py-3 font-medium text-gray-600">{{ __('admin.actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@forelse ($finances as $entry)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 whitespace-nowrap">{{ $entry->date->format('d.m.Y') }}</td>
<td class="px-4 py-3">
@if ($entry->type === \App\Enums\FinanceType::Income)
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">{{ __('admin.finance_income') }}</span>
@else
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">{{ __('admin.finance_expense') }}</span>
@endif
</td>
<td class="px-4 py-3">{{ $entry->category->label() }}</td>
<td class="px-4 py-3 text-gray-500">{{ $entry->team?->name ?? '' }}</td>
<td class="px-4 py-3">
{{ $entry->title }}
@if ($entry->notes)
<span class="text-gray-400 text-xs ml-1" title="{{ $entry->notes }}">💬</span>
@endif
</td>
<td class="px-4 py-3 text-right font-medium whitespace-nowrap {{ $entry->type === \App\Enums\FinanceType::Income ? 'text-green-700' : 'text-red-700' }}">
{{ $entry->type === \App\Enums\FinanceType::Income ? '+' : '-' }}{{ $entry->formatted_amount }}
</td>
<td class="px-4 py-3 text-right whitespace-nowrap space-x-2">
<a href="{{ route('admin.finances.edit', $entry) }}" class="text-blue-600 hover:underline text-sm">{{ __('ui.edit') }}</a>
<form method="POST" action="{{ route('admin.finances.destroy', $entry) }}" class="inline" onsubmit="return confirm(@js(__('admin.finance_confirm_delete')))">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:underline text-sm">{{ __('ui.delete') }}</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-8 text-center text-gray-500">{{ __('admin.finance_no_entries') }}</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- Pagination --}}
@if ($finances->hasPages())
<div class="mt-4">{{ $finances->links() }}</div>
@endif
{{-- Chart.js --}}
@if ($totalIncome > 0 || $totalExpense > 0)
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"
integrity="sha384-vsrfeLOOY6KuIYKDlmVH5UiBmgIdB1oEf7p01YgWHuqmOHfZr374+odEv96n9tNC"
crossorigin="anonymous"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const monthlyData = @js($chartMonthly);
const catData = @js($chartCategories);
// Monatliche Uebersicht
if (monthlyData.labels.length && document.getElementById('chartMonthly')) {
new Chart(document.getElementById('chartMonthly'), {
type: 'bar',
data: {
labels: monthlyData.labels,
datasets: [
{ label: @js(__('admin.finance_income')), data: monthlyData.income, backgroundColor: '#3e7750', borderRadius: 3 },
{ label: @js(__('admin.finance_expense')), data: monthlyData.expense, backgroundColor: '#8f504b', borderRadius: 3 }
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: { y: { beginAtZero: true, ticks: { callback: v => v + ' €' } } },
plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, font: { size: 11 } } } }
}
});
}
// Kategorie-Charts
const doughnutOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } } }
};
if (catData.income.data.length && document.getElementById('chartCatIncome')) {
new Chart(document.getElementById('chartCatIncome'), {
type: 'doughnut',
data: { labels: catData.income.labels, datasets: [{ data: catData.income.data, backgroundColor: catData.income.colors }] },
options: doughnutOptions
});
}
if (catData.expense.data.length && document.getElementById('chartCatExpense')) {
new Chart(document.getElementById('chartCatExpense'), {
type: 'doughnut',
data: { labels: catData.expense.labels, datasets: [{ data: catData.expense.data, backgroundColor: catData.expense.colors }] },
options: doughnutOptions
});
}
});
</script>
@endpush
@endif
</x-layouts.admin>