This repository has been archived on 2026-09-27. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
tutoring/app/Services/CalendarService.php
T
2026-01-14 00:54:08 +00:00

104 lines
3.8 KiB
PHP
Raw 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 Mon–Fri 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 wasn’t 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,
];
}
}