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>
This commit is contained in:
316
resources/views/dashboard.blade.php
Executable file
316
resources/views/dashboard.blade.php
Executable file
@@ -0,0 +1,316 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user