Files
WebAPP/resources/views/dashboard.blade.php
Rhino 2e24a40d68 Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill
- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er)
- Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link
- Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest
- Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen)
- Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:30:37 +01:00

317 lines
15 KiB
PHP
Executable File

<x-layouts.app :title="__('ui.dashboard')">
<h1 class="text-2xl font-bold mb-6">{{ __('events.hello_user', ['name' => auth()->user()->name]) }}</h1>
{{-- Kalender --}}
<div x-data="calendarApp()" class="mb-8">
{{-- Header: View-Toggle + Navigation --}}
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4">
<h2 class="text-lg font-semibold">{{ __('events.calendar') }}</h2>
<div class="flex items-center gap-2">
<button @click="goToToday()" class="px-3 py-1.5 text-xs border border-gray-300 rounded-md hover:bg-gray-50">{{ __('events.today') }}</button>
<div class="flex bg-gray-100 rounded-md p-0.5">
<button @click="view = 'month'" :class="view === 'month' ? 'bg-white shadow text-gray-900' : 'text-gray-500 hover:text-gray-700'" class="px-3 py-1.5 text-xs font-medium rounded-md transition">{{ __('events.month_view') }}</button>
<button @click="view = 'year'" :class="view === 'year' ? 'bg-white shadow text-gray-900' : 'text-gray-500 hover:text-gray-700'" class="px-3 py-1.5 text-xs font-medium rounded-md transition">{{ __('events.year_view') }}</button>
</div>
</div>
</div>
{{-- ===== MONATSANSICHT ===== --}}
<template x-if="view === 'month'">
<div class="bg-white rounded-lg shadow">
{{-- Monats-Navigation --}}
<div class="flex items-center justify-between px-4 py-3 border-b">
<button @click="prevMonth()" class="p-1 hover:bg-gray-100 rounded-md">
<svg class="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
</button>
<h3 class="text-base font-semibold text-gray-900" x-text="monthName + ' ' + currentYear"></h3>
<button @click="nextMonth()" class="p-1 hover:bg-gray-100 rounded-md">
<svg class="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
</button>
</div>
{{-- Wochentage --}}
<div class="grid grid-cols-7 border-b">
<template x-for="day in weekDayNames" :key="day">
<div class="px-1 py-2 text-center text-xs font-medium text-gray-500 uppercase" x-text="day"></div>
</template>
</div>
{{-- Tage --}}
<div class="grid grid-cols-7">
<template x-for="(day, index) in calendarDays" :key="index">
<div
:class="{
'bg-gray-50 text-gray-400': !day.currentMonth,
'bg-blue-50': day.isToday,
'min-h-[80px] sm:min-h-[100px]': true
}"
class="border-b border-r p-1 text-xs"
>
<div class="flex items-center justify-between mb-0.5">
<span
:class="day.isToday ? 'bg-blue-600 text-white w-6 h-6 flex items-center justify-center rounded-full text-xs font-bold' : 'text-gray-700 px-1'"
x-text="day.dayNum"
></span>
</div>
<div class="space-y-0.5">
<template x-for="evt in eventsForDate(day.dateStr)" :key="evt.id">
<a :href="evt.url" class="flex items-center gap-0.5 truncate rounded px-1 py-0.5 text-[10px] leading-tight font-medium hover:opacity-80" :class="eventBgClass(evt.type)">
<span x-text="evt.time + ' ' + evt.typeLabel"></span>
<span class="ml-auto flex gap-px shrink-0 tabular-nums">
<span class="text-green-700" x-text="evt.tl.y"></span>
<span class="text-red-700" x-text="evt.tl.n"></span>
<span class="opacity-50" x-text="evt.tl.o"></span>
</span>
</a>
</template>
</div>
</div>
</template>
</div>
</div>
</template>
{{-- ===== JAHRESANSICHT ===== --}}
<template x-if="view === 'year'">
<div>
{{-- Jahres-Navigation --}}
<div class="flex items-center justify-center gap-4 mb-4">
<button @click="currentYear--" class="p-1 hover:bg-gray-100 rounded-md">
<svg class="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
</button>
<h3 class="text-base font-semibold text-gray-900" x-text="currentYear"></h3>
<button @click="currentYear++" class="p-1 hover:bg-gray-100 rounded-md">
<svg class="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
</button>
</div>
{{-- 12 Mini-Monate --}}
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
<template x-for="m in 12" :key="m">
<div class="bg-white rounded-lg shadow p-3 cursor-pointer hover:shadow-md transition-shadow" @click="currentMonth = m - 1; view = 'month'">
<h4 class="text-xs font-semibold text-gray-700 mb-2" x-text="getMonthName(m - 1)"></h4>
{{-- Mini-Wochentage --}}
<div class="grid grid-cols-7 gap-px mb-1">
<template x-for="wd in weekDayLetters" :key="wd">
<div class="text-center text-[9px] text-gray-400" x-text="wd"></div>
</template>
</div>
{{-- Mini-Tage --}}
<div class="grid grid-cols-7 gap-px">
<template x-for="(d, i) in miniMonthDays(m - 1)" :key="i">
<div class="flex items-center justify-center h-4 w-full">
<template x-if="d.dayNum">
<span
class="w-3.5 h-3.5 flex items-center justify-center rounded-full text-[8px] leading-none"
:class="d.isToday ? 'bg-blue-600 text-white font-bold' : (d.eventType ? eventDotClass(d.eventType) : 'text-gray-600')"
x-text="d.dayNum"
></span>
</template>
</div>
</template>
</div>
</div>
</template>
</div>
</div>
</template>
</div>
<script>
function calendarApp() {
const today = new Date();
return {
view: 'month',
currentMonth: today.getMonth(),
currentYear: today.getFullYear(),
events: @js($calendarEvents),
locale: document.documentElement.lang || 'de',
// Index der Events nach Datum für schnellen Zugriff
_eventIndex: null,
get eventIndex() {
if (!this._eventIndex) {
this._eventIndex = {};
this.events.forEach(e => {
if (!this._eventIndex[e.date]) this._eventIndex[e.date] = [];
this._eventIndex[e.date].push(e);
});
}
return this._eventIndex;
},
get monthName() {
return this.getMonthName(this.currentMonth);
},
getMonthName(month) {
return new Date(2024, month).toLocaleDateString(this.locale, { month: 'long' });
},
get weekDayNames() {
const days = [];
// Montag = 1 als erster Tag
for (let i = 1; i <= 7; i++) {
const d = new Date(2024, 0, i); // 2024-01-01 ist Montag
days.push(d.toLocaleDateString(this.locale, { weekday: 'short' }));
}
return days;
},
get weekDayLetters() {
return this.weekDayNames.map(d => d.charAt(0).toUpperCase());
},
get calendarDays() {
const year = this.currentYear;
const month = this.currentMonth;
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
// Wochentag des 1. (0=So, 1=Mo, ..., 6=Sa) → Umrechnung auf Mo=0
let startWeekDay = firstDay.getDay() - 1;
if (startWeekDay < 0) startWeekDay = 6;
const days = [];
// Tage vom Vormonat
const prevMonthLast = new Date(year, month, 0);
for (let i = startWeekDay - 1; i >= 0; i--) {
const dayNum = prevMonthLast.getDate() - i;
const d = new Date(year, month - 1, dayNum);
days.push({
dayNum: dayNum,
dateStr: this.formatDate(d),
currentMonth: false,
isToday: this.isToday(d)
});
}
// Tage des aktuellen Monats
for (let d = 1; d <= lastDay.getDate(); d++) {
const date = new Date(year, month, d);
days.push({
dayNum: d,
dateStr: this.formatDate(date),
currentMonth: true,
isToday: this.isToday(date)
});
}
// Tage des nächsten Monats (Rest auffüllen bis Vielfaches von 7)
const remaining = 7 - (days.length % 7);
if (remaining < 7) {
for (let d = 1; d <= remaining; d++) {
const date = new Date(year, month + 1, d);
days.push({
dayNum: d,
dateStr: this.formatDate(date),
currentMonth: false,
isToday: this.isToday(date)
});
}
}
return days;
},
miniMonthDays(month) {
const year = this.currentYear;
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
let startWeekDay = firstDay.getDay() - 1;
if (startWeekDay < 0) startWeekDay = 6;
const days = [];
// Leere Zellen vor dem 1.
for (let i = 0; i < startWeekDay; i++) {
days.push({ dayNum: 0, eventType: null, isToday: false });
}
// Tage
for (let d = 1; d <= lastDay.getDate(); d++) {
const date = new Date(year, month, d);
const dateStr = this.formatDate(date);
const dayEvents = this.eventIndex[dateStr];
days.push({
dayNum: d,
eventType: dayEvents ? dayEvents[0].type : null,
isToday: this.isToday(date)
});
}
return days;
},
eventsForDate(dateStr) {
return this.eventIndex[dateStr] || [];
},
formatDate(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return y + '-' + m + '-' + d;
},
isToday(date) {
return date.getFullYear() === today.getFullYear()
&& date.getMonth() === today.getMonth()
&& date.getDate() === today.getDate();
},
prevMonth() {
if (this.currentMonth === 0) {
this.currentMonth = 11;
this.currentYear--;
} else {
this.currentMonth--;
}
},
nextMonth() {
if (this.currentMonth === 11) {
this.currentMonth = 0;
this.currentYear++;
} else {
this.currentMonth++;
}
},
goToToday() {
this.currentMonth = today.getMonth();
this.currentYear = today.getFullYear();
},
// CSS-Klassen für Events in der Monatsansicht
eventBgClass(type) {
const map = {
home_game: 'bg-blue-100 text-blue-800',
away_game: 'bg-indigo-100 text-indigo-800',
training: 'bg-green-100 text-green-800',
tournament: 'bg-purple-100 text-purple-800',
meeting: 'bg-yellow-100 text-yellow-800',
other: 'bg-gray-100 text-gray-800'
};
return map[type] || 'bg-gray-100 text-gray-800';
},
// CSS-Klassen für Punkte in der Jahresansicht
eventDotClass(type) {
const map = {
home_game: 'bg-blue-500 text-white',
away_game: 'bg-indigo-500 text-white',
training: 'bg-green-500 text-white',
tournament: 'bg-purple-500 text-white',
meeting: 'bg-yellow-500 text-white',
other: 'bg-gray-400 text-white'
};
return map[type] || 'bg-gray-400 text-white';
}
};
}
</script>
</x-layouts.app>