feat: Kalender Hover-Highlight (Web) + manuelle Event-Erstellung (App)

Web: Hover über Zeitslots zeigt leichten Indigo-Highlight an
App: + Button im Kalender-Header öffnet neuen Termin Modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
boban 2026-04-21 00:53:38 +02:00
parent fa52148021
commit 3aa9eb1633
12 changed files with 63 additions and 4 deletions

View File

@ -0,0 +1,26 @@
<?php
use App\Models\Translation;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
$keys = [
'events.edit' => ['de' => 'Termin bearbeiten', 'en' => 'Edit event'],
'events.new' => ['de' => 'Neuer Termin', 'en' => 'New event'],
];
foreach ($keys as $key => $locales) {
foreach ($locales as $locale => $value) {
Translation::updateOrCreate(
['key' => $key, 'locale' => $locale],
['value' => $value]
);
}
}
}
public function down(): void {}
};

View File

@ -244,6 +244,8 @@ class TranslationSeeder extends Seeder
'calendar.no_events' => ['de' => 'Keine Termine', 'en' => 'No events'], 'calendar.no_events' => ['de' => 'Keine Termine', 'en' => 'No events'],
// ── Event Form ────────────────────────── // ── Event Form ──────────────────────────
'events.edit' => ['de' => 'Termin bearbeiten', 'en' => 'Edit event'],
'events.new' => ['de' => 'Neuer Termin', 'en' => 'New event'],
'events.title' => ['de' => 'Titel', 'en' => 'Title'], 'events.title' => ['de' => 'Titel', 'en' => 'Title'],
'events.title_placeholder' => ['de' => 'Titel hinzufügen', 'en' => 'Add title'], 'events.title_placeholder' => ['de' => 'Titel hinzufügen', 'en' => 'Add title'],
'events.datetime' => ['de' => 'Datum & Zeit', 'en' => 'Date & Time'], 'events.datetime' => ['de' => 'Datum & Zeit', 'en' => 'Date & Time'],

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,7 @@
] ]
}, },
"resources/css/app.css": { "resources/css/app.css": {
"file": "assets/app-BPc0iRlq.css", "file": "assets/app-YhqCCiLi.css",
"name": "app", "name": "app",
"names": [ "names": [
"app.css" "app.css"

View File

@ -13,6 +13,10 @@ document.addEventListener('alpine:init', () => {
// ── Focus (Einzelklick → Vordergrund) ──────────────────────── // ── Focus (Einzelklick → Vordergrund) ────────────────────────
focusedEventId: null, focusedEventId: null,
// ── Hover-Highlight ──────────────────────────────────────────
hoverCol: null,
hoverSlot: null,
// ── Timed-Event DnD (Week / Day) ───────────────────────────── // ── Timed-Event DnD (Week / Day) ─────────────────────────────
draggingId: null, draggingId: null,
dragOffsetY: 0, dragOffsetY: 0,
@ -53,6 +57,25 @@ document.addEventListener('alpine:init', () => {
return String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0'); return String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0');
}, },
// ═══════════════════════════════════════════════════════════════
// HOVER HIGHLIGHT (Week + Day timeline)
// ═══════════════════════════════════════════════════════════════
onTimeHover(e, col) {
if (this.draggingId) return;
const rect = e.currentTarget.getBoundingClientRect();
const relY = Math.max(0, e.clientY - rect.top);
this.hoverSlot = Math.floor(relY / SLOT_PX);
this.hoverCol = col;
},
onTimeLeave(e, col) {
if (this.hoverCol === col) {
this.hoverCol = null;
this.hoverSlot = null;
}
},
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
// CREATE ON DOUBLE-CLICK (Week + Day timeline) // CREATE ON DOUBLE-CLICK (Week + Day timeline)
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════

View File

@ -26,7 +26,15 @@
x-on:dragleave="onDragLeave($event)" x-on:dragleave="onDragLeave($event)"
x-on:drop="onDrop($event, '{{ $dayStr }}')" x-on:drop="onDrop($event, '{{ $dayStr }}')"
x-on:dblclick.self="onCreateAt($event, '{{ $dayStr }}')" x-on:dblclick.self="onCreateAt($event, '{{ $dayStr }}')"
x-on:click.self="focusedEventId = null"> x-on:click.self="focusedEventId = null"
x-on:mousemove="onTimeHover($event, {{ $colIndex }})"
x-on:mouseleave="onTimeLeave($event, {{ $colIndex }})">
{{-- Hover-Highlight --}}
<div class="absolute left-0 right-0 pointer-events-none z-[1]"
x-show="hoverCol === {{ $colIndex }} && hoverSlot !== null"
x-bind:style="hoverSlot !== null ? 'top:' + (hoverSlot * {{ $cellPx / 4 }}) + 'px' : ''"
style="height: {{ $cellPx / 4 }}px; background: rgba(99,102,241,0.06);"></div>
{{-- Stunden-Linien --}} {{-- Stunden-Linien --}}
@for($h = $hourStart; $h < $hourEnd; $h++) @for($h = $hourStart; $h < $hourEnd; $h++)

0
src/storage/app/.gitignore vendored Normal file → Executable file
View File

0
src/storage/app/private/.gitignore vendored Normal file → Executable file
View File

0
src/storage/app/public/.gitignore vendored Normal file → Executable file
View File

0
src/storage/framework/.gitignore vendored Normal file → Executable file
View File

0
src/storage/framework/testing/.gitignore vendored Normal file → Executable file
View File