tutoring/app/Services/CalendarService.php

104 lines
3.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services;
use App\Models\Slot;
use Carbon\Carbon;
class CalendarService
{
/**
* Build calendar grid for MonFri with your six daily times.
*
* @param Carbon $start Monday of the week you want to display
* @param Carbon $now Current time (use 'Europe/London')
* @param bool $applyShortNotice If true, mark ≤ 120 min as closed (student view)
* @return array{headers: array<int, array{date:string,label:string}>, rows: array<int, array{time:string,label:string,cells:array<int,array{date:string,time:string,slot:int|null,status:string,class:string,name:?string}>}>, times: string[]}
*/
public function build(Carbon $start, Carbon $now, bool $applyShortNotice): array
{
$end = $start->copy()->addDays(6);
$weekdays = [1,2,3,4,5]; // Mon..Fri
$times = ['09:00:00','10:00:00','11:00:00','14:00:00','15:00:00','16:00:00'];
// Get all slots with bookings in the visible week
$slots = Slot::with(['bookings' => function ($q) use ($start, $end) {
$q->whereBetween('date', [$start->toDateString(), $end->toDateString()]);
}])->get();
// Map slots by weekday/time for O(1) lookup in the loop
$map = [];
foreach ($slots as $s) {
$map[(int)$s->weekday][$s->time] = $s;
}
// Column headers
$headers = [];
for ($i = 0; $i < 5; $i++) {
$d = $start->copy()->addDays($i);
$headers[] = [
'date' => $d->toDateString(),
'label' => $d->format('D d M'),
];
}
// Rows (each time)
$rows = [];
foreach ($times as $time) {
$cells = [];
foreach ($weekdays as $wd) {
$date = $start->copy()->addDays($wd - 1)->toDateString();
$slot = $map[$wd][$time] ?? null;
$booking = $slot ? $slot->bookings->firstWhere('date', $date) : null;
// Compute short-notice closure only when required (students)
$sessionStart = Carbon::createFromFormat('Y-m-d H:i:s', "{$date} {$time}", $now->timezone);
$minutesUntil = $now->diffInMinutes($sessionStart, false);
$tooSoon = $applyShortNotice ? ($minutesUntil <= 120) : false;
// Decide status + cell class
$status = 'closed';
$class = 'bg-gray-100';
$name = null;
if ($booking) {
if ($booking->status === 'booked') { $status = 'booked'; $class = 'bg-red-200'; }
elseif ($booking->status === 'blocked') { $status = 'blocked'; $class = 'bg-yellow-200'; }
} elseif (!$slot) {
$status = 'not_seeded'; // slot wasnt seeded for this weekday/time
} elseif ($minutesUntil < 0 || $tooSoon) {
$status = 'closed';
} else {
$status = 'free';
$class = 'bg-green-100';
}
if ($booking && $booking->student_name) {
$name = $booking->student_name;
}
$cells[] = [
'date' => $date,
'time' => $time,
'slot' => $slot?->id,
'status' => $status, // free | booked | blocked | closed | not_seeded
'class' => $class,
'name' => $name,
];
}
$rows[] = [
'time' => $time,
'label' => substr($time, 0, 5),
'cells' => $cells,
];
}
return [
'headers' => $headers,
'rows' => $rows,
'times' => $times,
];
}
}