104 lines
3.8 KiB
PHP
104 lines
3.8 KiB
PHP
<?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,
|
||
];
|
||
}
|
||
} |