UI-Verbesserungen, PWA-Icons, Branding und Settings-Erweiterung

Invertiertes Logo für Admin-Navbar, neue PWA-Icons, Manifest-Updates,
Tailwind-Config-Extraktion, Farb-/Namenseinstellungen im Admin-Bereich
und diverse Layout-Optimierungen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-02 23:49:12 +01:00
parent 4eaf2368af
commit ee89141628
30 changed files with 401 additions and 27 deletions

View File

@@ -1,6 +1,12 @@
# Handball Team Manager <p align="center">
<img src="public/images/vereinos_logo.png" alt="VereinsOS Logo" width="200">
</p>
**Open-Source-Webanwendung zur Verwaltung von Handball-Jugendmannschaften** <h1 align="center">VereinsOS</h1>
<p align="center">
<strong>Open-Source-Webanwendung zur Verwaltung von Handball-Jugendmannschaften</strong>
</p>
Eine moderne, mehrsprachige Web-App für Trainer, Elternvertreter und Familien, um Termine, Spieler, Fahrgemeinschaften, Statistiken und Vereinsorganisation an einem Ort zu bündeln. Optimiert für Shared Hosting (kein SSH nötig) und als Progressive Web App (PWA) auf dem Smartphone nutzbar. Eine moderne, mehrsprachige Web-App für Trainer, Elternvertreter und Familien, um Termine, Spieler, Fahrgemeinschaften, Statistiken und Vereinsorganisation an einem Ort zu bündeln. Optimiert für Shared Hosting (kein SSH nötig) und als Progressive Web App (PWA) auf dem Smartphone nutzbar.

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\ActivityLog; use App\Models\ActivityLog;
use App\Models\FileCategory; use App\Models\FileCategory;
use App\Models\Season;
use App\Models\Setting; use App\Models\Setting;
use App\Services\HtmlSanitizerService; use App\Services\HtmlSanitizerService;
use App\Services\SupportApiService; use App\Services\SupportApiService;
@@ -78,10 +79,12 @@ class SettingsController extends Controller
'from_name' => config('mail.from.name'), 'from_name' => config('mail.from.name'),
]; ];
$seasons = Season::orderByDesc('start_date')->get();
return view('admin.settings.edit', compact( return view('admin.settings.edit', compact(
'settings', 'eventDefaults', 'fileCategories', 'visibilitySettings', 'settings', 'eventDefaults', 'fileCategories', 'visibilitySettings',
'isRegistered', 'installationId', 'updateInfo', 'isRegistered', 'installationId', 'updateInfo',
'availableLocales', 'localeSettings', 'mailConfig' 'availableLocales', 'localeSettings', 'mailConfig', 'seasons'
)); ));
} }

View File

@@ -227,6 +227,8 @@ HTML;
$visibilitySettings = [ $visibilitySettings = [
['key' => 'visibility_statistics_coach', 'label' => 'Statistik: Trainer', 'type' => 'number', 'value' => '1'], ['key' => 'visibility_statistics_coach', 'label' => 'Statistik: Trainer', 'type' => 'number', 'value' => '1'],
['key' => 'visibility_statistics_parent_rep', 'label' => 'Statistik: Elternvertretung', 'type' => 'number', 'value' => '1'], ['key' => 'visibility_statistics_parent_rep', 'label' => 'Statistik: Elternvertretung', 'type' => 'number', 'value' => '1'],
['key' => 'visibility_finances_coach', 'label' => 'Finanzen: Trainer', 'type' => 'number', 'value' => '1'],
['key' => 'visibility_finances_parent_rep', 'label' => 'Finanzen: Elternvertretung', 'type' => 'number', 'value' => '1'],
['key' => 'visibility_catering_history_coach', 'label' => 'Catering-Verlauf: Trainer', 'type' => 'number', 'value' => '1'], ['key' => 'visibility_catering_history_coach', 'label' => 'Catering-Verlauf: Trainer', 'type' => 'number', 'value' => '1'],
['key' => 'visibility_catering_history_parent_rep', 'label' => 'Catering-Verlauf: Elternvertretung', 'type' => 'number', 'value' => '1'], ['key' => 'visibility_catering_history_parent_rep', 'label' => 'Catering-Verlauf: Elternvertretung', 'type' => 'number', 'value' => '1'],
]; ];

View File

