895 lines
27 KiB
PHP
895 lines
27 KiB
PHP
<?php
|
|
|
|
|
|
namespace App\Livewire\Calendar;
|
|
|
|
use App\Models\Event;
|
|
use Livewire\Component;
|
|
use Carbon\Carbon;
|
|
|
|
class Index extends Component
|
|
{
|
|
public $view = 'month';
|
|
public $date;
|
|
public $events = [];
|
|
public $search = '';
|
|
public $from = null;
|
|
public $to = null;
|
|
|
|
public $rawEvents = [];
|
|
public $holidays = [
|
|
'2026-04-06' => 'Ostermontag',
|
|
];
|
|
|
|
protected $queryString = [
|
|
'view' => ['except' => 'month'],
|
|
'date' => ['except' => ''],
|
|
'search' => ['except' => ''],
|
|
'from' => ['except' => ''],
|
|
'to' => ['except' => ''],
|
|
];
|
|
|
|
protected $listeners = ['eventCreated' => 'loadEvents'];
|
|
|
|
public function mount()
|
|
{
|
|
$tz = auth()->user()->timezone;
|
|
|
|
$this->date = $this->date
|
|
? Carbon::parse($this->date, $tz)
|
|
: now($tz);
|
|
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function updatedView()
|
|
{
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function setView($view)
|
|
{
|
|
$this->view = $view;
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function loadEvents()
|
|
{
|
|
$tz = auth()->user()->timezone;
|
|
$user = auth()->user();
|
|
|
|
// 🔥 VIEW RANGE
|
|
if ($this->view === 'month') {
|
|
$start = $this->date->copy()->startOfMonth()->startOfWeek();
|
|
$end = $this->date->copy()->endOfMonth()->endOfWeek();
|
|
} elseif ($this->view === 'week') {
|
|
$start = $this->date->copy()->startOfWeek();
|
|
$end = $this->date->copy()->endOfWeek();
|
|
} else {
|
|
$start = $this->date->copy()->startOfDay();
|
|
$end = $this->date->copy()->endOfDay();
|
|
}
|
|
|
|
$query = $user->events();
|
|
|
|
// 🔥 SEARCH
|
|
if ($this->search) {
|
|
$query->where('title', 'like', '%' . $this->search . '%');
|
|
}
|
|
|
|
// 🔥 FILTER (richtig für Multi-Day!)
|
|
if ($this->from) {
|
|
$from = Carbon::parse($this->from)->startOfDay();
|
|
|
|
$query->where(function ($q) use ($from) {
|
|
$q->where('starts_at', '>=', $from)
|
|
->orWhere('ends_at', '>=', $from);
|
|
});
|
|
}
|
|
|
|
if ($this->to) {
|
|
$to = Carbon::parse($this->to)->endOfDay();
|
|
|
|
$query->where(function ($q) use ($to) {
|
|
$q->where('starts_at', '<=', $to)
|
|
->orWhere('ends_at', '<=', $to);
|
|
});
|
|
}
|
|
|
|
// 🔥 RANGE (WICHTIG)
|
|
$rawEvents = $query->where(function ($q) use ($start, $end) {
|
|
$q->whereBetween('starts_at', [$start, $end])
|
|
->orWhereBetween('ends_at', [$start, $end])
|
|
->orWhere(function ($q2) use ($start, $end) {
|
|
$q2->where('starts_at', '<=', $start)
|
|
->where('ends_at', '>=', $end);
|
|
});
|
|
})->get();
|
|
|
|
$this->rawEvents = $rawEvents;
|
|
// 🔥 SPLIT EVENTS IN DAYS
|
|
$events = [];
|
|
|
|
foreach ($rawEvents as $event) {
|
|
|
|
$startDate = $event->starts_at
|
|
->copy()
|
|
->setTimezone($tz)
|
|
->startOfDay();
|
|
|
|
$endDate = $event->ends_at
|
|
? $event->ends_at->copy()->setTimezone($tz)->startOfDay()
|
|
: $startDate;
|
|
|
|
for ($date = $startDate->copy(); $date <= $endDate; $date->addDay()) {
|
|
$events[$date->format('Y-m-d')][] = $event;
|
|
}
|
|
}
|
|
|
|
$this->events = collect($events)->map(fn($g) => collect($g));
|
|
}
|
|
|
|
public function updatedSearch()
|
|
{
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function updatedFrom()
|
|
{
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function updatedTo()
|
|
{
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function updateEventTime($eventId, $start, $end)
|
|
{
|
|
$event = \App\Models\Event::findOrFail($eventId);
|
|
|
|
$tz = auth()->user()->timezone ?? 'UTC';
|
|
|
|
$event->update([
|
|
'starts_at' => Carbon::parse($start, $tz)->utc(),
|
|
'ends_at' => $end ? Carbon::parse($end, $tz)->utc() : null,
|
|
]);
|
|
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function moveEventToDate($eventId, $date)
|
|
{
|
|
$event = \App\Models\Event::findOrFail($eventId);
|
|
|
|
$tz = auth()->user()->timezone ?? 'UTC';
|
|
|
|
$start = $event->starts_at->copy()->timezone($tz);
|
|
$end = $event->ends_at?->copy()->timezone($tz);
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 🔥 FALL 1: ALL DAY ODER MULTI DAY
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
$isMultiDay = $end && $start->toDateString() !== $end->toDateString();
|
|
|
|
if ($event->is_all_day || $isMultiDay) {
|
|
|
|
// 👉 Tage-Differenz behalten (NICHT Sekunden!)
|
|
$daySpan = $end
|
|
? $start->startOfDay()->diffInDays($end->startOfDay())
|
|
: 0;
|
|
|
|
// 👉 neuer Start (immer 00:00 bei allday)
|
|
$newStart = Carbon::parse($date, $tz)->startOfDay();
|
|
|
|
// 👉 neuer End
|
|
$newEnd = $end
|
|
? $newStart->copy()->addDays($daySpan)->endOfDay()
|
|
: null;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 🔥 FALL 2: NORMALER TERMIN
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
$duration = $end
|
|
? $start->diffInSeconds($end)
|
|
: 3600;
|
|
|
|
$newStart = Carbon::parse(
|
|
$date . ' ' . $start->format('H:i'),
|
|
$tz
|
|
);
|
|
|
|
$newEnd = $end
|
|
? $newStart->copy()->addSeconds($duration)
|
|
: null;
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| SAVE (UTC!)
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
$event->update([
|
|
'starts_at' => $newStart->utc(),
|
|
'ends_at' => $newEnd?->utc(),
|
|
]);
|
|
|
|
$this->loadEvents();
|
|
}
|
|
|
|
// public function moveEventToDate($eventId, $date)
|
|
// {
|
|
// $event = \App\Models\Event::findOrFail($eventId);
|
|
//
|
|
// $tz = auth()->user()->timezone ?? 'UTC';
|
|
//
|
|
// // aktuelle Zeiten holen (in User TZ)
|
|
// $start = $event->starts_at->copy()->timezone($tz);
|
|
// $end = $event->ends_at?->copy()->timezone($tz);
|
|
//
|
|
// // 🔥 Dauer berechnen (wichtig für Multi-Day)
|
|
// $duration = $end
|
|
// ? $start->diffInSeconds($end)
|
|
// : 3600;
|
|
//
|
|
// // 🔥 neues Datum + gleiche Uhrzeit
|
|
// $newStart = \Carbon\Carbon::parse(
|
|
// $date . ' ' . $start->format('H:i'),
|
|
// $tz
|
|
// )->utc();
|
|
//
|
|
// $newEnd = $end
|
|
// ? $newStart->copy()->addSeconds($duration)
|
|
// : null;
|
|
//
|
|
// $event->update([
|
|
// 'starts_at' => $newStart,
|
|
// 'ends_at' => $newEnd,
|
|
// ]);
|
|
//
|
|
// $this->loadEvents();
|
|
// }
|
|
|
|
public function goToToday()
|
|
{
|
|
$this->date = now(auth()->user()->timezone);
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function next()
|
|
{
|
|
if ($this->view === 'month') $this->date->addMonth();
|
|
if ($this->view === 'week') $this->date->addWeek();
|
|
if ($this->view === 'day') $this->date->addDay();
|
|
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function prev()
|
|
{
|
|
if ($this->view === 'month') $this->date->subMonth();
|
|
if ($this->view === 'week') $this->date->subWeek();
|
|
if ($this->view === 'day') $this->date->subDay();
|
|
|
|
$this->loadEvents();
|
|
}
|
|
|
|
public function moveEventToDateTime($eventId, $date, $time)
|
|
{
|
|
$event = Event::findOrFail($eventId);
|
|
|
|
$tz = auth()->user()->timezone ?? config('app.timezone');
|
|
|
|
if (!preg_match('/^\d{2}:\d{2}$/', $time)) {
|
|
return;
|
|
}
|
|
|
|
[$hour, $minute] = explode(':', $time);
|
|
|
|
$hour = min(max((int)$hour, 0), 23);
|
|
$minute = min(max((int)$minute, 0), 59);
|
|
|
|
// 🔥 WICHTIG: alte Zeiten in USER TZ holen
|
|
$oldStart = $event->starts_at->copy()->setTimezone($tz);
|
|
$oldEnd = $event->ends_at?->copy()->setTimezone($tz);
|
|
|
|
$duration = $oldEnd
|
|
? $oldStart->diffInMinutes($oldEnd)
|
|
: 60;
|
|
|
|
if ($duration <= 0) {
|
|
$duration = 60;
|
|
}
|
|
|
|
// 🔥 neue Zeit in USER TZ setzen
|
|
$newStart = Carbon::parse($date, $tz)->setTime($hour, $minute);
|
|
$newEnd = $newStart->copy()->addMinutes($duration);
|
|
|
|
// 🔥 zurück in UTC speichern
|
|
$event->starts_at = $newStart->copy()->utc();
|
|
$event->ends_at = $newEnd->copy()->utc();
|
|
|
|
$event->save();
|
|
|
|
$this->loadEvents();
|
|
}
|
|
|
|
|
|
public function updateEventTimeAndDate($eventId, $date, $start, $end)
|
|
{
|
|
|
|
$event = Event::findOrFail($eventId);
|
|
|
|
$tz = auth()->user()->timezone ?? config('app.timezone');
|
|
|
|
$start = Carbon::parse($date.' '.$start, $tz)->utc();
|
|
$end = Carbon::parse($date.' '.$end, $tz)->utc();
|
|
|
|
if ($end->lessThanOrEqualTo($start)) {
|
|
$end = $start->copy()->addHour();
|
|
}
|
|
|
|
$event->update([
|
|
'starts_at' => $start,
|
|
'ends_at' => $end,
|
|
]);
|
|
|
|
$this->loadEvents();
|
|
}
|
|
|
|
|
|
public function updateEventRange($id, $startDate, $endDate, $startTime, $endTime)
|
|
{
|
|
$event = Event::findOrFail($id);
|
|
|
|
$tz = auth()->user()->timezone;
|
|
|
|
$start = Carbon::parse("$startDate $startTime", $tz)->utc();
|
|
$end = Carbon::parse("$endDate $endTime", $tz)->utc();
|
|
|
|
if ($end->lessThanOrEqualTo($start)) {
|
|
$end = $start->copy()->addHour();
|
|
}
|
|
|
|
$event->update([
|
|
'starts_at' => $start,
|
|
'ends_at' => $end,
|
|
]);
|
|
|
|
$this->loadEvents();
|
|
}
|
|
|
|
|
|
private function transformAllDayEvents($events)
|
|
{
|
|
return $events->map(function ($event) {
|
|
|
|
$start = $event->starts_at->copy();
|
|
$end = $event->ends_at?->copy() ?? $start;
|
|
|
|
$startDay = $start->startOfDay();
|
|
$endDay = $end->startOfDay();
|
|
|
|
return [
|
|
'id' => $event->id,
|
|
'title' => $event->title,
|
|
'color' => $event->color,
|
|
|
|
'start_day' => $startDay,
|
|
'end_day' => $endDay,
|
|
|
|
'duration_days' => $startDay->diffInDays($endDay) + 1,
|
|
];
|
|
});
|
|
}
|
|
|
|
private function mapAllDayToWeek($events, $weekStart)
|
|
{
|
|
return $events->map(function ($event) use ($weekStart) {
|
|
|
|
$startOffset = $event['start_day']->diffInDays($weekStart, false);
|
|
$endOffset = $event['end_day']->diffInDays($weekStart, false);
|
|
|
|
// clamp auf Woche
|
|
$start = max($startOffset, 0);
|
|
$end = min($endOffset, 6);
|
|
|
|
$span = ($end - $start) + 1;
|
|
|
|
return [
|
|
...$event,
|
|
'left' => ($start / 7) * 100,
|
|
'width' => ($span / 7) * 100,
|
|
];
|
|
});
|
|
}
|
|
|
|
public function getCalendarDaysProperty(): \Illuminate\Support\Collection
|
|
{
|
|
$tz = auth()->user()->timezone;
|
|
|
|
return collect(range(0, 6))->map(function ($i) use ($tz) {
|
|
|
|
$day = $this->date->copy()->startOfWeek()->addDays($i);
|
|
$key = $day->format('Y-m-d');
|
|
|
|
$events = collect($this->events[$key] ?? []);
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 🔥 ALLDAY + MULTIDAY RAUSFILTERN (WICHTIG!)
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
$filtered = $events->reject(function ($event) use ($tz) {
|
|
|
|
$start = $event->starts_at->copy()->setTimezone($tz);
|
|
$end = $event->ends_at?->copy()->setTimezone($tz) ?? $start;
|
|
|
|
// 👉 gleiche Logik wie in getAllDayEventsProperty
|
|
$isMultiDay = $end->startOfDay()->gt($start->startOfDay());
|
|
|
|
return $event->is_all_day || $isMultiDay;
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 🔥 TIMED EVENTS TRANSFORMIEREN
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
$mapped = $filtered
|
|
->map(fn($event) => $this->transformEvent($event, $day, $tz))
|
|
->sortBy('start')
|
|
->values();
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 🔥 OVERLAP FIX
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
$timed = $mapped->map(function ($event) use ($mapped) {
|
|
|
|
$overlapping = $mapped->filter(function ($e) use ($event) {
|
|
return $e['start'] < $event['end']
|
|
&& $e['end'] > $event['start'];
|
|
})->values();
|
|
|
|
$count = max($overlapping->count(), 1);
|
|
|
|
$index = $overlapping->pluck('id')->search($event['id']);
|
|
$index = $index === false ? 0 : $index;
|
|
|
|
$event['count'] = $count;
|
|
$event['index'] = $index;
|
|
|
|
return $event;
|
|
});
|
|
|
|
return [
|
|
'date' => $day,
|
|
'key' => $key,
|
|
'timed' => $timed,
|
|
];
|
|
});
|
|
}
|
|
// public function getCalendarDaysProperty(): \Illuminate\Support\Collection
|
|
// {
|
|
// $tz = auth()->user()->timezone;
|
|
//
|
|
// return collect(range(0, 6))->map(function ($i) use ($tz) {
|
|
//
|
|
// $day = $this->date->copy()->startOfWeek()->addDays($i);
|
|
// $key = $day->format('Y-m-d');
|
|
//
|
|
// $events = collect($this->events[$key] ?? []);
|
|
//
|
|
// /*
|
|
// |--------------------------------------------------------------------------
|
|
// | 🔥 NUR ALLDAY RAUS
|
|
// |--------------------------------------------------------------------------
|
|
// */
|
|
// $mapped = $events
|
|
// ->reject(fn($e) => $e->is_all_day)
|
|
// ->map(fn($event) => $this->transformEvent($event, $day, $tz))
|
|
// ->sortBy('start')
|
|
// ->values();
|
|
//
|
|
// /*
|
|
// |--------------------------------------------------------------------------
|
|
// | OVERLAP
|
|
// |--------------------------------------------------------------------------
|
|
// */
|
|
// $timed = $mapped->map(function ($event) use ($mapped) {
|
|
//
|
|
// $overlapping = $mapped->filter(function ($e) use ($event) {
|
|
// return $e['start'] < $event['end']
|
|
// && $e['end'] > $event['start'];
|
|
// })->values();
|
|
//
|
|
// $count = max($overlapping->count(), 1);
|
|
//
|
|
// $index = $overlapping->pluck('id')->search($event['id']);
|
|
// $index = $index === false ? 0 : $index;
|
|
//
|
|
// $event['count'] = $count;
|
|
// $event['index'] = $index;
|
|
//
|
|
// return $event;
|
|
// });
|
|
//
|
|
// return [
|
|
// 'date' => $day,
|
|
// 'key' => $key,
|
|
// 'timed' => $timed,
|
|
// ];
|
|
// });
|
|
// }
|
|
|
|
// public function getCalendarDaysProperty(): \Illuminate\Support\Collection
|
|
// {
|
|
// $tz = auth()->user()->timezone;
|
|
//
|
|
// return collect(range(0, 6))->map(function ($i) use ($tz) {
|
|
//
|
|
// $day = $this->date->copy()->startOfWeek()->addDays($i);
|
|
// $key = $day->format('Y-m-d');
|
|
//
|
|
// $events = collect($this->events[$key] ?? []);
|
|
//
|
|
// // 👉 AllDay Events
|
|
// $allDay = $events->filter(fn($e) => $e->is_all_day);
|
|
//
|
|
// // 👉 Timed Events transformieren
|
|
// $mapped = $events
|
|
// ->reject(fn($e) => $e->is_all_day)
|
|
// ->map(fn($event) => $this->transformEvent($event, $day, $tz))
|
|
// ->sortBy('start') // 🔥 wichtig für stabile Reihenfolge
|
|
// ->values();
|
|
//
|
|
// // 👉 Overlap berechnen
|
|
// $timed = $mapped->map(function ($event) use ($mapped) {
|
|
//
|
|
// $overlapping = $mapped->filter(function ($e) use ($event) {
|
|
// return $e['start'] < $event['end']
|
|
// && $e['end'] > $event['start'];
|
|
// })->values();
|
|
//
|
|
// $count = max($overlapping->count(), 1);
|
|
//
|
|
// $index = $overlapping->pluck('id')->search($event['id']);
|
|
// $index = $index === false ? 0 : $index;
|
|
//
|
|
// $event['count'] = $count;
|
|
// $event['index'] = $index;
|
|
//
|
|
// return $event;
|
|
// });
|
|
//
|
|
// return [
|
|
// 'date' => $day,
|
|
// 'key' => $key,
|
|
// 'allDay' => $allDay,
|
|
// 'timed' => $timed,
|
|
// ];
|
|
// });
|
|
// }
|
|
|
|
|
|
// public function getAllDayEventsProperty()
|
|
// {
|
|
// $tz = auth()->user()->timezone;
|
|
//
|
|
// $weekStart = $this->date->copy()->startOfWeek();
|
|
// $weekEnd = $this->date->copy()->endOfWeek();
|
|
//
|
|
// return collect($this->rawEvents)
|
|
// ->filter(fn($event) => $event->is_all_day == true)
|
|
// ->map(function ($event) use ($weekStart, $weekEnd, $tz) {
|
|
//
|
|
// $start = $event->starts_at->copy()->setTimezone($tz);
|
|
// $end = $event->ends_at
|
|
// ? $event->ends_at->copy()->setTimezone($tz)
|
|
// : $start;
|
|
//
|
|
// if ($end->lt($weekStart) || $start->gt($weekEnd)) {
|
|
// return null;
|
|
// }
|
|
//
|
|
// $startClamped = $start->copy()->max($weekStart)->startOfDay();
|
|
// $endClamped = $end->copy()->min($weekEnd)->startOfDay();
|
|
//
|
|
// $startOffset = $weekStart->diffInDays($startClamped);
|
|
// $endOffset = $weekStart->diffInDays($endClamped);
|
|
//
|
|
// $span = max(($endOffset - $startOffset) + 1, 1);
|
|
//
|
|
// return [
|
|
// 'id' => $event->id,
|
|
// 'title' => $event->title,
|
|
// 'color' => $event->color,
|
|
//
|
|
// // 🔥 DAS HAT GEFEHLT
|
|
// 'start' => $startClamped,
|
|
// 'end' => $endClamped,
|
|
//
|
|
// 'left' => ($startOffset / 7) * 100,
|
|
// 'width' => ($span / 7) * 100,
|
|
// ];
|
|
// })
|
|
// ->filter()
|
|
// ->values();
|
|
// }
|
|
|
|
// public function getAllDayEventsProperty()
|
|
// {
|
|
// $tz = auth()->user()->timezone;
|
|
//
|
|
// $weekStart = $this->date->copy()->startOfWeek()->startOfDay();
|
|
// $weekEnd = $this->date->copy()->endOfWeek()->endOfDay();
|
|
//
|
|
// $events = collect($this->rawEvents)
|
|
//
|
|
// ->filter(function ($event) use ($tz) {
|
|
//
|
|
// $start = $event->starts_at->copy()->setTimezone($tz);
|
|
// $end = $event->ends_at?->copy()->setTimezone($tz) ?? $start;
|
|
//
|
|
// // 👉 echtes AllDay oder MultiDay
|
|
// return $event->is_all_day
|
|
// || $end->startOfDay()->gt($start->startOfDay());
|
|
// })
|
|
//
|
|
// ->map(function ($event) use ($tz, $weekStart, $weekEnd) {
|
|
//
|
|
// $start = $event->starts_at->copy()->setTimezone($tz);
|
|
// $end = $event->ends_at
|
|
// ? $event->ends_at->copy()->setTimezone($tz)
|
|
// : $start;
|
|
//
|
|
// /*
|
|
// |--------------------------------------------------------------------------
|
|
// | ❌ außerhalb → raus
|
|
// |--------------------------------------------------------------------------
|
|
// */
|
|
// if ($end->lt($weekStart) || $start->gt($weekEnd)) {
|
|
// return null;
|
|
// }
|
|
//
|
|
// /*
|
|
// |--------------------------------------------------------------------------
|
|
// | 🔥 CLAMP AUF WOCHE
|
|
// |--------------------------------------------------------------------------
|
|
// */
|
|
// $startClamped = $start->lt($weekStart)
|
|
// ? $weekStart->copy()
|
|
// : $start->copy();
|
|
//
|
|
// $endClamped = $end->gt($weekEnd)
|
|
// ? $weekEnd->copy()
|
|
// : $end->copy();
|
|
//
|
|
// /*
|
|
// |--------------------------------------------------------------------------
|
|
// | 🔥 WICHTIG: START/END auf DAY LEVEL
|
|
// |--------------------------------------------------------------------------
|
|
// */
|
|
// $startDay = $startClamped->copy()->startOfDay();
|
|
// $endDay = $endClamped->copy()->startOfDay();
|
|
//
|
|
// /*
|
|
// |--------------------------------------------------------------------------
|
|
// | 🔥 OFFSET BERECHNUNG (STABIL)
|
|
// |--------------------------------------------------------------------------
|
|
// */
|
|
// $startOffset = $weekStart->diffInDays($startDay);
|
|
// $endOffset = $weekStart->diffInDays($endDay);
|
|
//
|
|
// $span = max(1, ($endOffset - $startOffset) + 1);
|
|
//
|
|
// return [
|
|
// 'id' => $event->id,
|
|
// 'title' => $event->title,
|
|
// 'color' => $event->color,
|
|
//
|
|
// 'start' => $startDay,
|
|
// 'end' => $endDay,
|
|
//
|
|
// 'left' => ($startOffset / 7) * 100,
|
|
// 'width' => ($span / 7) * 100,
|
|
// ];
|
|
// })
|
|
//
|
|
// ->filter()
|
|
// ->sortBy('start')
|
|
// ->values();
|
|
//
|
|
// return $this->stackAllDayEvents($events);
|
|
// }
|
|
|
|
public function getAllDayEventsProperty()
|
|
{
|
|
$tz = auth()->user()->timezone;
|
|
|
|
$weekStart = $this->date->copy()->startOfWeek()->startOfDay();
|
|
$weekEnd = $this->date->copy()->endOfWeek()->endOfDay();
|
|
|
|
$events = collect($this->rawEvents)
|
|
|
|
->filter(function ($event) use ($tz) {
|
|
|
|
$start = $event->starts_at->copy()->setTimezone($tz);
|
|
$end = $event->ends_at?->copy()->setTimezone($tz) ?? $start;
|
|
|
|
$isMultiDay = $end->startOfDay()->gt($start->startOfDay());
|
|
|
|
return $event->is_all_day || $isMultiDay;
|
|
})
|
|
|
|
->map(function ($event) use ($tz, $weekStart, $weekEnd) {
|
|
|
|
$start = $event->starts_at->copy()->setTimezone($tz)->startOfDay();
|
|
$end = $event->ends_at
|
|
? $event->ends_at->copy()->setTimezone($tz)->startOfDay()
|
|
: $start;
|
|
|
|
if ($end->lt($weekStart) || $start->gt($weekEnd)) {
|
|
return null;
|
|
}
|
|
|
|
$startClamped = $start->lt($weekStart) ? $weekStart->copy() : $start;
|
|
$endClamped = $end->gt($weekEnd) ? $weekEnd->copy() : $end;
|
|
|
|
$startOffset = $weekStart->diffInDays($startClamped);
|
|
$endOffset = $weekStart->diffInDays($endClamped);
|
|
|
|
$span = ($endOffset - $startOffset) + 1;
|
|
|
|
return [
|
|
'id' => $event->id,
|
|
'title' => $event->title,
|
|
'color' => $event->color,
|
|
|
|
'start' => $startClamped,
|
|
'end' => $endClamped,
|
|
|
|
'col' => $startOffset,
|
|
'span' => $span,
|
|
];
|
|
})
|
|
|
|
->filter()
|
|
->sortBy('start')
|
|
->values();
|
|
|
|
return $this->stackAllDayEvents($events);
|
|
}
|
|
|
|
private function stackAllDayEvents($events)
|
|
{
|
|
$rows = [];
|
|
|
|
foreach ($events as $event) {
|
|
|
|
$placed = false;
|
|
|
|
foreach ($rows as &$row) {
|
|
|
|
$collision = collect($row)->first(function ($e) use ($event) {
|
|
|
|
$aStart = $event['col'];
|
|
$aEnd = $event['col'] + $event['span'] - 1;
|
|
|
|
$bStart = $e['col'];
|
|
$bEnd = $e['col'] + $e['span'] - 1;
|
|
|
|
return !($aEnd < $bStart || $aStart > $bEnd);
|
|
});
|
|
|
|
if (!$collision) {
|
|
$row[] = $event;
|
|
$placed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$placed) {
|
|
$rows[] = [$event];
|
|
}
|
|
}
|
|
|
|
return collect($rows)->map(function ($row, $rowIndex) {
|
|
|
|
return collect($row)->map(function ($event) use ($rowIndex) {
|
|
return [
|
|
...$event,
|
|
'row' => $rowIndex
|
|
];
|
|
});
|
|
|
|
})->flatten(1);
|
|
}
|
|
|
|
private function transformEvent($event, $day, $tz)
|
|
{
|
|
$start = $event->starts_at->copy()->setTimezone($tz);
|
|
$end = $event->ends_at?->copy()->setTimezone($tz);
|
|
|
|
$dayStart = $day->copy()->startOfDay();
|
|
$dayEnd = $day->copy()->endOfDay();
|
|
|
|
// 👉 Seminar / MultiDay Fix
|
|
if ($end && $start->diffInDays($end) > 0 && !$event->is_all_day) {
|
|
|
|
$effectiveStart = $dayStart->copy()->setTime($start->hour, $start->minute);
|
|
$effectiveEnd = $dayStart->copy()->setTime($end->hour, $end->minute);
|
|
|
|
// 👉 Exceptions
|
|
$exceptions = is_array($event->exceptions)
|
|
? $event->exceptions
|
|
: json_decode($event->exceptions ?? '[]', true);
|
|
|
|
$exception = collect($exceptions)
|
|
->firstWhere('date', $day->format('Y-m-d'));
|
|
|
|
if ($exception) {
|
|
if (!empty($exception['start'])) {
|
|
[$h, $m] = explode(':', $exception['start']);
|
|
$effectiveStart->setTime($h, $m);
|
|
}
|
|
|
|
if (!empty($exception['end'])) {
|
|
[$h, $m] = explode(':', $exception['end']);
|
|
$effectiveEnd->setTime($h, $m);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
$effectiveStart = $start;
|
|
$effectiveEnd = $end ?? $start->copy()->addHour();
|
|
}
|
|
|
|
$hourHeight = 64;
|
|
|
|
$top = ($effectiveStart->hour * $hourHeight)
|
|
+ (($effectiveStart->minute / 60) * $hourHeight);
|
|
|
|
$height = max($effectiveStart->diffInMinutes($effectiveEnd), 1) / 60 * $hourHeight;
|
|
|
|
|
|
return [
|
|
'model' => $event,
|
|
'id' => $event->id,
|
|
'title' => $event->title,
|
|
'top' => $top,
|
|
'height' => $height,
|
|
'start' => $effectiveStart,
|
|
'end' => $effectiveEnd,
|
|
];
|
|
}
|
|
|
|
public function colorToRgba($color, $opacity = 1): string
|
|
{
|
|
if (!$color) return 'rgba(99,102,241,0.8)'; // fallback
|
|
|
|
$color = ltrim($color, '#');
|
|
|
|
$r = hexdec(substr($color, 0, 2));
|
|
$g = hexdec(substr($color, 2, 2));
|
|
$b = hexdec(substr($color, 4, 2));
|
|
|
|
return "rgba($r, $g, $b, $opacity)";
|
|
}
|
|
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.calendar.index')
|
|
->layout('layouts.app');
|
|
}
|
|
}
|