@@ -100,6 +100,22 @@ return [
'rueckraum_rechts' => 'ظي', 'rueckraum_rechts' => 'ظي',
'kreislaeufer' => 'دو', 'kreislaeufer' => 'دو',
], ],
'finance_type' => [
'income' => 'إيراد',
'expense' => 'مصروف',
],
'finance_category' => [
'catering' => 'تقديم الطعام',
'sponsoring' => 'رعاية',
'membership' => 'رسوم العضوية',
'tournament_fees' => 'رسوم البطولات',
'equipment' => 'معدات',
'transport' => 'نقل',
'venue_rental' => 'إيجار القاعة',
'training_material' => 'مواد التدريب',
'events' => 'فعاليات',
'other' => 'أخرى',
],
], ],
'locales' => [ 'locales' => [
'de' => 'Deutsch', 'de' => 'Deutsch',

View File

@@ -115,6 +115,22 @@ return [
'rueckraum_rechts' => 'RR', 'rueckraum_rechts' => 'RR',
'kreislaeufer' => 'KL', 'kreislaeufer' => 'KL',
], ],
'finance_type' => [
'income' => 'Einnahme',
'expense' => 'Ausgabe',
],
'finance_category' => [
'catering' => 'Catering',
'sponsoring' => 'Sponsoring',
'membership' => 'Mitgliedsbeiträge',
'tournament_fees' => 'Turniergebühren',
'equipment' => 'Ausrüstung',
'transport' => 'Transport/Fahrtkosten',
'venue_rental' => 'Hallenmiete',
'training_material' => 'Trainingsmaterial',
'events' => 'Veranstaltungen',
'other' => 'Sonstiges',
],
], ],
// Sprachen // Sprachen

View File

@@ -99,6 +99,22 @@ return [
'rueckraum_rechts' => 'RB', 'rueckraum_rechts' => 'RB',
'kreislaeufer' => 'PV', 'kreislaeufer' => 'PV',
], ],
'finance_type' => [
'income' => 'Income',
'expense' => 'Expense',
],
'finance_category' => [
'catering' => 'Catering',
'sponsoring' => 'Sponsoring',
'membership' => 'Membership Fees',
'tournament_fees' => 'Tournament Fees',
'equipment' => 'Equipment',
'transport' => 'Transport',
'venue_rental' => 'Venue Rental',
'training_material' => 'Training Material',
'events' => 'Events',
'other' => 'Other',
],
], ],
'locales' => [ 'locales' => [
'de' => 'Deutsch', 'de' => 'Deutsch',

View File

@@ -100,6 +100,22 @@ return [
'rueckraum_rechts' => 'PR', 'rueckraum_rechts' => 'PR',
'kreislaeufer' => 'KO', 'kreislaeufer' => 'KO',
], ],
'finance_type' => [
'income' => 'Przychód',
'expense' => 'Wydatek',
],
'finance_category' => [
'catering' => 'Catering',
'sponsoring' => 'Sponsoring',
'membership' => 'Składki członkowskie',
'tournament_fees' => 'Opłaty turniejowe',
'equipment' => 'Sprzęt',
'transport' => 'Transport',
'venue_rental' => 'Wynajem hali',
'training_material' => 'Materiały treningowe',
'events' => 'Imprezy',
'other' => 'Inne',
],
], ],
'locales' => [ 'locales' => [
'de' => 'Deutsch', 'de' => 'Deutsch',

View File

@@ -100,6 +100,22 @@ return [
'rueckraum_rechts' => 'ПП', 'rueckraum_rechts' => 'ПП',
'kreislaeufer' => 'ЛН', 'kreislaeufer' => 'ЛН',
], ],
'finance_type' => [
'income' => 'Доход',
'expense' => 'Расход',
],
'finance_category' => [
'catering' => 'Кейтеринг',
'sponsoring' => 'Спонсорство',
'membership' => 'Членские взносы',
'tournament_fees' => 'Турнирные сборы',
'equipment' => 'Оборудование',
'transport' => 'Транспорт',
'venue_rental' => 'Аренда зала',
'training_material' => 'Тренировочный инвентарь',
'events' => 'Мероприятия',
'other' => 'Прочее',
],
], ],
'locales' => [ 'locales' => [
'de' => 'Deutsch', 'de' => 'Deutsch',

View File

@@ -100,6 +100,22 @@ return [
'rueckraum_rechts' => 'SĞÇ', 'rueckraum_rechts' => 'SĞÇ',
'kreislaeufer' => 'PV', 'kreislaeufer' => 'PV',
], ],
'finance_type' => [
'income' => 'Gelir',
'expense' => 'Gider',
],
'finance_category' => [
'catering' => 'Catering',
'sponsoring' => 'Sponsorluk',
'membership' => 'Üyelik Aidatları',
'tournament_fees' => 'Turnuva Ücretleri',
'equipment' => 'Ekipman',
'transport' => 'Ulaşım',
'venue_rental' => 'Salon Kirası',
'training_material' => 'Antrenman Malzemeleri',
'events' => 'Etkinlikler',
'other' => 'Diğer',
],
], ],
'locales' => [ 'locales' => [
'de' => 'Deutsch', 'de' => 'Deutsch',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -128,13 +128,16 @@ if (!file_exists($basePath . '/storage/installed')) {
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Installation — Systemcheck</title> <title>Installation — Systemcheck</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config={theme:{extend:{colors:{blue:{50:'#eef1f5',100:'#d9dfe8',200:'#b7c1d1',300:'#8e9db3',400:'#677a95',500:'#4a5e7a',600:'#2D3441',700:'#252b36',800:'#1e222b',900:'#161a21',950:'#0e1117'},green:{50:'#eef4f0',100:'#d6e7da',200:'#afd0b8',300:'#7fb38e',400:'#579469',500:'#3e7750',600:'#305f3f',700:'#284d34',800:'#223e2b',900:'#1c3324',950:'#0e1c13'},red:{50:'#f6f0f0',100:'#eddddc',200:'#dbbcba',300:'#c3918e',400:'#a86b67',500:'#8f504b',600:'#76403b',700:'#613532',800:'#502d2a',900:'#432725',950:'#241413'}}}}}
</script>
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
</head> </head>
<body class="min-h-screen bg-gray-100 flex flex-col"> <body class="min-h-screen bg-gray-100 flex flex-col">
<main class="flex-1 flex items-center justify-center px-4 py-12"> <main class="flex-1 flex items-center justify-center px-4 py-12">
<div class="w-full max-w-2xl"> <div class="w-full max-w-2xl">
<div class="text-center mb-6"> <div class="text-center mb-6">
<img src="/images/logo_sg_woelfe.png" alt="Logo" class="mx-auto h-20 mb-3" onerror="this.style.display='none'"> <img src="/images/vereinos_logo.png" alt="VereinsOS" class="mx-auto h-20 mb-3" onerror="this.style.display='none'">
<h1 class="text-xl font-bold text-gray-900">Installation</h1> <h1 class="text-xl font-bold text-gray-900">Installation</h1>
</div> </div>

View File

@@ -1,7 +1,7 @@
{ {
"name": "SG Wölfe Handball", "name": "VereinsOS",
"short_name": "SG Wölfe", "short_name": "VereinsOS",
"description": "Spieltermine und Teamorganisation", "description": "Vereinsorganisation und Teamverwaltung",
"start_url": "/dashboard", "start_url": "/dashboard",
"display": "standalone", "display": "standalone",
"orientation": "portrait-primary", "orientation": "portrait-primary",

View File

@@ -1,9 +1,9 @@
// ============================================================ // ============================================================
// Service Worker SG Wölfe Handball WebApp // Service Worker VereinsOS
// Strategie: Lokale Assets cachen, HTML Network-First // Strategie: Lokale Assets cachen, HTML Network-First
// ============================================================ // ============================================================
const CACHE_NAME = 'handball-v3'; const CACHE_NAME = 'vereinos-v1';
const OFFLINE_URL = '/offline'; const OFFLINE_URL = '/offline';
// Lokale Assets, die beim Install gecached werden // Lokale Assets, die beim Install gecached werden
@@ -14,7 +14,7 @@ const PRECACHE_ASSETS = [
'/images/icon-192x192.png', '/images/icon-192x192.png',
'/images/icon-512x512.png', '/images/icon-512x512.png',
'/manifest.json', '/manifest.json',
'/images/logo_woelfe.png' '/images/vereinos_logo.png'
]; ];
// ---- INSTALL ---- // ---- INSTALL ----

View File

@@ -7,7 +7,7 @@
</div> </div>
{{-- Filter --}} {{-- Filter --}}
<form method="GET" class="mb-4 flex gap-3"> <form method="GET" class="mb-4 flex flex-wrap gap-3">
<select name="team_id" onchange="this.form.submit()" class="px-3 py-2 border border-gray-300 rounded-md text-sm"> <select name="team_id" onchange="this.form.submit()" class="px-3 py-2 border border-gray-300 rounded-md text-sm">
<option value="">{{ __('ui.all_teams') }}</option> <option value="">{{ __('ui.all_teams') }}</option>
@foreach ($teams as $team) @foreach ($teams as $team)
@@ -20,6 +20,14 @@
<option value="{{ $status->value }}" {{ request('status') === $status->value ? 'selected' : '' }}>{{ $status->label() }}</option> <option value="{{ $status->value }}" {{ request('status') === $status->value ? 'selected' : '' }}>{{ $status->label() }}</option>
@endforeach @endforeach
</select> </select>
@if ($seasons->isNotEmpty())
<select name="season_id" onchange="this.form.submit()" class="px-3 py-2 border border-gray-300 rounded-md text-sm">
<option value="">{{ __('admin.all_seasons') }}</option>
@foreach ($seasons as $season)
<option value="{{ $season->id }}" {{ request('season_id') == $season->id ? 'selected' : '' }}>{{ $season->name }}{{ $season->is_current ? ' ●' : '' }}</option>
@endforeach
</select>
@endif
</form> </form>
{{-- Event-Karten --}} {{-- Event-Karten --}}

View File

@@ -84,7 +84,7 @@
color: #d1d5db; color: #d1d5db;
} }
.link { .link {
color: #2563eb; color: #4a5e7a;
text-decoration: none; text-decoration: none;
} }

View File

@@ -31,6 +31,11 @@
{{ __('admin.settings_tab_categories') }} {{ __('admin.settings_tab_categories') }}
</button> </button>
@if (auth()->user()->isAdmin()) @if (auth()->user()->isAdmin())
<button type="button" @click="tab = 'seasons'"
:class="tab === 'seasons' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
class="whitespace-nowrap px-4 py-2.5 border-b-2 text-sm font-medium transition-colors" role="tab">
{{ __('admin.settings_tab_seasons') }}
</button>
<button type="button" @click="tab = 'visibility'" <button type="button" @click="tab = 'visibility'"
:class="tab === 'visibility' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'" :class="tab === 'visibility' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
class="whitespace-nowrap px-4 py-2.5 border-b-2 text-sm font-medium transition-colors" role="tab"> class="whitespace-nowrap px-4 py-2.5 border-b-2 text-sm font-medium transition-colors" role="tab">
@@ -438,6 +443,119 @@
</div> </div>
</div> </div>
{{-- Tab: Saisons (nur Admin) --}}
@if (auth()->user()->isAdmin())
<div x-show="tab === 'seasons'" role="tabpanel" x-data="{ editId: null, editName: '', editStart: '', editEnd: '', editCurrent: false }">
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('admin.seasons_title') }}</h3>
{{-- Bestehende Saisons --}}
@if ($seasons->isNotEmpty())
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b">
<tr>
<th class="text-left px-4 py-2 font-medium text-gray-600">{{ __('admin.season_name') }}</th>
<th class="text-left px-4 py-2 font-medium text-gray-600">{{ __('admin.season_start') }}</th>
<th class="text-left px-4 py-2 font-medium text-gray-600">{{ __('admin.season_end') }}</th>
<th class="text-center px-4 py-2 font-medium text-gray-600">{{ __('admin.season_current') }}</th>
<th class="text-right px-4 py-2 font-medium text-gray-600">{{ __('admin.actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@foreach ($seasons as $season)
<tr>
<template x-if="editId === {{ $season->id }}">
<td colspan="5" class="px-4 py-3">
<form method="POST" action="{{ route('admin.seasons.update', $season) }}" class="flex flex-wrap items-end gap-3">
@csrf
@method('PUT')
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.season_name') }}</label>
<input type="text" name="name" x-model="editName" required class="px-2 py-1.5 border border-gray-300 rounded-md text-sm w-32">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.season_start') }}</label>
<input type="date" name="start_date" x-model="editStart" required class="px-2 py-1.5 border border-gray-300 rounded-md text-sm">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.season_end') }}</label>
<input type="date" name="end_date" x-model="editEnd" required class="px-2 py-1.5 border border-gray-300 rounded-md text-sm">
</div>
<label class="flex items-center gap-1.5 text-sm">
<input type="hidden" name="is_current" value="0">
<input type="checkbox" name="is_current" value="1" x-model="editCurrent" class="rounded border-gray-300">
{{ __('admin.season_current') }}
</label>
<div class="flex gap-2">
<button type="submit" class="px-3 py-1.5 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700">{{ __('ui.save') }}</button>
<button type="button" @click="editId = null" class="px-3 py-1.5 bg-gray-100 text-gray-700 rounded-md text-sm hover:bg-gray-200">{{ __('ui.cancel') }}</button>
</div>
</form>
</td>
</template>
<template x-if="editId !== {{ $season->id }}">
<td class="px-4 py-2 font-medium">{{ $season->name }}</td>
</template>
<template x-if="editId !== {{ $season->id }}">
<td class="px-4 py-2">{{ $season->start_date->format('d.m.Y') }}</td>
</template>
<template x-if="editId !== {{ $season->id }}">
<td class="px-4 py-2">{{ $season->end_date->format('d.m.Y') }}</td>
</template>
<template x-if="editId !== {{ $season->id }}">
<td class="px-4 py-2 text-center">
@if ($season->is_current)
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">{{ __('admin.season_current') }}</span>
@endif
</td>
</template>
<template x-if="editId !== {{ $season->id }}">
<td class="px-4 py-2 text-right space-x-2">
<button type="button" @click="editId = {{ $season->id }}; editName = @js($season->name); editStart = @js($season->start_date->format('Y-m-d')); editEnd = @js($season->end_date->format('Y-m-d')); editCurrent = {{ $season->is_current ? 'true' : 'false' }}" class="text-blue-600 hover:underline text-sm">{{ __('ui.edit') }}</button>
<form method="POST" action="{{ route('admin.seasons.destroy', $season) }}" class="inline" onsubmit="return confirm('{{ __('admin.season_confirm_delete') }}')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:underline text-sm">{{ __('ui.delete') }}</button>
</form>
</td>
</template>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-sm text-gray-500 mb-6">{{ __('admin.no_seasons_yet') }}</p>
@endif
{{-- Neue Saison erstellen --}}
<h4 class="text-sm font-semibold text-gray-700 mb-3">{{ __('admin.new_season') }}</h4>
<form method="POST" action="{{ route('admin.seasons.store') }}" class="flex flex-wrap items-end gap-3">
@csrf
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.season_name') }}</label>
<input type="text" name="name" required placeholder="2025/2026" class="px-2 py-1.5 border border-gray-300 rounded-md text-sm w-32">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.season_start') }}</label>
<input type="date" name="start_date" required class="px-2 py-1.5 border border-gray-300 rounded-md text-sm">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('admin.season_end') }}</label>
<input type="date" name="end_date" required class="px-2 py-1.5 border border-gray-300 rounded-md text-sm">
</div>
<label class="flex items-center gap-1.5 text-sm">
<input type="hidden" name="is_current" value="0">
<input type="checkbox" name="is_current" value="1" class="rounded border-gray-300">
{{ __('admin.season_current') }}
</label>
<button type="submit" class="px-3 py-1.5 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700">{{ __('ui.create') }}</button>
</form>
</div>
</div>
@endif
{{-- Tab: Sichtbarkeit (nur Admin) --}} {{-- Tab: Sichtbarkeit (nur Admin) --}}
@if (auth()->user()->isAdmin()) @if (auth()->user()->isAdmin())
<div x-show="tab === 'visibility'" role="tabpanel"> <div x-show="tab === 'visibility'" role="tabpanel">
@@ -447,6 +565,7 @@
@php @php
$features = [ $features = [
'statistics' => __('admin.visibility_feature_statistics'), 'statistics' => __('admin.visibility_feature_statistics'),
'finances' => __('admin.visibility_feature_finances'),
'catering_history' => __('admin.visibility_feature_catering_history'), 'catering_history' => __('admin.visibility_feature_catering_history'),
]; ];
$roles = [ $roles = [
@@ -756,10 +875,13 @@
], ],
init() { init() {
const validTabs = ['general', 'mail', 'legal', 'defaults', 'categories', 'visibility', 'license', 'maintenance']; const validTabs = ['general', 'mail', 'legal', 'defaults', 'categories', 'seasons', 'visibility', 'license', 'maintenance'];
const urlTab = new URLSearchParams(window.location.search).get('tab');
const hash = window.location.hash.replace('#', ''); const hash = window.location.hash.replace('#', '');
const stored = sessionStorage.getItem('settings_tab'); const stored = sessionStorage.getItem('settings_tab');
if (validTabs.includes(hash)) { if (validTabs.includes(urlTab)) {
this.tab = urlTab;
} else if (validTabs.includes(hash)) {
this.tab = hash; this.tab = hash;
} else if (validTabs.includes(stored)) { } else if (validTabs.includes(stored)) {
this.tab = stored; this.tab = stored;

View File

@@ -6,6 +6,7 @@
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ __('ui.admin') }} - {{ $title ?? \App\Models\Setting::get('app_name', config('app.name')) }}</title> <title>{{ __('ui.admin') }} - {{ $title ?? \App\Models\Setting::get('app_name', config('app.name')) }}</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
@include('components.tailwind-config')
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js" integrity="sha384-9Ax3MmS9AClxJyd5/zafcXXjxmwFhZCdsT6HJoJjarvCaAkJlk5QDzjLJm+Wdx5F" crossorigin="anonymous"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js" integrity="sha384-9Ax3MmS9AClxJyd5/zafcXXjxmwFhZCdsT6HJoJjarvCaAkJlk5QDzjLJm+Wdx5F" crossorigin="anonymous"></script>
@php $favicon = \App\Models\Setting::get('app_favicon'); @endphp @php $favicon = \App\Models\Setting::get('app_favicon'); @endphp
@if ($favicon) @if ($favicon)
@@ -18,7 +19,7 @@
<meta name="theme-color" content="#1f2937"> <meta name="theme-color" content="#1f2937">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="SG Wölfe"> <meta name="apple-mobile-web-app-title" content="{{ \App\Models\Setting::get('app_name', 'VereinsOS') }}">
<link rel="apple-touch-icon" href="/images/apple-touch-icon.png"> <link rel="apple-touch-icon" href="/images/apple-touch-icon.png">
@stack('styles') @stack('styles')
</head> </head>
@@ -29,7 +30,8 @@
<div class="flex justify-between h-14"> <div class="flex justify-between h-14">
<div class="flex items-center space-x-6 rtl:space-x-reverse"> <div class="flex items-center space-x-6 rtl:space-x-reverse">
<a href="{{ route('admin.dashboard') }}" class="flex items-center gap-2 font-bold"> <a href="{{ route('admin.dashboard') }}" class="flex items-center gap-2 font-bold">
<img src="/images/logo_woelfe.png" alt="Logo" class="h-8 w-8 object-contain"> @php $logoApp = \App\Models\Setting::get('app_logo_app'); @endphp
<img src="{{ $logoApp ? asset('storage/' . $logoApp) : asset('images/vereinos_logo_white.png') }}" alt="Logo" class="h-8 w-8 object-contain">
{{ __('ui.admin') }} {{ __('ui.admin') }}
</a> </a>
{{-- Desktop nav links (ab lg sichtbar) --}} {{-- Desktop nav links (ab lg sichtbar) --}}
@@ -38,6 +40,9 @@
@if (\App\Models\Setting::isFeatureVisibleFor('statistics', auth()->user())) @if (\App\Models\Setting::isFeatureVisibleFor('statistics', auth()->user()))
<a href="{{ route('admin.statistics.index') }}" class="text-sm text-gray-300 hover:text-white {{ request()->routeIs('admin.statistics.*') ? 'text-white font-semibold' : '' }}">{{ __('admin.nav_statistics') }}</a> <a href="{{ route('admin.statistics.index') }}" class="text-sm text-gray-300 hover:text-white {{ request()->routeIs('admin.statistics.*') ? 'text-white font-semibold' : '' }}">{{ __('admin.nav_statistics') }}</a>
@endif @endif
@if (\App\Models\Setting::isFeatureVisibleFor('finances', auth()->user()))
<a href="{{ route('admin.finances.index') }}" class="text-sm text-gray-300 hover:text-white {{ request()->routeIs('admin.finances.*') ? 'text-white font-semibold' : '' }}">{{ __('admin.nav_finances') }}</a>
@endif
@if (auth()->user()->isStaff()) @if (auth()->user()->isStaff())
<a href="{{ route('admin.teams.index') }}" class="text-sm text-gray-300 hover:text-white {{ request()->routeIs('admin.teams.*') ? 'text-white font-semibold' : '' }}">{{ __('admin.nav_teams') }}</a> <a href="{{ route('admin.teams.index') }}" class="text-sm text-gray-300 hover:text-white {{ request()->routeIs('admin.teams.*') ? 'text-white font-semibold' : '' }}">{{ __('admin.nav_teams') }}</a>
<a href="{{ route('admin.players.index') }}" class="text-sm text-gray-300 hover:text-white {{ request()->routeIs('admin.players.*') ? 'text-white font-semibold' : '' }}">{{ __('admin.nav_players') }}</a> <a href="{{ route('admin.players.index') }}" class="text-sm text-gray-300 hover:text-white {{ request()->routeIs('admin.players.*') ? 'text-white font-semibold' : '' }}">{{ __('admin.nav_players') }}</a>
@@ -104,6 +109,9 @@
@if (\App\Models\Setting::isFeatureVisibleFor('statistics', auth()->user())) @if (\App\Models\Setting::isFeatureVisibleFor('statistics', auth()->user()))
<a href="{{ route('admin.statistics.index') }}" class="block py-2 text-sm text-gray-300">{{ __('admin.nav_statistics') }}</a> <a href="{{ route('admin.statistics.index') }}" class="block py-2 text-sm text-gray-300">{{ __('admin.nav_statistics') }}</a>
@endif @endif
@if (\App\Models\Setting::isFeatureVisibleFor('finances', auth()->user()))
<a href="{{ route('admin.finances.index') }}" class="block py-2 text-sm text-gray-300">{{ __('admin.nav_finances') }}</a>
@endif
@if (auth()->user()->isStaff()) @if (auth()->user()->isStaff())
<a href="{{ route('admin.teams.index') }}" class="block py-2 text-sm text-gray-300">{{ __('admin.nav_teams') }}</a> <a href="{{ route('admin.teams.index') }}" class="block py-2 text-sm text-gray-300">{{ __('admin.nav_teams') }}</a>
<a href="{{ route('admin.players.index') }}" class="block py-2 text-sm text-gray-300">{{ __('admin.nav_players') }}</a> <a href="{{ route('admin.players.index') }}" class="block py-2 text-sm text-gray-300">{{ __('admin.nav_players') }}</a>

View File

@@ -6,6 +6,7 @@
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $title ?? \App\Models\Setting::get('app_name', config('app.name')) }}</title> <title>{{ $title ?? \App\Models\Setting::get('app_name', config('app.name')) }}</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
@include('components.tailwind-config')
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js" integrity="sha384-9Ax3MmS9AClxJyd5/zafcXXjxmwFhZCdsT6HJoJjarvCaAkJlk5QDzjLJm+Wdx5F" crossorigin="anonymous"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js" integrity="sha384-9Ax3MmS9AClxJyd5/zafcXXjxmwFhZCdsT6HJoJjarvCaAkJlk5QDzjLJm+Wdx5F" crossorigin="anonymous"></script>
@php $favicon = \App\Models\Setting::get('app_favicon'); @endphp @php $favicon = \App\Models\Setting::get('app_favicon'); @endphp
@if ($favicon) @if ($favicon)
@@ -18,7 +19,7 @@
<meta name="theme-color" content="#1f2937"> <meta name="theme-color" content="#1f2937">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="SG Wölfe"> <meta name="apple-mobile-web-app-title" content="{{ \App\Models\Setting::get('app_name', 'VereinsOS') }}">
<link rel="apple-touch-icon" href="/images/apple-touch-icon.png"> <link rel="apple-touch-icon" href="/images/apple-touch-icon.png">
@stack('styles') @stack('styles')
</head> </head>
@@ -30,7 +31,7 @@
<div class="flex items-center space-x-6 rtl:space-x-reverse"> <div class="flex items-center space-x-6 rtl:space-x-reverse">
@php $logoApp = \App\Models\Setting::get('app_logo_app'); @endphp @php $logoApp = \App\Models\Setting::get('app_logo_app'); @endphp
<a href="{{ route('dashboard') }}" class="flex items-center gap-2 font-bold text-gray-900"> <a href="{{ route('dashboard') }}" class="flex items-center gap-2 font-bold text-gray-900">
<img src="{{ $logoApp ? asset('storage/' . $logoApp) : asset('images/logo_woelfe.png') }}" alt="Logo" class="h-8 w-8 object-contain"> <img src="{{ $logoApp ? asset('storage/' . $logoApp) : asset('images/vereinos_logo.png') }}" alt="Logo" class="h-8 w-8 object-contain">
{{ \App\Models\Setting::get('app_name', config('app.name')) }} {{ \App\Models\Setting::get('app_name', config('app.name')) }}
</a> </a>
<div class="hidden sm:flex items-center space-x-6 rtl:space-x-reverse"> <div class="hidden sm:flex items-center space-x-6 rtl:space-x-reverse">

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>{{ $title ?? \App\Models\Setting::get('app_name', config('app.name')) }}</title> <title>{{ $title ?? \App\Models\Setting::get('app_name', config('app.name')) }}</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
@include('components.tailwind-config')
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js" integrity="sha384-9Ax3MmS9AClxJyd5/zafcXXjxmwFhZCdsT6HJoJjarvCaAkJlk5QDzjLJm+Wdx5F" crossorigin="anonymous"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js" integrity="sha384-9Ax3MmS9AClxJyd5/zafcXXjxmwFhZCdsT6HJoJjarvCaAkJlk5QDzjLJm+Wdx5F" crossorigin="anonymous"></script>
@php $favicon = \App\Models\Setting::get('app_favicon'); @endphp @php $favicon = \App\Models\Setting::get('app_favicon'); @endphp
@if ($favicon) @if ($favicon)
@@ -17,7 +18,7 @@
<meta name="theme-color" content="#1f2937"> <meta name="theme-color" content="#1f2937">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="SG Wölfe"> <meta name="apple-mobile-web-app-title" content="{{ \App\Models\Setting::get('app_name', 'VereinsOS') }}">
<link rel="apple-touch-icon" href="/images/apple-touch-icon.png"> <link rel="apple-touch-icon" href="/images/apple-touch-icon.png">
</head> </head>
<body class="min-h-screen bg-gray-100 flex flex-col"> <body class="min-h-screen bg-gray-100 flex flex-col">
@@ -26,7 +27,7 @@
<div class="text-center mb-8"> <div class="text-center mb-8">
@php $logoLogin = \App\Models\Setting::get('app_logo_login'); @endphp @php $logoLogin = \App\Models\Setting::get('app_logo_login'); @endphp
<a href="{{ auth()->check() ? route('dashboard') : route('login') }}"> <a href="{{ auth()->check() ? route('dashboard') : route('login') }}">
<img src="{{ $logoLogin ? asset('storage/' . $logoLogin) : asset('images/logo_sg_woelfe.png') }}" alt="Logo" class="mx-auto h-24 mb-3 object-contain"> <img src="{{ $logoLogin ? asset('storage/' . $logoLogin) : asset('images/vereinos_logo.png') }}" alt="Logo" class="mx-auto h-24 mb-3 object-contain">
</a> </a>
<h1 class="text-2xl font-bold text-gray-900">{{ \App\Models\Setting::get('app_name', config('app.name')) }}</h1> <h1 class="text-2xl font-bold text-gray-900">{{ \App\Models\Setting::get('app_name', config('app.name')) }}</h1>
@php $slogan = \App\Models\Setting::get('app_slogan'); @endphp @php $slogan = \App\Models\Setting::get('app_slogan'); @endphp

View File

@@ -3,8 +3,9 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Installation Handball WebApp</title> <title>Installation VereinsOS</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
@include('components.tailwind-config')
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js" integrity="sha384-9Ax3MmS9AClxJyd5/zafcXXjxmwFhZCdsT6HJoJjarvCaAkJlk5QDzjLJm+Wdx5F" crossorigin="anonymous"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js" integrity="sha384-9Ax3MmS9AClxJyd5/zafcXXjxmwFhZCdsT6HJoJjarvCaAkJlk5QDzjLJm+Wdx5F" crossorigin="anonymous"></script>
<link rel="icon" href="{{ asset('favicon.ico') }}"> <link rel="icon" href="{{ asset('favicon.ico') }}">
</head> </head>
@@ -13,7 +14,7 @@
<div class="w-full max-w-2xl"> <div class="w-full max-w-2xl">
{{-- Logo --}} {{-- Logo --}}
<div class="text-center mb-6"> <div class="text-center mb-6">
<img src="/images/logo_sg_woelfe.png" alt="Logo" class="mx-auto h-20 mb-3"> <img src="/images/vereinos_logo.png" alt="VereinsOS" class="mx-auto h-20 mb-3">
<h1 class="text-xl font-bold text-gray-900">Installation</h1> <h1 class="text-xl font-bold text-gray-900">Installation</h1>
</div> </div>
@@ -63,7 +64,7 @@
</div> </div>
</main> </main>
<footer class="text-center py-3 text-xs text-gray-400"> <footer class="text-center py-3 text-xs text-gray-400">
Handball WebApp &mdash; Installation VereinsOS &mdash; Installation
</footer> </footer>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,115 @@
{{-- Zentrale Tailwind-Farbkonfiguration VereinsOS --}}
<script>
tailwind.config = {
theme: {
extend: {
colors: {
blue: {
50: '#eef1f5',
100: '#d9dfe8',
200: '#b7c1d1',
300: '#8e9db3',
400: '#677a95',
500: '#4a5e7a',
600: '#2D3441',
700: '#252b36',
800: '#1e222b',
900: '#161a21',
950: '#0e1117',
},
green: {
50: '#eef4f0',
100: '#d6e7da',
200: '#afd0b8',
300: '#7fb38e',
400: '#579469',
500: '#3e7750',
600: '#305f3f',
700: '#284d34',
800: '#223e2b',
900: '#1c3324',
950: '#0e1c13',
},
red: {
50: '#f6f0f0',
100: '#eddddc',
200: '#dbbcba',
300: '#c3918e',
400: '#a86b67',
500: '#8f504b',
600: '#76403b',
700: '#613532',
800: '#502d2a',
900: '#432725',
950: '#241413',
},
yellow: {
50: '#f6f3ec',
100: '#ebe4d0',
200: '#d8c9a2',
300: '#c2a86e',
400: '#ae8e49',
500: '#99783a',
600: '#806130',
700: '#674c29',
800: '#553f26',
900: '#483523',
950: '#271c11',
},
amber: {
50: '#f7f3ec',
100: '#ede3cf',
200: '#dbc7a0',
300: '#c6a46a',
400: '#b58a43',
500: '#a07537',
600: '#875e2e',
700: '#6e4a28',
800: '#5b3d25',
900: '#4d3321',
950: '#2a1b10',
},
purple: {
50: '#f2f0f5',
100: '#e2dde9',
200: '#c7bed5',
300: '#a596ba',
400: '#84729e',
500: '#6b5a84',
600: '#57486c',
700: '#483c59',
800: '#3c334a',
900: '#332b3e',
950: '#1e1824',
},
indigo: {
50: '#eff1f6',
100: '#dbdfec',
200: '#bac2da',
300: '#939fc1',
400: '#717ea6',
500: '#57638d',
600: '#465073',
700: '#3a4260',
800: '#31394f',
900: '#2b3143',
950: '#191d28',
},
teal: {
50: '#eef3f3',
100: '#d5e5e3',
200: '#adccc8',
300: '#7eada8',
400: '#578e88',
500: '#41746e',
600: '#345d58',
700: '#2c4c48',
800: '#263f3c',
900: '#213433',
950: '#111e1d',
},
},
},
},
}
</script>

View File

@@ -1,5 +1,13 @@
<x-layouts.app :title="__('ui.dashboard')"> <x-layouts.app :title="__('ui.dashboard')">
<h1 class="text-2xl font-bold mb-6">{{ __('events.hello_user', ['name' => auth()->user()->name]) }}</h1> <div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">{{ __('events.hello_user', ['name' => auth()->user()->name]) }}</h1>
@if (auth()->user()->isStaff())
<a href="{{ route('admin.events.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_event') }}
</a>
@endif
</div>
{{-- Kalender --}} {{-- Kalender --}}
<div x-data="calendarApp()" class="mb-8"> <div x-data="calendarApp()" class="mb-8">

View File

@@ -119,7 +119,7 @@
<div> <div>
<label for="mail_from_name" class="block text-sm font-medium text-gray-700 mb-1">Absender-Name <span class="text-gray-400 font-normal">(optional)</span></label> <label for="mail_from_name" class="block text-sm font-medium text-gray-700 mb-1">Absender-Name <span class="text-gray-400 font-normal">(optional)</span></label>
<input type="text" name="mail_from_name" id="mail_from_name" value="{{ old('mail_from_name') }}" <input type="text" name="mail_from_name" id="mail_from_name" value="{{ old('mail_from_name') }}"
placeholder="z.B. SG Woelfe Handball" placeholder="z.B. Mein Verein"
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offline SG Wölfe Handball</title> <title>Offline VereinsOS</title>
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#1f2937"> <meta name="theme-color" content="#1f2937">
<link rel="icon" href="/images/icon-192x192.png"> <link rel="icon" href="/images/icon-192x192.png">
@@ -40,7 +40,7 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<img src="/images/icon-192x192.png" alt="SG Wölfe" class="logo"> <img src="/images/icon-192x192.png" alt="VereinsOS" class="logo">
<h1>Keine Internetverbindung</h1> <h1>Keine Internetverbindung</h1>
<p> <p>
Die Seite kann gerade nicht geladen werden. Die Seite kann gerade nicht geladen werden.